Como desarrollar un paquete npm con TypeScript y TDD

Álvaro José Agámez Licha

Software Developer at DevSavant

https://github.com/aagamezl

@aagamezl

¿Qué es npm?

npm es el registro de software más grande del mundo. Los desarrolladores de código abierto de todos los continentes usan npm para compartir y tomar prestados paquetes, y muchas organizaciones también usan npm para administrar el desarrollo privado.

npm consta de tres componentes distintos:

 

  • El sitio web
  • La interfaz de línea de comandos (CLI)
  • El registro

Herramientas que usaremos

  • GitHub: Es un proveedor de alojamiento de Internet para el desarrollo de software y el control de versiones mediante Git.
  • TypeScript: Es un lenguaje de programación fuertemente tipado que se basa en JavaScript y te brinda mejores herramientas a cualquier escala.
  • TS Standard: Guía de estilo para TypeScript, con linter y corrector de código automático basado en StandardJS.
  • AVA: Un test runner para Node.js con una API concisa, salida de error detallada, adopción de nuevas funciones de JavaScript y aislamiento de procesos que te permite desarrollar con confianza.
  • Istanbul: Es una herramienta de cobertura de pruebas que realiza un seguimiento de qué partes de tú código son ejecutadas por tus pruebas unitarias.
  • Conventional Commits: Una especificación para agregar significado legible por humanos y máquinas para a los mensajes de los commits.
  • husky: Hooks nativos de Git nativos modernos y simplificados, husky mejora tus commits y más.
  • Standard Version: Una utilidad para el control de versiones utilizando semver y la generación CHANGELOG, basándose en Conventional Commits.
  • Verdaccio: Un registro npm privado y ligero.
  • EditorConfig: Ayuda a mantener estilos de codificación coherentes para varios desarrolladores que trabajen en el mismo proyecto en varios editores e IDEs.

Ok, esto puede parecer una lista bastante extensa para construir un paquete npm, pero créeme, verás que cuando comencemos a escribir el código todo encajará fácilmente, ya que varias de estas herramientas las usaremos sin darnos cuenta, y otros los usaremos en momentos específicos, como el linter o cuando hacemos nuestros commits o releases.

¿Qué construiremos?

Te enseñaré cómo construyo uno de mis propios paquetes npm, un conjunto de utilidades que me ayudan en mis otros proyectos.

 

El paquete constará de un conjunto de pequeñas funciones escritas en TypeScript, y para las que siempre comenzaremos escribiendo la prueba unitaria correspondiente, siguiendo el proceso de desarrollo TDD.

import * as utils from '@you-org/utils'
const utils = require('@you-org/utils')

import { camelCase } from '@you-org/utils'
const { camelCase } = require('@you-org/utils')

utils.camelCase('someValue')  // someValue
utils.camelCase('some value')  // someValue
utils.camelCase('some  value')  // someValue
utils.camelCase('SOME VALUE')  // someValue

utils.isEqual(1.23, 1.23)  // true
utils.isEqual('1', '1')  // true
utils.isEqual(true, true)  // true
utils.isEqual(undefined, undefined)  // true
utils.isEqual(null, null)  // true
utils.isEqual({}, {})  // true
utils.isEqual({ foo: 'bar' }, { foo: 'bar' })  // true

A final nuestro paquete se podrá usar de la siguiente manera.

Configuración

Esta configuración es para Node.js v16.13.0 y NPM 7.0.0 o superior y usaremos una nueva opción del cli de npm llamada set-script que solo está disponible desde la versión 7.0.0, aunque si no puedes usar esta opción del cli, puede modificar el package.json manualmente.

Repositorio git

Para administrar y publicar el código de nuestro paquete usaremos GitHub, por lo que debemos crear un nuevo repositorio. Es importante agregar un archivo README, un .gitignore eligiendo la plantilla Node y una licencia (yo uso MIT). Una vez creado el repositorio, debemos clonarlo en nuestro entorno de desarrollo.

Crear el paquete

Para crear la estructura base de nuestro paquete useremos npm init; también podemos crear nuestro paquete bajo un scope u organización, yo personalmente siempre uso este enfoque y para hacerlo debemos usar el parámetro --scope.

npm init -y --scope=@my-org

Crear el paquete

Debemos modificar nuestro archivo package.json indicando el punto de entrada de nuestro paquete y los archivos que se incluirán en el mismo

{
  "main": "lib/index.js",
  "types": "lib/types/index.d.ts",
  "files": [
    "lib/**/*"
  ],
},

EditorConfig

Para generar mi archivo de configuración utilizo una extensión de VS Code llamada EditorConfigGenerator, que básicamente se encarga de generar un archivo de configuración con los valores por defecto que suelo utilizar. Puedes cambiar estos valores, pero lo cierto es que los valores predeterminados son los más recomendados.  Además para que VS Code utilice mi archivo de configuración uso la extensión EditorConfig for VS Code.

Dependencias npm

Ahora procederemos a instalar las dependencias de desarrollo que usaremos para nuestro paquete.

$ npm i -D typescript @types/node ts-standard ava nyc standard-version @commitlint/{cli,config-conventional,format} husky

TypeScript

Debemos crear nuestro archivo de configuración para TypeScript, agregar la configuración necesaria y agregar un par de scripts para realizar la compilación.

$ touch tsconfig.json
$ npm set-script clean "rm -rf lib"
$ npm set-script build "npm run clean && tsc"
$ npm set-script build:watch "npm run build -- -w"

TypeScript

{
  "compilerOptions": {
    "allowJs": false,
    "declaration": true,
    "declarationDir": "./lib/types",
    "esModuleInterop": true,
    "lib": [
      "ES2021"
    ],
    "module": "commonjs",
    "moduleResolution": "node",
    "noImplicitAny": true,
    "outDir": "./lib",
    "rootDir": "./src",
    "sourceMap": true,
    "strict": true,
    "target": "ES2021"
  },
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "lib",
    "./node_modules"
  ]
}

Linter

La única configuración que necesitamos definir para ts-standard son los archivos que no queremos que verifique; lamentablemente solo hay una forma de configurar ts-standard y es a través del package.json, así que debemos agregar la configuración de manera manual, además necesitaremos un par de scripts.

$ npm set-script lint "ts-standard"
$ npm set-script lint:fix "ts-standard --fix"

Linter

"ts-standard": {
  "ignore": [
    "lib",
    "test"
  ]
},

El directorio lib es el directorio donde se genera nuestro JavaScript después del proceso de compilación.

Test Runner

Podemos configurar ava de dos formas diferentes, usando el package.json o usando un archivo de configuración, pero para nuestro paquete vamos a usar el archivo de configuración para que nuestro archivo package.json esté lo más limpio posible.

$ touch ava.config.js
$ npm set-script test "ava"
$ npm set-script test:watch "ava --watch"
$ npm set-script coverage "nyc ava"

Test Runner

ava no tiene soporte para TypeScript por defecto, pero esto no es un problema, podemos activarlo nosotros mismos muy fácilmente creando el archivo ava.config.js.

export default {
  files: [
    'test/**/*.spec.ts'
  ],
  extensions: [
    'ts'
  ],
  require: [
    'ts-node/register'
  ]
}

Commitlint

La configuración de Commitlint es muy sencilla, solo debemos crear su archivo de configuración en el directorio raíz de nuestro paquete.

# Configure commitlint to use conventional config
$ echo "module.exports = { extends: ['@commitlint/config-conventional'] }" > commitlint.config.js

Husky Hooks

Para configurar Husky solo debemos ejecutar los siguientes comandos:

npm pkg set scripts.prepare="husky install"
$ npm set-script release "standard-version"
$ npm set-script release:first "npm run release -- --first-release"
$ npm run prepare
$ npx husky add .husky/pre-commit "npm run lint"
$ npx husky add .husky/pre-commit "npm test"
$ npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'
$ git add .husky/pre-commit

# Testing our pre-commit hook
$ git commit -m "Keep calm and commit"
# `npm run lint` and `npm test` will run every time you commit

Verdaccio

Verdaccio es un paquete de npm que necesita ser instalado de manera global:

$ npm install --global verdaccio
$ npm install --global verdaccio
$ npm install --global verdaccio

Para dar de alta nuestro registry privado, solo necesitamos ejecutar y posteriormente podremos acceder a la interfaz web:

$ verdaccio

Verdaccio

A continuación les comparto los comandos básicos para interactuar con Verdaccio:

$ npm install --global verdaccio
$ npm install --global verdaccio
$ npm install -g verdaccio

$ verdaccio

$ npm adduser --registry http://localhost:4873  **_(Login)_**

$ npm publish --registry http://localhost:4873 --access public (Publicar paquete a Verdaccio)

$ npm install package-name --registry http://localhost:4873

$ npm unpublish --registry http://localhost:4873 [<@scope>/]<pkg>@<version> --force

README file

Escribir un README claro y lo más completo posible para nuestros paquetes es imprescindible, ya que proporciona a los nuevos usuarios y potenciales contribuyentes la primera impresión y el como usar nuestro paquete. Para esto uso estas 2 herramientas https://readme.so/ y https://shields.io/

README file

![Node CI](https://github.com/devnetic/utils/workflows/Node%20CI/badge.svg)
![npm (scoped)](https://img.shields.io/npm/v/@devnetic/utils)
![npm bundle size (scoped)](https://img.shields.io/bundlephobia/minzip/@devnetic/utils?color=red)
![npm](https://img.shields.io/npm/dt/@devnetic/utils)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
![GitHub issues](https://img.shields.io/github/issues-raw/devnetic/utils)
![GitHub](https://img.shields.io/github/license/devnetic/utils)

Este es el encabezado que siempre agrego a mis paquetes.

README file

## License

[MIT](https://choosealicense.com/licenses/mit/)

Este es el final de mi archivo README, donde establezco la licencia que usaré en el paquete; escogí la licencia MIT ya que es bastante abierta en los permisos que otorga:

Publicar el paquete

Para publicar nuestro paquete al registro de npm necesitamos ejecutar el siguiente comando:

$ npm publish --access public

Esta acción no se puede deshacer, así que debemos solo publicar paquetes que estén listo para producción.

Publicar el paquete

Una buena práctica antes de publicar el paquete, aún sí usamos Verdaccio para probarlo localmente, es usar el comando npm pack; este paquete genera un archivo tgz que representa nuestro paquete, es bueno para verificar que no hayamos incluido algo que no debíamos o que hayamos olvidado incluir algo.

Tiempo de codificar

Como construir un paquete npm con TypeScript y TDD

By Alvaro Agamez

Como construir un paquete npm con TypeScript y TDD

  • 422