Sergio Xalambrí
March 11, 2019

# Cómo crear y publicar un módulo para npm

Aprende como crear un módulo y publicarlo a npm configurando Prettier, ESLint, pruebas, tipos y más.

JavaScript tiene el ecosistema más grande de módulos los cuales se alojan en GitHub y se publican al registro de npm para su posterior uso en proyectos.

Usar un módulo desde npm es bastante sencillo, solo necesitamos correr
`npm install <module>` o `yarn add <module>` (reemplazando `<module>` por el nombre del módulo a instalar) y listo, el módulo se va a registrar como dependencia de nuestro proyecto y vamos a poder importarlo en nuestro código.

En este tutorial crearemos un módulo, lo más completo posible, y lo publicaremos al registro de npm para que cualquiera lo use.

## Inicia el proyecto

Primero hay que iniciar el proyecto, para esto se puede usar npx, una herramienta que viene junto a npm que permite instalar, temporalmente, y ejecutar módulos desde npm. Creá una carpeta para el proyecto con el nombre del proyecto, digamos `my-module`, y dentro ejecutá los siguientes comandos.

    git init
    echo "# my-module" > README.md
    npx license MIT -o "Sergio Xalambrí" > LICENSE
    npx gitignore node
    npx covgen "hello@sergiodxa.com"
    npm init -y # o yarn init -y
    git add -A
    git commit -m "Initial commit"

Vayamos línea por línea viendo que hace cada comando.

1. Se inicia git en la carpeta de nuestro proyecto
2. Se crea un archivo README.md con el contenido `# my-module`
3. Se genera una licencia MIT con nuestro nombre y la guardamos en el archivo LICENSE
4. Se genera un `.gitignore` genérico para proyectos de Node.js y JavaScript
5. Se genera un archivo CONTRIBUTING.md con nuestro email siguiendo el Contributor Covenant
6. Se inicia el proyecto de Node.js haciendo que se cree un package.json
7. Se agregan todos los archivos a git
8. Se crea un commit inicial

Con estos ya está todo listo para empezar el proyecto, y nuestra carpeta debería tener estos archivos.

- .gitignore
- LICENSE
- README.md
- package.json

## Manifiesto

El manifiest es el archivo `package.json`, este posee información del módulo como el nombre, versión, autor y dependencias, entre otras. Con el sexto comando que corriste antes se creó uno inicial que debería verse más o menos así

    {
      "name": "my-module",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo "Error: no test specified" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC"
    }

> Nota: Depende de tu configuración de npm puede haber más o menos información inicial, este es uno básico.

Vamos a ver que es cada propiedad.

- `name` es el nombre del módulo, por defecto el nombre de la carpeta, el nombre puede además incluír un namespace en la forma de `@namespace/my-module`, el namespace debe ser o tu nombre de usuario en npm o una organización a la que pertenezcas.
- `version` es la versión del módulo, siguiente el sistema de versionamiento semático (semver).
- `description` es la descripción del proyecto, se usa en el registro de npm para mostrar que hace el proyecto, lo ideal es que sea corta y concisa.
- `main` es la ubicación u nombre del archivo principal de nuestro código, por defecto es `index.js`, en caso de que decidamos usar otro archivo diferente o con otro nombre vamos a necesitarlo.
- `scripts` es un objeto que posee listas de scripts con nombres que podemos luego ejecutar haciendo `npm run <script>` o `yarn <script>`, por defecto viene un script `test` que muestra une error (nota: `test` es uno de varios scripts especiales que se pueden ejecutar con `npm <script>` sin el run).
- `keywords` son palabras claves que puede usar el registro para que nuestro módulo salga en ciertas búsquedas
- `author` es la información del autor, puede ser un string con el formato `name <url> (email)` o un objeto con esas propiedades
- `license` es la licencia bajo la cual publicas el módulo, en nuestro caso vamos a usar MIT por lo que deberíamos cambiarlo.

## El código

Creá una carpeta `src` con el archivo `index.js` que tenga esto dentro:

    function hello(name = "Sergio") {
      return `Hello, ${name}`;
    }

    export default hello;

Algo super simple, podés poner cualquier contenido en realidad, pero para el ejemplo voy a usar ese. Nuestro proyecto ahora debería verse así.

- .gitignore
- LICENSE
- README.md
- package.json
- src
  - index.js

## Dependencias

El código de este módulo es super simple y no necesita dependencias, en caso de que las necesite se pueden instalar haciendo

    npm install another-module
    # yarn add another-module

También es importante saber que existen varios tipos de dependencias que en el `package.json` se definen con distintos nombres.

- `dependencies` son las dependencias del código las cuales se usan directamente haciendo `import` en el código del módulo
- `devDependencies` se usan solo en desarrollo, normalmente son herramientas que se usan al desarrollar el módulo, como pueden ser frameworks de pruebas o herramientas de compilación
- `peerDependencies` son dependencias que se usan directo en el código, pero que se espera que el usuario del módulo provea, esto es normal para que se pueda usar cualquier versión de estas dependencias.

Hay más, pero estas son las más comunes, en el caso de `my-module` no hay `dependencies` pero si va a haber `devDependencies`, para instalarlas se usa el comando

    npm install -D another-module
    # yarn add -D another-module

Donde la `-D` indica que es una dependencia de desarrollo. Las que se van a usar en el módulo, y que yo recomiendo son dos.

- Jest
- microbundle

El primero es un framework de pruebas creado por Facebook que va a servir para automatizar las pruebas del código.

El segundo es una herramienta del creador de Preact, y otro muchos módulos pequeños, que sirve para hacer distintas versiones del código compatibles con varios sistemas de módulos que existen, en total soporta 3, un módulo de CommonJS para usar en Node.js, uno de UMD para usar en una etiqueta script en el navegador y uno de ECMAScript que usa `export` e `import` para que herramientas como webpack o Parcel puedan optimizar el código final.

Para instalar ambos el comando final sería

    npm install -D jest microbundle
    # yarn add -D jest microbundle

> Nota: como se ve, se pueden poner varios módulos separados por espacios

## Configurando los scripts

Ya teniendo instaladas las dependencias hay que configurar scripts en el `package.json` para usarlas. Si abrís el archivo debería estar actualizado para verse así.

    {
      "name": "@sergiodxa/my-module",
      "version": "1.0.0",
      "description": "My super cool module",
      "main": "index.js",
      "scripts": {
        "test": "echo "Error: no test specified" && exit 1"
      },
      "keywords": [],
      "author": "Sergio Xalambrí <hello@sergiodxa.com> (https://sergiodxa.com)",
      "license": "MIT",
      "devDependencies": {
        "jest": "^24.1.0",
        "microbundle": "^0.9.0"
      }
    }

Ahora el `package.json` tiene la propiedad `devDependencies`, además ya deberías haber cambiando `license` por MIT, poner tu nombre, email y sitio web en `author` y una descripción. El nombre del módulo en este caso como ya está usado es necesario ponerle un namespace.

Ya visto eso, para configurar los scripts solo hay que actualizar el objeto `scripts` con el siguiente.

    {
      "test": "jest",
      "build": "microbundle"
    }

El primero es para ejecutar las pruebas y el segundo para hacer "build" del módulo usanod microbundle. el proyecto debería ahora verse así (el `yarn.lock` solo aparece si usaste yarn para instalar módulo, si usas npm habría un `package-lock.json`).

- .gitignore
- LICENSE
- README.md
- package.json
- yarn.lock
- src
  - index.js

## Agregando pruebas

Idealmente un módulo siempre debería tener pruebas automatizadas, en este caso las pruebas que se pueden agregar son bastantes simples, usar la función `hello` con y sin valor de `name`. Para esto creamos un archivo `src/index.test.js` con el archivo de del test.

    mport hello from ".";

    describe("it should say hello", () => {
      it("should greet 'Sergio'", () => {
        expect(hello()).toBe("Hello, Sergio");
      });

      it("should greet 'Daniel'", () => {
        expect(hello("Daniel")).toBe("Hello, Daniel");
      });
    });

Hasta ahora nuestro proyecto debería verse así en el sistema de archivos

- .gitignore
- LICENSE
- README.md
- package.json
- yarn.lock
- src
  - index.js
  - index.test.js

### Soportando ESModules en Jest

Como usamos módulo de ECMAScript en nuestro código y Jest no lo soporta (debido a que Node.js no lo soporta) entonces necesitamos usar una herramienta para dar soporte.

Para estos usamos `babel-jest` y `@babel/preset-env`.

    npm install -D babel-jest @babel/preset-env
    # yarn add -D babel-jest @babel/preset-env

Ahora creamos un `babel.config.js` en la raíz del proyecto y colocamos el siguiente código.

    module.exports = {
      presets: ["@babel/preset-env"]
    };

El proyecto debería verse ahora de esta forma.

- .gitignore
- LICENSE
- README.md
- babel.config.js
- package.json
- yarn.lock
- src
  - index.js
  - index.test.js

### Corriendo las pruebas

Con todo listo, ya podemos ver si nuestro módulo funciona, para esto corremos las pruebas con `yarn test` y deberíamos ver algo así.

    $ yarn test
    yarn run v1.13.0
    $ jest
     PASS  src/index.test.js
      it should say hello
        ✓ should greet 'Sergio' (4ms)
        ✓ should greet 'Daniel'

    Test Suites: 1 passed, 1 total
    Tests:       2 passed, 2 total
    Snapshots:   0 total
    Time:        2.1s
    Ran all test suites.
    ✨  Done in 3.35s.

¡Si vemos esto es que todo pasó y el código funciona!

## Construyendo archivos para producción

Ahora es necesario construir los archivos para producción de nuestro módulo, los que vamos a publicar a npm. Esto lo hacemos con la dependencia de desarrollo microbundle que instalamos antes y lo ejecutamos con `yarn build`.

Por defecto microbundle va a colocar todos los archivos en la raíz del proyecto, vamos a configurarlo para que queden en una carpeta `dist` que podemos agregar al archivo `.gitignore`, para configurar microbundle, para configurarlo usamos el `package.json`.

    {
      "name": "@sergiodxa/my-module",
      "version": "1.0.0",
      "description": "My super cool module",
      "main": "dist/index.js",
      "umd:main": "dist/index.umd.js",
      "module": "dist/index.mjs",
      "source": "src/index.js",
      "scripts": {
        "test": "jest",
        "build": "microbundle"
      },
      "keywords": [],
      "author": "Sergio Xalambrí <hello@sergiodxa.com> (https://sergiodxa.com)",
      "license": "MIT",
      "dependencies": {},
      "devDependencies": {
        "@babel/preset-env": "^7.3.4",
        "babel-jest": "^24.4.0",
        "jest": "^24.1.0",
        "microbundle": "^0.9.0"
      }
    }

Agregando `main` con la ruta del archivo para Node.js (CJS), `umd:main` para el archivo UMD y `module` para el archivo de ESM le estamos diciendo a microbundle que los archivos que genere deben estar en esas ubicaciones. La propiedad `source` indica el archivo fuente que va a usar microbundle para empezar a construir nuestro módulo.

Si ahora ejecutamos `npm run build` o `yarn build` va a generar la carpeta `dist` con el código.

- .gitignore
- LICENSE
- README.md
- babel.config.js
- package.json
- yarn.lock
- dist
  - index.js
  - index.js.map
  - index.mjs
  - index.mjs.map
  - index.umd.js
  - index.umd.js.map
- src
  - index.js
  - index.test.js

## Configurando `prepublish`

Algo que puede pasar es que nos olvidemos de hacer `build` antes de publicar nuestro módulo, para esto podemos definir un hook en nuestro scripts del `package.json`, para esto npm nos ofrece poner un script con el prefijo `pre`, este script va a correr antes de ejecutar otro script prefijado. Mejor veamos con un ejemplo, si hay un script `build` podemos tener `prebuild` para ejecutar otro script antes de hacer `build`.

En el caso de `publish` que es usado para publicar un módulo podemos poner `prepublish` donde vamos a ejecutar `npm run build` para hacer build del proyecto.

    {
      "name": "@sergiodxa/my-module",
      "version": "1.0.0",
      "description": "My super cool module",
      "main": "dist/index.js",
      "umd:main": "dist/index.umd.js",
      "module": "dist/index.mjs",
      "source": "src/index.js",
      "scripts": {
        "test": "jest",
        "build": "microbundle",
        "prepublish": "npm run build"
      },
      "keywords": [],
      "author": "Sergio Xalambrí <hello@sergiodxa.com> (https://sergiodxa.com)",
      "license": "MIT",
      "dependencies": {},
      "devDependencies": {
        "@babel/preset-env": "^7.3.4",
        "babel-jest": "^24.4.0",
        "jest": "^24.1.0",
        "microbundle": "^0.9.0"
      }
    }

Con esto, cuando vayamos a publicar nuestro módulo vamos a estar seguros de que antes se hizo build. Adicionalmente podemos agregar un `prebuild` script para ejecutar `npm test` y nunca hacer build si las pruebas no pasan primero.

## Evitándose commitear archivos con errores

Otra cosa que podemos hacer es asegurarnos de nunca commitear a nuestro repositorio código que no pase las pruebas o que no pase un linter para ver que esté bien escrito. Para esto existen unas herramientas llamadas husky y lint-staged que nos sirven para ejecutar un script antes de hacer commit.

Para usarlas primero necesitamos instalarlas.

    npm install -D husky lint-staged
    # yarn add -D husky lint-staged

Después a nuestro `package.json` le agregamos la configuración de estas herramientas.

    {
      "husky": {
        "hooks": {
          "pre-commit": "npm test"
        }
      }
    }

Con esa configuración vamos a ejecutar las pruebas de nuestro módulo antes de hacer commit, si las pruebas no pasan entonces no se permite hacer commit hasta que pasen, con esto nos aseguramos de que nada se guarda en Git si las pruebas están rotas.

También instalamos `lint-staged`, esta herramienta nos permite ejecutar algo solo para los archivos actualizados o agregados. Podemos usarlo entonces para usar un linter y asegurarnos de que el código cumpla ciertas reglas.

### Configurando un Linter

Para asegurarnos de que nuestro código cumple ciertas reglas podemos usar un Linter, un linter lo que hace es leer nuestro código de forma estática (sin ejecutarlo) y detectar errores según ciertas reglas preconfiguradas.

Vamos a usar ESLint y Prettier para asegurarnos de que nuestro código no tenga errores y esté escrito con un estilo igual sin importar cuantas personas trabajen el código del módulo.

    npm install -D eslint eslint-plugin-prettier eslint-config-prettier prettier
    # yarn add -D eslint eslint-plugin-prettier eslint-config-prettier prettier

Ahora vamos a configurar ESLint creando un archivo `.eslintrc` con el siguiente contenido.

    {
      "extends": ["prettier"],
      "plugins": ["prettier"],
      "parserOptions": {
        "ecmaVersion": 2018
      },
      "env": {
        "node": true,
        "es6": true
      }
    }

Con esto le decimos a ESLint que extienda la configuración de Prettier, que lo use como Plugin, que soporte ECMAScript 2018 y que el código se ejecuta en entornos ES6 o más y Node.js. Ahora hay que hacer que en cada cambio a un archivo `.js` se ejecute prettier y ESLint, para eso configuramos en el `package.json` lo siguiente.

    {
      "husky": {
        "hooks": {
          "pre-commit": "npm test && lint-staged"
        }
      },
      "lint-staged": {
        "*.js": [
          "prettier --write",
          "eslint --fix",
          "git add"
        ],
        "*.{json,md}": [
          "prettier --write",
          "git add"
        ]
      }
    }

Ahora Prettier va a arreglar cualquier problema en archivos `.json` y `.md` y Prettier y ESLint van a ver problemas en archivos `.js`.

## Agregando tipado

TypeScript es cada vez más popular en la comundidad de JavaScript, FlowType aunque no tan popular tiene sus usuarios, incluso si tu código no usa directamente estos lenguajes es posible proveer los tipos de datos que usa nuestro módulo y ayudar a quienes usan estas herramientas.

Para esto vamos a crear un archivo `index.d.ts` y un archivo `index.js.flow` con los tipos de datos de nuestro módulo, en este caso es bastante simple.

    export default function hello(name: string): string;

Esos son todos los tipos necesarios por nuestro módulo. Este mismo código se puede agregar a ambos archivos. Luego de esto nuestro proyecto debería verse así.

- .gitignore
- LICENSE
- README.md
- babel.config.js
- index.d.ts
- index.js.flow
- package.json
- yarn.lock
- dist
  - index.js
  - index.js.map
  - index.mjs
  - index.mjs.map
  - index.umd.js
  - index.umd.js.map
- src
  - index.js
  - index.test.js

## Publicando

Ya con todo listo y configurado, es hora de publicar el módulo, primero hay que estar seguro de hacer commit de todos los archivos cambiados y hacerles push a nuestro repositorio de Github. Después vamos a definir que archivos vamos a subir a npm creando en nuestro `package.json` una key `files` con un array de los archivos y carpetas a subir.

    {
      "name": "@sergiodxa/my-module",
      "version": "1.0.0",
      "description": "My super cool module",
      "main": "dist/index.js",
      "umd:main": "dist/index.umd.js",
      "module": "dist/index.mjs",
      "source": "src/index.js",
      "scripts": {
        "test": "jest",
        "prebuild": "npm test",
        "build": "microbundle",
        "prepublish": "npm run build"
      },
      "keywords": [],
      "author": "Sergio Xalambrí <hello@sergiodxa.com> (https://sergiodxa.com)",
      "license": "MIT",
      "dependencies": {},
      "devDependencies": {
        "@babel/preset-env": "^7.3.4",
        "babel-jest": "^24.4.0",
        "husky": "^1.3.1",
        "jest": "^24.1.0",
        "lint-staged": "^8.1.5",
        "microbundle": "^0.9.0"
      },
      "husky": {
        "hooks": {
          "pre-commit": "npm test && lint-staged"
        }
      },
      "lint-staged": {
        "*.js": [
          "prettier --write",
          "eslint --fix",
          "git add"
        ],
        "*.{json,md}": [
          "prettier --write",
          "git add"
        ]
      },
      "files": [
        "dist",
        "index.d.ts",
        "index.js.flow",
        "package.json",
        "README.md"
      ]
    }

¡Ahora sí, hora de publicar¡ Esto lo hacemos con el siguiente comando.

    npm publish

Si da un error por usar un namespace diciéndo que pagues para publicar módulos privados hay que agregar `--access public` al comando.

    npm publish --access public

¡Ya con esto va a estar publicado! Este mismo módulo se puede bajar desde @sergiodxa/my-module.

## Palabras finales

Son muchos pasos, pero no todos son realmente necesarios, con tener el `package.json` y el código es suficiente para hacer `publish`, en ese artículo fuimos un poco más allá agregando un build, pruebas automatizadas, linter y más cosas para asegurarnos de publicar un buen módulo lo más completo.