Exportar paquetes de una librería en el package.json

Cómo estructurar el package.json para exportar varios módulos desde una misma librería con soporte ESM.

breakpoints-astro-ssr-vscode

Vamos a ver cómo crear una librería que exporte varios paquetes o módulos para que luego puedan usarse en otro proyecto. Para ello la clave está en definir correctamente cómo se exportan esos componentes desde el package.json. Esto se vuelve especialmente relevante en entornos ESM (ECMAScript Modules), donde la resolución de imports y rutas debe ser explícita y consistente.

Veamos paso a paso cómo estructurar una librería modular y exportarla correctamente usando el campo exports de package.json.

Estructura del proyecto

Supongamos que tenemos una librería con la siguiente estructura:

my-lib/
├── src/
│ ├── index.ts
│ ├── alpha.ts
│ └── beta.ts
├── dist/
│ ├── index.js
│ ├── alpha.js
│ └── beta.js
├── package.json
├── tsconfig.json

Cada archivo representa un módulo independiente: alpha, beta, y una entrada principal en index.ts.

Contenido de los módulos

El archivo principal index.ts sirve como índice de los paquetes que serán exportados.

src/index.ts
export * from './alpha';
export * from './beta';

Luego creamos dos archivos de ejemplo que representan el contenido de los paquetes a exportar.

src/alpha.ts
export function alphaFn() {
return 'alpha';
}
src/beta.ts
export function betaFn() {
return 'beta';
}

Después de compilar a JavaScript usando TypeScript con module: "ESNext" y outDir: "dist", tenemos los archivos .js correspondientes en la carpeta dist.

Configuración del package.json

Para que la librería sea consumida correctamente por otras aplicaciones o paquetes ESM, debemos declarar los puntos de entrada en el campo exports. Esto permite exponer explícitamente qué módulos se pueden importar desde fuera.

{
"name": "my-lib",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.js",
"exports": {
".": {
"import": "./dist/index.js"
},
"./*": {
"import": "./dist/*.js"
}
}
}

Con esta configuración, los consumidores pueden hacer lo siguiente:

import { alphaFn } from 'my-lib/alpha';
import { betaFn } from 'my-lib/beta';

O también:

import { alphaFn, betaFn } from 'my-lib';

Qué tener en cuenta

  • El valor de "type": "module" es necesario para que Node.js trate todos los archivos .js como módulos ESM.
  • Al usar el campo exports, todo lo que no esté declarado explícitamente queda fuera del acceso externo. Esto ayuda a mantener una API pública clara. Como en el ejemplo podemos usar wildcards (./dist/*.js) si tenemos muchas librerías a exportar.
  • Si se usa también un sistema de tipos, conviene declarar el campo typesVersions o exports con rutas a los .d.ts equivalentes.

Por ejemplo:

"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./*": {
"import": "./dist/*.js",
"types": "./dist/*.d.ts"
}
}

Así permitimos a los editores y herramientas de tipado detectar los tipos automáticamente sin necesidad de configuraciones adicionales.

En resumen

Exportar múltiples paquetes desde una librería ESM es una cuestión de estructurar bien el package.json. Declarar explícitamente cada entrada en el campo exports nos da mayor control sobre la API pública y permite que los usuarios accedan a los módulos.

Una vez configurado, el mantenimiento de la librería es más predecible, especialmente cuando se trabaja con múltiples equipos o cuando la librería crece. Nos permite también ofrecer imports individuales, lo cual puede ser útil para evitar importar más código del necesario.