La arquitectura de dos mitades: dev y build
Vite son dos herramientas con un mismo config: un servidor de desarrollo que sirve ESM sin empaquetar y un comando de build que produce un bundle optimizado. Cada mitad elige el compromiso opuesto, y la costura entre sus motores es lo que Rolldown viene a eliminar.
Vite tiene una doble naturaleza que hay que ver con claridad para no confundirse nunca. No es un servidor con un exportador pegado al final: son dos máquinas distintas, gobernadas por un mismo vite.config.ts, que optimizan objetivos contrarios. La mitad de desarrollo persigue iterar en milisegundos y por eso no empaqueta. La mitad de producción persigue cargar rápido en la red de un usuario real y por eso empaqueta con esmero. Entender por qué difieren —y dónde esa diferencia ha causado dolor histórico— es entender la ingeniería de Vite.
- Distinguir las dos mitades de Vite y el objetivo que optimiza cada una.
- Explicar por qué el ESM sin bundle no sirve en producción.
- Trazar los pipelines de desarrollo y de build por separado.
- Identificar la costura entre motores que Rolldown viene a unificar.
Dos naturalezas, un solo config
Cuando ejecutas vite, arrancas la mitad de desarrollo: un servidor que sirve tu código fuente como ESM nativo, sin empaquetar, con HMR quirúrgico.
Cuando ejecutas vite build, invocas la otra mitad: un compilador que recorre el grafo desde index.html y produce un bundle optimizado en dist/.
Comparten configuración, plugins y resolución de módulos, pero son procesos con motores y prioridades diferentes. La razón de que existan dos es que ningún compromiso sirve para ambos mundos.
Si empaquetaras en desarrollo, perderías el arranque instantáneo y el HMR fino. Si sirvieras ESM sin empaquetar en producción, condenarías al usuario a una experiencia lenta. Cada mitad, por tanto, toma la decisión opuesta a conciencia.
Que ambas compartan vite.config.ts es lo que garantiza que resuelvan los módulos igual y apliquen los mismos plugins. El archivo tiene secciones dedicadas a cada mitad:
import { defineConfig } from "vite";
export default defineConfig({
server: {
port: 5173, // opciones de la mitad de desarrollo
},
build: {
outDir: "dist", // opciones de la mitad de produccion
sourcemap: true, // mapas para depurar el bundle minificado
},
});
La clave server solo afecta al dev server; la clave build, solo al empaquetado. Los plugins y los resolve.alias, en cambio, se aplican a las dos mitades, y esa parte compartida es la que asegura coherencia entre lo que ves al iterar y lo que despliegas.
Por qué el ESM sin bundle no vale para producción
En tu máquina, servir doscientos módulos sueltos por localhost es gratis: la latencia es nula y todo está en caché. En la red real de un usuario, cada módulo es un viaje de ida y vuelta.
Los import anidados crean una cascada: el navegador no puede pedir un módulo hasta que ha recibido y leído el que lo importa. Doscientas peticiones encadenadas son un desastre de rendimiento, sobre todo en redes móviles con latencia alta.
Por eso la mitad de producción no se limita a servir: transforma el grafo con una batería de optimizaciones que solo tienen sentido al entregar.
- Tree shaking: elimina el código que nadie usa, imposible sin ver el grafo entero.
- Code splitting: parte el bundle en trozos que se cargan bajo demanda.
- Minificación: reduce los bytes acortando nombres y quitando espacios.
- Hashing: pone un sello de contenido en cada nombre para cachear con seguridad.
El ESM sin bundle es la herramienta correcta para iterar y la incorrecta para entregar. Ese es, en una frase, el porqué de las dos mitades.
flowchart TB cfg[vite.config.ts] --> d1[ESM sin bundle] cfg --> b1[lee index.html] subgraph Desarrollo d1 --> d2[transforma por peticion] d2 --> d3[HMR quirurgico] end subgraph Produccion b1 --> b2[recorre el grafo] b2 --> b3[tree shaking] b3 --> b4[minifica y hashea] b4 --> b5[dist] end style cfg fill:#f9e2af,color:#11111b style d1 fill:#89b4fa,color:#11111b style b1 fill:#a6e3a1,color:#11111b
La mitad de desarrollo por dentro
El pipeline de desarrollo es reactivo. Ante cada petición, Vite resuelve el especificador a un archivo, lo transforma —tipos fuera, JSX a funciones, import reescritos— y lo sirve.
Solo hay una pieza que empaqueta: el pre-bundling de dependencias, que agrupa lo que sacas de node_modules para evitar la cascada de peticiones. Ese pre-bundling lo ha hecho históricamente esbuild.
Todo lo demás —tu código fuente— viaja sin empaquetar, módulo a módulo, para que el HMR pueda tocar un solo nodo del grafo cuando guardas.
El ciclo que Vite repite ante cada petición es siempre el mismo:
- Resuelve el especificador del
importa un archivo real en disco. - Transforma ese archivo con el motor nativo, sin tocar los demás.
- Reescribe sus
importinternos a rutas que el navegador sabe pedir. - Lo entrega con cabeceras de caché y vuelve a quedarse a la espera.
Ese bucle, repetido a la velocidad de un motor nativo, es lo que hace que arrancar y trabajar se sientan sin latencia:
VITE v8.0.0 ready in 128 ms
Local: http://localhost:5173/
Dev: iterar rápido
Sirve el código fuente como ESM sin empaquetar; solo pre-empaqueta dependencias. Prioridad: arranque y HMR instantáneos.
Build: cargar rápido
Recorre el grafo entero y produce un bundle podado, minificado y hasheado. Prioridad: bytes mínimos y peticiones mínimas.
La mitad de producción y la costura entre motores
El pipeline de build es exhaustivo. Toma index.html como raíz, sigue cada import, construye el grafo completo, aplica las optimizaciones y escribe dist/. Ese empaquetado lo ha hecho históricamente Rollup.
El resultado no son módulos sueltos, sino pocos archivos con nombres versionados por contenido:
dist/
index.html
assets/
index-a1b2c3.js # tu codigo, empaquetado y minificado
vendor-d4e5f6.js # las dependencias, en su propio chunk
index-9f8e7d.css # el CSS extraido y hasheado
Y aquí aparece la costura que hay que conocer. Durante años, la mitad de desarrollo usó esbuild (Go) para las dependencias y la mitad de producción usó Rollup (JavaScript). Dos motores, dos lenguajes, dos formas sutilmente distintas de resolver, transformar y ordenar módulos.
La consecuencia práctica era el problema de paridad dev/prod: código que funcionaba perfecto en el servidor podía comportarse distinto tras el build. Y depurar esas diferencias era doloroso precisamente porque nacían de que ambas mitades hablaban idiomas distintos.
vite # mitad dev: esbuild pre-empaqueta deps, ESM para tu codigo
vite build # mitad prod: Rollup empaqueta todo el grafo
vite preview # sirve dist/ para ver el resultado real de produccion
El error más caro de quien empieza con Vite es confiar en que, si algo funciona en vite, funcionará tras vite build. Casi siempre es cierto, y por eso el fallo duele más cuando llega: son dos pipelines distintos con optimizaciones que solo se activan en producción. El tree shaking puede eliminar código que creías vivo; la minificación puede exponer una dependencia mal declarada; el orden de módulos del bundle puede diferir del orden en dev. La disciplina profesional es sencilla: antes de desplegar, ejecuta siempre vite build y vite preview, y prueba sobre ese resultado, no sobre el dev server.
La existencia de dos mitades no es un defecto: es una respuesta honesta a que desarrollo y producción tienen objetivos irreconciliables, y forzar un único modelo para ambos siempre sacrifica algo. El error sería creer que Vite podría simplemente usar el mismo proceso en los dos mundos; no puede, porque no empaquetar es óptimo para iterar y pésimo para entregar. El problema de ingeniería real, el que ha guiado la evolución de Vite hacia 2026, no es reducir las dos mitades a una, sino garantizar que hablen exactamente el mismo idioma. Cuando el motor que pre-empaqueta en dev y el que empaqueta en build son distintos, cualquier discrepancia entre ambos —una resolución diferente, un tratamiento distinto de CommonJS, un orden de módulos que no coincide— se filtra como un fallo que solo aparece en producción, el peor lugar para descubrirlo. La respuesta de Vite 8 es unificar el motor sin unificar las estrategias: Rolldown, un único bundler en Rust, se ocupa tanto del pre-bundling en desarrollo como del empaquetado de producción. Las dos mitades siguen existiendo y siguen eligiendo compromisos opuestos, pero ahora comparten el mismo cerebro. Comprender esto te ahorra la clase de bug más frustrante de la web moderna: el que funciona en tu máquina y se rompe al desplegar.
- Ejecuta
vite, mueve la aplicación y confirma en la pestaña de red que tu código llega como módulos ESM sueltos, sin empaquetar. - Ejecuta
vite builde inspeccionadist/: cuenta cuántos archivos hay frente a los módulos que veías en desarrollo. - Lanza
vite previewy navega el resultado de producción: esta es la única forma fiel de ver lo que el usuario recibirá. - Piensa en un caso donde dev y build podrían divergir —una dependencia CommonJS conflictiva, por ejemplo— y anota por qué un motor unificado como Rolldown elimina esa clase de sorpresa.