wandres.dev
ROLLUP · el modelo de plugins

Config de input y output: entradas, chunks y externals

La configuración de Rollup se organiza alrededor de dos polos: `input` define por dónde entra el grafo, `output` cómo sale. Las tres formas de `input` —cadena, array y objeto de entradas nombradas—, la diferencia entre `output.file` y `output.dir`, los patrones de nombres con `[name]` y `[hash]` para el caché a largo plazo, el troceado manual con `manualChunks` y su sucesor `advancedChunks` en Rolldown, y `external` para dejar fuera del bundle lo que el consumidor ya trae.

⏱ 18 min

Toda la configuración de Rollup gira sobre dos polos. input responde por dónde entra el grafo: uno o varios puntos de partida desde los que se teje todo. output responde cómo sale: en qué formato, en cuántos archivos, con qué nombres, y qué queda fuera. Dominar ese par —y las decisiones intermedias de troceado y externalización— es lo que separa un bundle que simplemente funciona de uno que cachea bien, no duplica código y no arrastra dependencias que el consumidor ya tiene.

🎯 Al terminar esta lección sabrás
  • Usar las tres formas de input: cadena, array y objeto de entradas nombradas.
  • Distinguir output.file de output.dir y controlar los nombres con placeholders.
  • Trocear el bundle con manualChunks y conocer su sucesor advancedChunks.
  • Dejar dependencias fuera del bundle con external y saber cuándo hacerlo.

Las tres formas de input

El campo input acepta tres formas, y cada una expresa una intención distinta. La más simple es una cadena: un único punto de entrada del que cuelga todo el grafo. La segunda es un array de rutas, útil cuando tu librería o tu app tiene varias entradas independientes; Rollup deriva el nombre de cada chunk de su archivo. La tercera, y la más expresiva, es un objeto: las claves se convierten en el nombre de cada entrada, lo que te da control explícito sobre cómo se llamará cada chunk de salida.

// rollup.config.js — entradas nombradas por las claves del objeto
export default {
  input: {
    main: 'src/main.js',
    admin: 'src/admin/index.js',
  },
  output: {
    dir: 'dist',
    format: 'es',
    entryFileNames: '[name]-[hash].js',
    chunkFileNames: 'chunks/[name]-[hash].js',
    assetFileNames: 'assets/[name]-[hash][extname]',
  },
  external: ['react', 'react-dom'],
};

La forma de objeto es la que usarás en cuanto tengas más de una entrada, porque la clave —main, admin— alimenta el placeholder [name] de los nombres de archivo. Con la forma de array pierdes ese control fino sobre los nombres; con la de objeto, cada entrada sale con la etiqueta que tú decides.

output.dir, output.file y los nombres

En el lado de salida, la primera bifurcación es entre output.file y output.dir. Usa output.file —una única ruta— solo cuando el build produce un solo chunk: una entrada, sin imports dinámicos, sin troceado. En cuanto hay varias entradas, un import() dinámico o un manualChunks, la salida son varios archivos y debes usar output.dir, el directorio donde Rollup los deposita. Intentar output.file con varios chunks es un error directo.

Los nombres de esos archivos se controlan con tres patrones y un puñado de placeholders. output.entryFileNames nombra los chunks de entrada, output.chunkFileNames los chunks compartidos o cargados dinámicamente, y output.assetFileNames los recursos como CSS o imágenes. Dentro de cada patrón, [name] es la etiqueta de la entrada o del asset, [hash] es un hash del contenido, y [extname] la extensión original.

Los placeholders forman un vocabulario corto que combinas dentro de cada patrón:

  • [name]: la etiqueta de la entrada o del asset, tomada de la clave de input.
  • [hash]: un hash del contenido del chunk, la base del caché a largo plazo.
  • [format]: el formato de salida, útil cuando un mismo build emite varios a la vez.
  • [extname]: la extensión original del asset, con su punto incluido.

El [hash] no es decorativo: es la base del caché a largo plazo. Si el nombre incluye un hash del contenido, cualquier cambio en el archivo produce un nombre nuevo, de modo que puedes servir esos assets con caché inmutable y confiar en que el navegador descargará la versión nueva solo cuando de verdad cambie. Un chunk de vendor que no varía entre despliegues conserva su nombre, y el usuario no vuelve a descargarlo.

ℹ️
output puede ser un array de configuraciones

Igual que input describe varias entradas, output puede ser un array de bloques de salida, y Rollup emite todos a partir del mismo grafo construido una sola vez. Es el patrón de la librería que publica es y cjs a la vez: dos objetos output con distinto format y distinto dir o file, sin pagar el coste de reconstruir el grafo dos veces. Separar la fase de build de la de output, como viste en la lección de plugins, es justo lo que hace posible esta economía.

manualChunks y external

Por defecto, Rollup decide el troceado solo: junta lo que puede y separa lo que comparten varios puntos de entrada. Cuando quieres imponer tu criterio —típicamente, aislar las dependencias de node_modules en un chunk de vendor estable—, usas manualChunks. Tiene dos formas: un objeto que mapea un nombre de chunk a una lista de módulos, o una función que recibe el id de cada módulo y devuelve el nombre del chunk al que debe ir.

output: {
  dir: 'dist',
  manualChunks(id) {
    if (id.includes('node_modules')) return 'vendor';
  },
}

En el ecosistema de 2026, con Rolldown como motor de Vite 8, manualChunks sigue soportado por compatibilidad, pero convive con un sucesor más expresivo: output.advancedChunks, que define grupos con reglas de test, tamaño mínimo y prioridad, en vez de una sola función. La idea es la misma —controlar dónde caen los módulos—, pero con una gramática que expresa políticas de troceado sin escribir imperativamente cada caso.

// Rolldown y Vite 8: agrupacion declarativa por reglas
output: {
  advancedChunks: {
    groups: [{ name: 'vendor', test: 'node_modules' }],
  },
}

El otro control decisivo es external. Marca módulos que Rollup no debe incluir en el bundle: en lugar de empaquetarlos, los deja como import o require en la salida, confiando en que el consumidor los provea. Para una librería es casi obligatorio externalizar sus peer dependencies —no quieres empaquetar React dentro de tu librería y que el consumidor acabe con dos copias—. Para una app, se externaliza lo que va a estar disponible en el entorno de ejecución, como los módulos nativos de Node bajo el prefijo node:.

// external como lista y como funcion
export default {
  external: ['react', 'react-dom'],
  // o, para excluir todos los builtins de Node:
  // external: (id) => id.startsWith('node:'),
};
flowchart TD
I[input objeto con claves] --> G[grafo de modulos]
G --> S[troceado en chunks]
S --> E[entry chunks segun entryFileNames]
S --> C[chunks compartidos segun chunkFileNames]
S --> A[assets segun assetFileNames]
X[modulos external fuera del grafo] --> S
🎯

input como objeto

Las claves nombran las entradas y alimentan el placeholder [name]. Control explícito sobre cada chunk de salida.

🔖

[hash] en el nombre

Un hash del contenido en el nombre habilita el caché a largo plazo: nombre nuevo solo cuando el archivo cambia de verdad.

🧩

manualChunks

Impone el troceado: aísla vendor en un chunk estable. En 2026 convive con advancedChunks, su sucesor por reglas.

🚫

external

Deja fuera del bundle lo que el consumidor ya provee. Imprescindible para no duplicar peer dependencies en librerías.

💡
preserveModules, la salida espejo para librerías

Cuando publicas una librería y quieres que el consumidor haga tree shaking a granularidad de archivo, output.preserveModules cambia el juego: en vez de fundir todo en unos pocos chunks, Rollup emite un archivo de salida por cada módulo de origen, replicando la estructura de carpetas —ajustable con output.preserveModulesRoot—. El consumidor importa solo los archivos que toca, y su bundler descarta el resto sin depender de tu troceado. Es lo contrario de manualChunks: en lugar de agrupar, preservas la separación original.

Configurar el output es diseñar cómo se cachea y se comparte tu código

Es tentador ver este bloque de configuración como fontanería —rutas, nombres, listas— y copiarlo del primer ejemplo que funcione. Pero cada decisión de output es una decisión sobre el rendimiento y la ergonomía de quien consume tu artefacto. Nombrar con [hash] no es cosmética: es la diferencia entre un usuario que redescarga megabytes en cada despliegue y uno que solo baja el chunk que cambió. Separar vendor con manualChunks no es orden: es fijar una frontera de caché estable para que tu código de aplicación pueda cambiar mil veces sin invalidar las dependencias. Externalizar con external no es ahorrar bytes: es evitar que dos copias de la misma librería coexistan y rompan comprobaciones de identidad río abajo. Y elegir entre trocear y preserveModules es elegir quién controla el tree shaking, si tú al publicar o el consumidor al integrar. Cuando internalizas esto, el bloque output deja de ser una plantilla y se vuelve lo que de verdad es: el plano de cómo tu código viaja por la red, se guarda en cachés ajenas y se combina con el de otros. El grafo lo construye el motor; la estrategia de distribución la diseñas aquí, línea a línea, y esa estrategia sobrevive intacta cuando el motor pasa de Rollup a Rolldown, porque advancedChunks no reemplaza el problema, solo te da mejor vocabulario para resolverlo.

⚔️ Modela tu propia salida
  1. Convierte un input de cadena en uno de objeto con dos entradas nombradas y observa cómo cambian los nombres de los chunks.
  2. Añade un import() dinámico y comprueba por qué ya no puedes usar output.file y necesitas output.dir.
  3. Pon [hash] en entryFileNames, cambia una línea de un módulo y verifica que solo ese chunk estrena nombre.
  4. Escribe un manualChunks que aísle node_modules en un chunk vendor y confirma que el resto de tu código no lo invalida al cambiar.
  5. Marca una peer dependency como external y razona qué problema de doble copia evitas en el consumidor.