wandres.dev
VITE · el dev server moderno

Por qué Vite es rápido: el coste que no pagas

La velocidad de Vite no viene de empaquetar mejor, sino de no empaquetar en dev: el navegador pide los módulos, Vite transforma solo lo que se solicita y cachea el resto. El modelo de coste pasa de O(proyecto) a O(lo que cambió).

⏱ 15 min

La pregunta interesante no es por qué Vite es rápido, sino qué coste ha dejado de pagar. La rapidez de un empaquetador clásico se mide por lo bien que hace un trabajo enorme; la de Vite, por cuánto de ese trabajo simplemente no ocurre. El navegador pide los módulos uno a uno, Vite transforma solo el que se le solicita en ese instante y guarda en caché todo lo que no ha cambiado. El resultado es que el modelo de coste deja de escalar con el tamaño del proyecto y pasa a escalar con lo que de verdad se mueve.

🎯 Al terminar esta lección sabrás
  • Explicar por qué delegar la carga de módulos en el navegador elimina trabajo.
  • Entender la transformación por archivo y bajo demanda con esbuild y Oxc.
  • Ver cómo la caché HTTP evita retransformar lo que no cambia.
  • Reconocer el pre-bundling de dependencias como la excepción deliberada.

El navegador hace de empaquetador

En el modelo clásico, la herramienta debe construir y mantener el grafo de módulos entero: resolver cada dependencia, transformarla y coserla en un bundle. Es un trabajo centralizado, total y adelantado.

Vite delega la parte más cara de ese trabajo —recorrer el grafo y decidir qué cargar— en la pieza que mejor lo hace y que ya está ahí: el propio navegador.

Cuando el motor encuentra un import, emite una petición. Vite no anticipa nada; reacciona a peticiones concretas. Esto convierte un problema de construir todo por adelantado en uno de responder lo que se pide.

Y responder lo que se pide es intrínsecamente más barato. Su coste se reparte en el tiempo y se limita a lo que la sesión actual necesita, en lugar de exigirse entero antes de empezar. Nadie paga por las rutas que hoy no visita.

Transformar solo lo que se pide, y una sola vez

Cada petición desencadena una transformación mínima y local: un único archivo. No hay grafo que recomponer ni paquete que rehacer, solo un archivo que traducir.

Esa transformación la hace un motor nativo —esbuild, escrito en Go, y cada vez más Oxc, escrito en Rust— órdenes de magnitud más rápido que las herramientas en JavaScript de la generación anterior. Borrar los tipos de un .ts o convertir el JSX de un .tsx es, a esa velocidad, prácticamente gratis.

// Entrada: un solo archivo, tal como lo escribes
export function Boton({ texto }: { texto: string }) {
  return <button>{texto}</button>;
}
// Salida: tipos borrados y JSX convertido, listo para el motor
export function Boton({ texto }) {
  return jsx("button", { children: texto });
}

Pero lo decisivo no es solo que cada transformación sea rápida, sino que ocurre una sola vez. Vite envía cabeceras de caché agresivas y aprovecha el protocolo HTTP como memoria.

Los módulos de tu código fuente usan validación condicional: si el archivo no cambió, el servidor responde con un 304 Not Modified y el navegador reutiliza su copia sin retransformar nada.

Las dependencias, que casi nunca cambian, se marcan como inmutables y se sirven directas desde la caché del navegador. Esa política se lee en las cabeceras que Vite emite:

# Tu codigo fuente: se revalida en cada peticion
Cache-Control: no-cache
# Una dependencia pre-empaquetada: inmutable, no se vuelve a pedir
Cache-Control: max-age=31536000, immutable

Así, el grueso del código de una página deja de generar trabajo en cuanto se ha visto una vez. Solo lo que tocas vuelve a pasar por el transformador.

flowchart TB
P[peticion de modulo] --> C[cache valida]
subgraph Reutiliza
  C -->|si| H[responde 304]
  H --> R[usa copia local]
end
subgraph Trabaja
  C -->|no| T[transforma el archivo]
  T --> S[sirve y cachea]
end
R --> N[navegador ejecuta]
S --> N
style P fill:#89b4fa,color:#11111b
style H fill:#a6e3a1,color:#11111b
style T fill:#fab387,color:#11111b
style N fill:#cba6f7,color:#11111b
🧠

Delegar el grafo

El navegador recorre el grafo de módulos y decide qué cargar. Vite deja de mantener esa estructura completa en memoria.

✂️

Transformar por archivo

Cada petición transforma un solo módulo con un motor nativo. El coste unitario es diminuto y ocurre bajo demanda.

📦

Cachear el resto

Cabeceras agresivas y respuestas 304 aseguran que nada se retransforme dos veces si no ha cambiado.

El cambio se propaga solo por donde toca

El mismo modelo explica por qué guardar un archivo se siente instantáneo. El HMR —el reemplazo de módulos en caliente que estudiarás en la próxima lección— se apoya en que Vite conoce, gracias al grafo que el navegador ha ido construyendo, qué módulos dependen de cuál.

Cuando editas un componente, Vite invalida solo ese módulo y avisa al navegador para que lo vuelva a pedir. El resto del grafo sigue vivo en memoria, con su estado intacto: no hay recarga de página ni pérdida de contexto.

El contraste con el modelo clásico es tajante:

  • Un empaquetador rehace al menos una parte del bundle ante cada cambio, porque su unidad de trabajo es el paquete.
  • Vite retoca un solo nodo del grafo, porque su unidad de trabajo es el módulo.
  • El coste de una iteración deja de depender del tamaño de la aplicación.
  • La sensación resultante —cambio visible antes de soltar la tecla— es la que redefine lo que esperas de una herramienta.

La excepción deliberada: pre-empaquetar dependencias

Hay un lugar donde no empaquetar sería un desastre, y Vite lo trata como la excepción que confirma la regla. Muchos paquetes de node_modules están fragmentados en cientos de archivos internos: lodash-es, por ejemplo, ronda los seiscientos módulos.

Si el navegador los pidiera uno a uno, se produciría una cascada de peticiones que ahogaría al servidor y a la red. Además, buena parte del ecosistema aún se publica en CommonJS, que el navegador no entiende y que hay que convertir a ESM.

Por eso, al arrancar, Vite ejecuta el pre-bundling de dependencias: escanea lo que importas de node_modules, convierte CommonJS a ESM y agrupa cada dependencia en pocos archivos, que cachea de forma inmutable.

# Sin pre-bundling: una cascada de peticiones por dependencia
lodash-es  ->  ~600 peticiones encadenadas
# Con pre-bundling: una dependencia, un archivo cacheado
lodash-es  ->  1 modulo servido y congelado

Este es el único empaquetado que Vite hace en desarrollo, y es coherente con su filosofía: se aplica solo a código que cambia rara vez, así que el trabajo se amortiza durante toda la sesión.

# El pre-bundling escribe su resultado en la cache del proyecto
node_modules/.vite/deps/     # dependencias convertidas a ESM y agrupadas

Y cuando una dependencia terca no se detecta sola, la fuerzas dentro del pre-bundling con una línea de configuración:

// vite.config.ts: incluir una dependencia en el pre-bundling
export default defineConfig({
  optimizeDeps: { include: ["libreria-cjs-problematica"] },
});
💡
El lenguaje del motor importa, pero no es la causa

Es habitual atribuir la velocidad de Vite a que sus motores están en Go o en Rust, y ese factor es real: esbuild y Oxc transforman a decenas de veces la velocidad de las herramientas antiguas en JavaScript. Pero cuidado con confundir el acelerador con el diseño. Aunque cada transformación individual fuese lenta, Vite seguiría ganando, porque hace muchísimas menos: transforma un archivo cuando se pide y no vuelve a tocarlo si no cambia. El lenguaje nativo acelera el trozo de trabajo; la arquitectura decide cuánto trabajo hay. Las dos cosas suman, pero la segunda es la que cambia el orden de magnitud.

La velocidad es una asimetría, no un truco

La causa profunda de la rapidez de Vite es una asimetría en el modelo de coste. Un empaquetador clásico paga O(proyecto) al arrancar y vuelve a pagar una fracción de ese coste en cada cambio, porque su unidad de trabajo es el grafo entero. Vite paga O(1) al arrancar y O(lo que cambió) en cada iteración, porque su unidad de trabajo es un archivo servido bajo demanda. La lección estratégica trasciende a Vite: en sistemas, la mayor optimización rara vez es hacer el trabajo más rápido; casi siempre es rediseñar para no hacer el trabajo. Vite no ganó la carrera de empaquetar en desarrollo; se salió de la carrera. Y cuando en 2026 Rolldown sustituye a esbuild bajo el capó, no cambia esta tesis: solo hace más veloz el trozo de trabajo que, deliberadamente, ya era el más pequeño posible. Interiorizar esta distinción te convierte en mejor ingeniero mucho más allá del frontend, porque te enseña a mirar cualquier sistema lento y preguntar primero no cómo acelero esto, sino por qué esto ocurre siquiera.

⚔️ Mide dónde va el coste
  1. Abre la pestaña de red, recarga y observa qué peticiones responden 200 (transformadas) y cuáles 304 (reutilizadas de caché): esa es la asimetría en acción.
  2. Guarda un cambio en un solo componente y confirma que únicamente ese módulo se vuelve a solicitar, no toda la aplicación.
  3. Inspecciona node_modules/.vite/deps/: cuenta cuántos archivos hay frente a los cientos de módulos originales de tus dependencias.
  4. Borra esa carpeta y reinicia vite: verás el pre-bundling ejecutarse una vez y, después, la sesión volver a ser instantánea.