tsup para TypeScript: compilar librerías con ESM+CJS+d.ts en una sola herramienta

tsup es un empaquetador para librerías TypeScript construido sobre esbuild. Con una única herramienta genera los formatos ESM y CommonJS, los ficheros de declaración .d.ts y los source maps, sin necesidad de configurar Rollup ni de encadenar llamadas a tsc.

Instalación y uso básico

// Instalación:
// npm install --save-dev tsup

// package.json
{
  "scripts": {
    "build": "tsup",
    "dev": "tsup --watch"
  }
}

// tsup.config.ts
import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm', 'cjs'],
  dts: true,
  sourcemap: true,
  clean: true,
});

El comando tsup sin argumentos busca tsup.config.ts en la raíz del proyecto. Con la config anterior genera:

// dist/
//   index.js          (ESM)
//   index.cjs         (CommonJS)
//   index.d.ts        (declaraciones de tipos)
//   index.js.map      (source maps)
//   index.cjs.map

Entry points múltiples

Las librerías con sub-rutas de importación necesitan múltiples puntos de entrada:

// tsup.config.ts
import { defineConfig } from 'tsup';

export default defineConfig({
  entry: {
    index: 'src/index.ts',
    utils: 'src/utils.ts',
    'react/index': 'src/react/index.ts',
  },
  format: ['esm', 'cjs'],
  dts: true,
  sourcemap: true,
  clean: true,
  splitting: true,   // Code splitting para ESM (código compartido en chunks)
});

El campo exports de package.json

Con múltiples entry points, el package.json necesita el campo exports para que los bundlers y Node.js resuelvan correctamente las sub-rutas:

// package.json
{
  "name": "mi-libreria",
  "version": "1.0.0",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
      "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
    },
    "./utils": {
      "import": { "types": "./dist/utils.d.ts", "default": "./dist/utils.js" },
      "require": { "types": "./dist/utils.d.cts", "default": "./dist/utils.cjs" }
    }
  },
  "files": ["dist"]
}

Minificación y opciones avanzadas

// tsup.config.ts
import { defineConfig } from 'tsup';

export default defineConfig([
  // Compilación de desarrollo (rápida, con source maps):
  {
    entry: ['src/index.ts'],
    format: ['esm', 'cjs'],
    dts: true,
    sourcemap: true,
  },
  // Compilación de producción (minificada):
  {
    entry: ['src/index.ts'],
    format: ['esm', 'cjs'],
    minify: true,
    outExtension: ({ format }) => ({
      js: format === 'esm' ? '.min.js' : '.min.cjs',
    }),
  },
]);

tsup frente a otras herramientas

tsc: genera declaraciones de tipos y transpila, pero no hace bundle ni tree-shaking. Buena opción si solo necesitas CommonJS y el proyecto tiene pocas dependencias externas.

esbuild directamente: muy rápido, pero no genera .d.ts. Hay que combinarlo con tsc --emitDeclarationOnly.

Rollup: más configurable, mejor tree-shaking, pero requiere bastantes plugins para TypeScript (rollup-plugin-typescript2 o @rollup/plugin-typescript más dts-bundle-generator). Tiene sentido para librerías con requisitos muy específicos de bundle.

tsup: la opción con mejor relación configuración/potencia para la mayoría de librerías TypeScript. Cubre el 95% de los casos con configuración mínima.

Imagen: Pexels / Bibek Ghosh

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP