Saltar al contenido
Framework Ecosystems LabsUn contrato, muchos ecosistemas, la misma prueba.

Clase 102 — Presupuesto de JavaScript#

⬅️ Clase 101 · 📚 Parte 7 · 🎓 Clases · 103 ➡️ Parte 7 — Renderizado y full-stack · Nivel 🔴 avanzado · Pista fullstack (Renderizado y full-stack) ✅ Clase construida — 5 implementaciones verificadas contra contrato.json.

🎯 Objetivo#

Las clases 094, 095 y 096 midieron lo que cuesta el JavaScript. Esta pone el número en un archivo y hace fallar la construcción cuando se pasa.

Esa segunda mitad es toda la clase. Un presupuesto que no rompe nada es una buena intención: se comenta en una reunión, se incumple en la siguiente dependencia y no vuelve a mirarlo nadie.

📚 Resultados de aprendizaje#

Al terminar podrás:

🧩 La situación#

La misma pantalla en las cinco: un encabezado y un botón que cuenta. Es lo más pequeño que se puede hacer y aun así necesita JavaScript, así que el número que sale es el suelo de cada framework: lo que cuesta empezar.

Y en cada una, un archivo de dos líneas que declara el límite, y otro que lo hace cumplir.

🧮 El contrato#

# Petición Qué comprueba
1 GET / la pantalla, el botón y el <script que lo revive
2 GET /presupuesto.json está dentro, y se comprueba al construir
3 GET /presupuesto.json la regla muerde: con mil bytes de límite, falla
4 GET /presupuesto.json el límite es sobre lo comprimido, y comprimir ahorra
5 GET /presupuesto.json archivos contados y bytes distintos de cero
6 GET /presupuesto.json dónde deja su JavaScript y qué entra en la cuenta

Pero la parte importante de esta clase no la comprueba el contrato: la comprueba el hecho de que las cinco implementaciones construyan.

  "preparar": [
    ["pnpm", "install", "--silent", "--ignore-scripts"],
    ["pnpm", "exec", "astro", "build"],
    ["node", "presupuesto.mjs"]
  ],

Ese tercer paso sale con estado 1 si el paquete se pasa. Si alguien añade una dependencia gorda a cualquiera de las cinco, esta clase se pone roja.

Y el caso 3 está por lo que la clase quiere enseñar: un límite que nunca dice que no es indistinguible de no tener límite. La misma cuenta se hace con mil bytes para demostrar que la regla sabe decir que no.

<!-- generado: fichas -->

📖 Las palabras que esta clase define#

Si alguna de estas no te dice nada todavía, esta es la clase donde se aprende. Las definiciones viven en el glosario, que reúne las del programa entero.

Palabra Qué significa
Presupuesto de JavaScript Un límite declarado de cuántos bytes de JavaScript puede enviar una página, comprobado automáticamente. Sin límite, el peso solo crece — nadie quita una dependencia por su cuenta.

🧰 Las piezas de esta clase, una por una#

Antes del código: qué es cada framework, qué versión se está usando y qué hace falta para ejecutarlo. Todo lo de esta sección sale de los archivos reales del repositorio —el catálogo, la receta de arranque y el manifiesto de dependencias de cada ecosistema—, así que no puede quedarse desactualizado sin que la validación lo detecte.

Framework Qué es Desde Licencia Quién lo mantiene
Next.js react-metaframework de JavaScript/TypeScript (TypeScript) 2016 MIT Vercel
Nuxt vue-metaframework de JavaScript/TypeScript (TypeScript) 2016 MIT proyecto independiente
SvelteKit svelte-metaframework de JavaScript/TypeScript (TypeScript) 2022 MIT proyecto independiente
Remix react-metaframework de JavaScript/TypeScript (TypeScript) 2021 MIT proyecto independiente
Astro web-metaframework de JavaScript/TypeScript (TypeScript) 2021 MIT proyecto independiente

🔧 Next.js#

Convirtió el renderizado en servidor en la opción por omisión del ecosistema React. Su acoplamiento con una plataforma concreta es la dimensión que el módulo 11 obliga a puntuar.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts
pnpm exec next build
node presupuesto.mjs

Arrancarla suelta, sin el verificador:

PORT=3000 pnpm exec next start -p 3000

Qué hay dentro de su directorio:

Archivo Qué es
app/Contador.jsx componente en JSX
app/layout.js código JavaScript
app/page.js código JavaScript
app/presupuesto.json/route.js código JavaScript
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
next.config.mjs código JavaScript (módulo ES)
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias

🔧 Nuxt#

El equivalente de Next.js sobre Vue, con un motor de servidor propio reutilizable fuera del framework.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts
pnpm exec nuxt build
node presupuesto.mjs

Arrancarla suelta, sin el verificador:

PORT=3000 node .output/server/index.mjs

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
nuxt.config.ts código TypeScript
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pages/index.vue archivo del proyecto
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
presupuesto.json datos en JSON usados por la implementación
presupuesto.mjs código JavaScript (módulo ES)

🔧 SvelteKit#

Enrutado por sistema de archivos y adaptadores de despliegue intercambiables, que es una estrategia de salida incorporada al diseño.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts
pnpm exec vite build
node presupuesto.mjs

Arrancarla suelta, sin el verificador:

PORT=3000 node build/index.js

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
presupuesto.json datos en JSON usados por la implementación
presupuesto.mjs código JavaScript (módulo ES)
src/app.html plantilla o marcado
src/routes/+page.svelte componente de Svelte

🔧 Remix#

Apostó por los estándares de la plataforma web —formularios, respuestas, caché— frente a abstracciones propias. Su fusión con React Router es un ejemplo de convergencia entre proyectos.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts
pnpm exec remix vite:build
node presupuesto.mjs

Arrancarla suelta, sin el verificador:

PORT=3000 pnpm exec remix-serve ./build/server/index.js

Qué hay dentro de su directorio:

Archivo Qué es
app/root.jsx componente en JSX
app/routes/_index.jsx componente en JSX
app/routes/presupuesto[.]json.js código JavaScript
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
presupuesto.json datos en JSON usados por la implementación

🔧 Astro#

Arquitectura de islas: por omisión no envía JavaScript y cada componente interactivo se declara explícitamente. Permite mezclar React, Vue y Svelte en la misma página, lo que lo hace un banco de pruebas ideal para comparar.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts
pnpm exec astro build
node presupuesto.mjs

Arrancarla suelta, sin el verificador:

PORT=3000 node ./dist/server/entry.mjs

Qué hay dentro de su directorio:

Archivo Qué es
astro.config.mjs código JavaScript (módulo ES)
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
presupuesto.json datos en JSON usados por la implementación
presupuesto.mjs código JavaScript (módulo ES)
src/components/Contador.jsx componente en JSX

Si alguna cadena de herramientas no está en tu máquina, node scripts/doctor.mjs dice cuál falta y con qué comando se instala. No hace falta tenerlas todas: el verificador ejecuta lo que encuentra y declara lo que omitió.

<!-- fin generado: fichas -->

🌐 Las implementaciones — el código a la vista#

El guardián, idéntico en las cinco#

astro/presupuesto.mjs — y la tesis entera en el primer párrafo:

 * Un presupuesto de JavaScript no es un consejo: es un número y una regla que
 * hace fallar la construcción cuando se pasa. Sin esa segunda mitad, el número
 * se comenta en una reunión, se incumple en el siguiente sprint y no vuelve a
 * mirarlo nadie.

Las dos decisiones de medición, que son la mitad del valor de la clase:

 *   1. **Se mide lo comprimido.** Lo que viaja por la red va comprimido, así que
 *      poner el límite sobre el tamaño en disco es medir algo que nadie
 *      descarga. La diferencia es de tres a cuatro veces.
 *   2. **Se mide el directorio de cliente, no el de servidor.** El código que se
 *      queda en el servidor no cuenta: no lo descarga nadie. Dónde está ese
 *      directorio cambia en cada framework, y esa es una de las comparaciones de
 *      la clase.

La regla que muerde, que son cuatro líneas:

  if (!r.dentro_del_presupuesto) {
    console.error(
      `PRESUPUESTO SUPERADO: ${kb(r.bytes_comprimidos)} comprimidos frente a un límite de ${kb(r.presupuesto_bytes)}`,
    );
    process.exit(1);
  }

Y la comprobación de que sabe decir que no:

    // La misma cuenta con un límite absurdo. Está aquí para demostrar que la
    // regla muerde: un presupuesto que nunca dice que no es un adorno.
    con_un_presupuesto_de_mil_bytes_falla: comprimidos > 1000,

Con un detalle que costó cuatro quinientos seguidos:

 * El motivo se descubrió con cuatro quinientos seguidos: cuando este archivo lo
 * importa una ruta de servidor, el empaquetador lo mete DENTRO del paquete, y
 * entonces `import.meta.url` apunta al paquete y no a aquí. El directorio de
 * trabajo, en cambio, es el de la implementación tanto al construir como al
 * servir.

Astro · el suelo más bajo, y se ve por qué#

astro/src/components/Contador.jsx:

/** Lo único interactivo de la pantalla, y por tanto lo único que ocupa
 *  presupuesto. En Astro, quitar la directiva `client:load` de la página deja el
 *  presupuesto en cero. */

Y su declaración — astro/presupuesto.json:

{ "directorio_de_cliente": "dist/client", "maximo_bytes_comprimidos": 14000 }

Next.js · el suelo más alto, y también se ve por qué#

nextjs/app/Contador.jsx:

/** Lo único interactivo. En Next, el presupuesto no empieza aquí: empieza en el
 *  tiempo de ejecución que la aplicación manda siempre —clase 095—, y este botón
 *  solo añade unos cientos de bytes encima. */

Y lo que declara que entra en su cuenta — app/presupuesto.json/route.js:

    que_entra_en_el_presupuesto: "el tiempo de ejecución de React y Next, más lo que lleve \"use client\"",

SvelteKit · lo que ahorra no mandar un motor#

sveltekit/src/routes/presupuesto.json/+server.js:

    que_entra_en_el_presupuesto: "el arranque de SvelteKit y los componentes compilados: no viaja motor de árbol virtual",

Y la pantalla, que es la misma que en los otros cuatro — +page.svelte:

  // Lo único interactivo de la pantalla.
  let veces = $state(0);

Remix · sin excepciones por ruta#

remix/app/routes/_index.jsx:

/** Lo único interactivo. En Remix no hay forma de que este botón no viaje: la
 *  aplicación se hidrata entera —clase 094— y no hay interruptor por ruta. */

Nuxt · y la observación que vale para los cinco#

nuxt/nuxt.config.ts:

 * Sin nada especial. Nuxt trae un analizador de paquetes —`nuxi analyze`— que
 * enseña qué ocupa qué, y no trae un límite que haga fallar la construcción.
 * Ninguno de los cinco lo trae: por eso esta clase lo escribe.

Y la pantalla — nuxt/pages/index.vue:

// Lo único interactivo de la pantalla.
const veces = ref(0);

🔬 Comparación#

El suelo de cada framework, medido con la misma báscula: la misma pantalla, el mismo botón, todo el JavaScript de cliente que produce cada construcción.

archivos en disco comprimido límite declarado dónde lo deja
Astro 5 23,2 kB 10,0 kB 13,7 kB dist/client
SvelteKit 12 77,4 kB 32,2 kB 39,1 kB build/client
Nuxt 4 191,7 kB 72,2 kB 80,1 kB .output/public/_nuxt
Remix 7 255,8 kB 83,9 kB 92,8 kB build/client
Next.js 17 773,2 kB 238,9 kB 253,9 kB .next/static

Cinco lecturas:

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 102

Cada implementación ejecuta su guardián como último paso de la preparación. Para verlo tú, dentro del directorio de cualquiera:

node presupuesto.mjs

Y para comprobar que muerde, baja maximo_bytes_comprimidos a 1000 en su presupuesto.json y vuelve a ejecutarlo: sale con estado 1 y la clase se pone roja.

🧪 Reto de transferencia#

  1. Pesa tu proyecto. Copia presupuesto.mjs, apunta directorio_de_cliente al tuyo y ejecútalo. El número que salga es tu punto de partida.
  2. Pon el límite un diez por ciento por encima. No en el número redondo de más arriba: en el tuyo más un margen. Así la primera dependencia gorda que entre lo va a romper.
  3. Métete el guardián en la integración continua. Un límite que solo se comprueba en tu máquina se incumple en la rama de otra persona.

🔗 Enlaces#

Fuentes#