Advanced (Actualizado: 2/6/2026)

Tree Shaking con Claude Code: guía práctica para reducir bundles

Mejora el tree shaking con Claude Code: ESM, sideEffects, medición, errores comunes y ejemplos ejecutables.

Tree Shaking con Claude Code: guía práctica para reducir bundles

Qué significa tree shaking en la práctica

Tree shaking es la optimización que elimina exports de JavaScript o TypeScript que no se usan en el bundle de producción. En términos simples, evita enviar al navegador código que la pantalla actual no necesita. No cambia tu producto por sí solo, pero reduce el coste de descarga, parseo y ejecución.

El bundler no conoce tus intenciones. Decide con la información que puede analizar: import, export, el campo sideEffects de package.json, transformaciones a CommonJS y código que se ejecuta en el nivel superior del módulo. Por eso aparecen fallos como “la función no usada sigue en el bundle” o “al poner sideEffects: false desapareció el CSS”.

Claude Code ayuda cuando le das una tarea medible. Primero debe registrar el tamaño actual, localizar dependencias CommonJS, revisar barrel files, separar exports y comprobar el resultado con un build de producción. Este artículo resume el flujo que Masa usa en proyectos Vite, React y Astro cuando el objetivo es reducir peso sin romper pantallas.

flowchart LR
  A["source files"] --> B["ESM import/export graph"]
  B --> C["bundler tree shaking"]
  C --> D["minified production bundle"]
  B --> E["side effects kept"]
  E --> D
  D --> F["measure bytes and gzip"]

Usa documentación oficial como base

Cada bundler tiene matices. Antes de tocar producción, conviene apoyarse en documentación oficial.

TemaEnlace oficialQué revisar
webpackTree ShakingsideEffects, ESM y build de producción
opción webpackoptimization.sideEffectscómo lee webpack el campo sideEffects
Rollup/ViteRollup treeshakeevitar cambios globales agresivos
detalle Rolluptreeshake.moduleSideEffectsconservar módulos con inicialización
esbuildTree shakinganálisis ESM y medición con metafile

La idea clave es que tree shaking no borra texto al azar. Sigue un grafo ESM estático y conserva código si eliminarlo podría cambiar el comportamiento en runtime. CommonJS, namespace imports, objetos default llenos de helpers y módulos que importan CSS o polyfills en el nivel superior suelen limitar el resultado.

Prompt recomendado para Claude Code

Empieza con investigación, no con cambios. Una configuración global mal aplicada puede ocultar regresiones visuales o de inicialización.

Investiga por qué el tree shaking es débil en el bundle de producción.
Primero entrega una tabla con tamaño actual, chunks principales, dependencias pesadas,
dependencias CommonJS y barrel exports.
Para cada cambio propuesto, incluye riesgo, impacto esperado y comandos de verificación.
CSS, polyfills, analytics y global setup no deben eliminarse.

Cuando toque modificar, acota el alcance.

En esta pasada trabaja solo en src/utils y src/components/index.ts.
Cambia default object exports a named exports y actualiza los imports.
Después ejecuta npm run build y la medición de tamaño de bundle.
Si se afecta una API pública, conserva un re-export compatible.

Así Claude Code optimiza alrededor de “qué comportamiento se mantiene” y no solo alrededor de “cuánto código se eliminó”.

Ejemplo mínimo ejecutable

Este proyecto pequeño compara un default object export con named exports usando esbuild.

mkdir tree-shaking-lab
cd tree-shaking-lab
npm init -y
npm install --save-dev esbuild
mkdir src scripts

Usa este package.json.

{
  "name": "tree-shaking-lab",
  "version": "1.0.0",
  "type": "module",
  "private": true,
  "sideEffects": false,
  "scripts": {
    "measure": "node scripts/measure-tree-shaking.mjs"
  },
  "devDependencies": {
    "esbuild": "^0.25.0"
  }
}

La versión menos favorable junta helpers en un objeto.

// src/bad-utils.ts
const utils = {
  formatEur(amount: number): string {
    return new Intl.NumberFormat("es-ES", {
      style: "currency",
      currency: "EUR"
    }).format(amount);
  },
  heavyReport(rows: number[]): string {
    const body = rows.map((row) => `row:${row}`).join("\n");
    return `report\n${body}\n${"=".repeat(4000)}`;
  },
  debugOnly(): string {
    return "debug:" + "x".repeat(4000);
  }
};

export default utils;

La versión más fácil de analizar exporta cada función.

// src/good-utils.ts
export function formatEur(amount: number): string {
  return new Intl.NumberFormat("es-ES", {
    style: "currency",
    currency: "EUR"
  }).format(amount);
}

export function heavyReport(rows: number[]): string {
  const body = rows.map((row) => `row:${row}`).join("\n");
  return `report\n${body}\n${"=".repeat(4000)}`;
}

export function debugOnly(): string {
  return "debug:" + "x".repeat(4000);
}

Crea dos entradas.

// src/bad-entry.ts
import utils from "./bad-utils";

console.log(utils.formatEur(1200));
// src/good-entry.ts
import { formatEur } from "./good-utils";

console.log(formatEur(1200));

Añade el script de medición.

// scripts/measure-tree-shaking.mjs
import { gzipSync } from "node:zlib";
import { build } from "esbuild";

async function bundle(entryPoint) {
  const result = await build({
    entryPoints: [entryPoint],
    bundle: true,
    minify: true,
    format: "esm",
    treeShaking: true,
    write: false,
    metafile: true
  });

  const code = result.outputFiles[0].text;
  return {
    entryPoint,
    bytes: Buffer.byteLength(code),
    gzipBytes: gzipSync(code).byteLength,
    inputs: Object.keys(result.metafile.inputs)
  };
}

const rows = await Promise.all([
  bundle("src/bad-entry.ts"),
  bundle("src/good-entry.ts")
]);

console.table(rows);

Ejecuta:

npm run measure

En un producto real añade nombres de chunks, gzip, Brotli y Total Blocking Time de Lighthouse. Cuando necesites saber qué dependencia quedó en el grafo, combina este flujo con la guía de análisis de bundle.

Caso de uso 1: ordenar utilidades

El primer ahorro suele aparecer en utils/index.ts o helpers.ts. Si fechas, moneda, CSV, Markdown y depuración viven juntos, usar un helper puede arrastrar más código del necesario.

Pídele a Claude Code una refactorización pequeña.

Divide src/utils por propósito.
Cambia los usos a named imports y reexporta desde index.ts solo los helpers públicos.
Si hay Date.now, console, localStorage o fetch en el nivel superior,
muévelos dentro de funciones.

Una forma limpia sería:

// src/utils/formatDate.ts
export function formatDate(date: Date, locale = "es-ES"): string {
  return new Intl.DateTimeFormat(locale).format(date);
}
// src/utils/index.ts
export { formatDate } from "./formatDate";
export { formatEur } from "./formatEur";
// src/pages/invoice.ts
import { formatEur } from "../utils/formatEur";

export function invoiceLabel(total: number): string {
  return `Total: ${formatEur(total)}`;
}

Los barrel files no son malos por definición. Se vuelven problemáticos cuando ejecutan inicialización, encadenan muchos export * from o empujan módulos no relacionados al grafo. En código de aplicación, importa directo; en librerías públicas, conserva un barrel delgado si necesitas compatibilidad.

Caso de uso 2: librerías internas de UI

En una librería interna, import { Button } from "@acme/ui" puede evaluar Modal, DatePicker, Chart, iconos, CSS y setup de tema. Si todos los componentes comparten una entrada grande, los named exports no bastan.

Divide entradas con subpaths.

{
  "name": "@acme/ui",
  "type": "module",
  "sideEffects": [
    "**/*.css",
    "./src/setup-theme.ts"
  ],
  "exports": {
    ".": "./dist/index.js",
    "./button": "./dist/button.js",
    "./modal": "./dist/modal.js"
  }
}

El consumidor importa solo lo necesario.

import { Button } from "@acme/ui/button";

No uses sideEffects: false sin revisar. Ese campo indica que importar un módulo no ejecuta trabajo necesario en el exterior. CSS, polyfills, registro de custom elements y setup de tema deben quedar listados si deben ejecutarse.

Caso de uso 3: cargar dependencias pesadas solo en admin

Procesadores Markdown, PDF, gráficos y editores ricos no suelen ser necesarios en la primera página pública. Aplica tree shaking para quitar exports no usados y code splitting para mover esas funciones a chunks tardíos.

// src/features/admin/loadMarkdownPreview.ts
export async function renderMarkdown(markdown: string): Promise<string> {
  const [{ unified }, remarkParse, remarkHtml] = await Promise.all([
    import("unified"),
    import("remark-parse"),
    import("remark-html")
  ]);

  const file = await unified()
    .use(remarkParse.default)
    .use(remarkHtml.default)
    .process(markdown);

  return String(file);
}

Dynamic import no reemplaza al tree shaking. Mueve código a un chunk posterior; no garantiza que ese chunk sea pequeño. Mide por separado lo que salió del bundle inicial y lo que se eliminó dentro del chunk lazy.

Caso de uso 4: publicar un paquete npm

Si publicas una librería, expón entradas ESM que el bundler del consumidor pueda analizar. Solo ofrecer un main CommonJS dificulta el tree shaking en aplicaciones frontend.

{
  "name": "@masa/formatters",
  "type": "module",
  "sideEffects": false,
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    },
    "./currency": {
      "types": "./dist/currency.d.ts",
      "import": "./dist/currency.js"
    }
  }
}

Usa sideEffects: false solo si el paquete no tiene efectos al importarse. Si importa CSS, instala polyfills, registra globals o inicia analytics, lista esos archivos en el array sideEffects.

Fallos y trampas comunes

TrampaSíntomaSolución
Babel o TypeScript emiten CommonJS prontoexports no usados permanecenconserva ESM hasta el bundler
sideEffects: false demasiado ampliodesaparecen CSS o polyfillslista archivos con efectos
default object exporthelpers no usados quedan dentrousa named exports
barrel con setup en nivel superiorimportar un componente pesa muchobarrel solo para re-export
medir en dev buildnúmeros engañososcompara producción, minify y gzip
moduleSideEffects: false globaldesaparece inicializaciónvalida por paquete o archivo
namespace importanálisis más conservadorusa named imports concretos

Los fallos visuales sutiles son los más peligrosos. Un test que solo verifica que existe el DOM puede pasar aunque falte CSS. Trata esta tarea como optimización de rendimiento: build, pantallas clave y comportamiento visible.

Presupuesto de bundle en CI

Si no se mide en CI, el tamaño vuelve a crecer. Este script falla cuando el gzip supera el presupuesto.

// scripts/check-bundle-budget.mjs
import { statSync } from "node:fs";
import { gzipSync } from "node:zlib";
import { readFileSync } from "node:fs";

const file = "dist/assets/index.js";
const maxGzipBytes = 160 * 1024;
const raw = readFileSync(file);
const gzipBytes = gzipSync(raw).byteLength;

if (gzipBytes > maxGzipBytes) {
  console.error(`Bundle budget exceeded: ${gzipBytes} > ${maxGzipBytes}`);
  process.exit(1);
}

console.log({
  file,
  bytes: statSync(file).size,
  gzipBytes
});

Ejecútalo después del build.

npm run build
node scripts/check-bundle-budget.mjs

No pongas el primer presupuesto como una meta irreal. Empieza cerca del gzip actual con algo de margen y exige explicación cuando un PR lo aumente. Si la app sigue lenta, revisa imágenes, fuentes, latencia de API e hidratación con la guía de speed optimization.

Checklist de revisión para Claude Code

Revisa este PR de tree shaking.
1. ¿Los exports no usados desaparecieron del bundle de producción?
2. ¿Se conservaron CSS, polyfills y archivos de registro?
3. ¿ESM se mantuvo hasta la fase que analiza el bundler?
4. ¿Los imports directos rompieron alguna API pública?
5. ¿Cuáles son los resultados de build, tests, pantallas clave y bundle budget?
Incluye archivos y evidencia de comandos en cada punto.

Este checklist convierte una refactorización en una revisión publicable. En trabajos de Masa, un cambio en sideEffects no se da por terminado hasta abrir login, facturación y administración para confirmar estilos e inicialización.

Impacto en monetización

Tree shaking no es solo limpieza técnica. Una carga inicial más ligera reduce fricción antes de leer artículos, abrir páginas de producto, registrarse o enviar un formulario de consultoría. En un sitio técnico como ClaudeCodeLab, una página de ejemplo o landing lenta debilita el camino hacia anuncios y consultas.

ClaudeCodeLab puede auditar bundles de Vite, Next.js, Astro y librerías internas de UI, y convertir hallazgos en tree shaking, code splitting y presupuestos de CI. Para una consulta enfocada, trae package.json, configuración de build, rutas clave y un reporte reciente de bundle.

Resumen

Tree shaking funciona cuando ESM, sideEffects preciso, efectos controlados y medición continua están alineados. Claude Code es útil si le das tareas pequeñas y verificables: investigar, dividir, actualizar imports, medir y revisar fallos.

Ejecuté el ejemplo mínimo de este artículo con npm run measure y confirmé que las entradas bad y good generan tamaños distintos. En proyectos reales, los números dependen de dependencias y configuración; mide siempre tu production build y documenta qué efectos secundarios deben conservarse.

#Claude Code #tree shaking #bundle size #ES Modules #frontend optimization
Gratis

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.