Advanced (Atualizado: 02/06/2026)

Tree shaking com Claude Code: reduza bundles com segurança

Melhore tree shaking com Claude Code: ESM, sideEffects, medição, armadilhas e exemplos executáveis.

Tree shaking com Claude Code: reduza bundles com segurança

Tree shaking explicado de forma simples

Tree shaking é a etapa do build de produção que remove exports JavaScript ou TypeScript que não são usados no bundle final. Na prática, é a técnica para não enviar ao navegador código que a página atual não precisa. O resultado pode aparecer em menos download, menos parsing, menos execução e uma primeira navegação mais leve.

O bundler não sabe qual era a intenção do time. Ele decide com base em import, export, no campo sideEffects do package.json, em transformações para CommonJS e em código executado no topo do módulo. Por isso surgem surpresas: uma função não usada continua no bundle, ou sideEffects: false remove CSS e polyfills necessários.

Claude Code ajuda quando a tarefa tem evidência. Peça primeiro a medição do tamanho atual, depois a busca por CommonJS, barrel files, default object exports e arquivos com efeitos colaterais. Só então faça mudanças pequenas e valide com build de produção. Este é o fluxo que Masa usa em projetos Vite, React e Astro quando precisa reduzir bundle sem quebrar telas.

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"]

Comece pela documentação oficial

Cada bundler tem detalhes próprios. Antes de alterar produção, use documentação oficial como referência.

TemaLink oficialO que conferir
webpackTree ShakingsideEffects, ESM e production build
opção webpackoptimization.sideEffectscomo webpack lê o campo sideEffects
Rollup/ViteRollup treeshakeevitar desligamentos globais agressivos
detalhe Rolluptreeshake.moduleSideEffectspreservar módulos de inicialização
esbuildTree shakinganálise ESM e medição com metafile

O ponto central é que tree shaking não remove texto por palpite. Ele segue um grafo ESM estático e mantém código quando removê-lo pode mudar o comportamento em runtime. CommonJS, namespace imports, objetos default grandes e imports de CSS ou polyfills no topo do arquivo tornam a análise mais conservadora.

Prompt recomendado para Claude Code

Comece investigando, não editando. Um sideEffects: false global pode esconder regressões visuais e falhas de inicialização.

Investigue por que o tree shaking está fraco no bundle de produção deste repositório.
Primeiro entregue uma tabela com tamanho atual, chunks principais, dependências pesadas,
dependências CommonJS e barrel exports.
Para cada mudança proposta, inclua risco, impacto esperado e comandos de verificação.
CSS, polyfills, analytics e global setup não devem ser removidos.

Quando for corrigir, limite o escopo.

Nesta rodada, trabalhe apenas em src/utils e src/components/index.ts.
Converta default object exports para named exports e atualize os imports.
Depois rode npm run build e a medição de tamanho do bundle.
Se uma API pública mudar, mantenha um re-export compatível.

Assim Claude Code otimiza sem perder o foco no comportamento que precisa continuar funcionando.

Exemplo mínimo executável

Este projeto compara default object export e named exports usando esbuild.

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

Use 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"
  }
}

A versão menos favorável coloca helpers em um objeto.

// src/bad-utils.ts
const utils = {
  formatBrl(amount: number): string {
    return new Intl.NumberFormat("pt-BR", {
      style: "currency",
      currency: "BRL"
    }).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;

A versão mais fácil de eliminar exporta cada função.

// src/good-utils.ts
export function formatBrl(amount: number): string {
  return new Intl.NumberFormat("pt-BR", {
    style: "currency",
    currency: "BRL"
  }).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);
}

Crie duas entradas.

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

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

console.log(formatBrl(1200));

Adicione o script de medição.

// 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);

Execute:

npm run measure

Em um produto real, registre também nomes de chunks, gzip, Brotli e Total Blocking Time do Lighthouse. Para descobrir qual dependência ficou no grafo, combine esta medição com o guia de análise de bundle.

Caso de uso 1: organizar utilitários

O ganho mais rápido costuma estar em utils/index.ts ou helpers.ts. Quando data, moeda, CSV, Markdown e debug ficam juntos, um único helper pode dificultar a análise.

Peça uma mudança pequena ao Claude Code.

Separe src/utils por finalidade.
Troque os usos para named imports e reexporte no index.ts apenas os helpers públicos.
Se houver Date.now, console, localStorage ou fetch no topo do módulo,
mova essas chamadas para dentro de funções.

Uma estrutura melhor:

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

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

Barrel files não são ruins por natureza. O problema aparece quando executam setup, encadeiam muitos export * from ou puxam módulos sem relação. No código da aplicação, prefira imports diretos; em bibliotecas públicas, mantenha um barrel fino se a compatibilidade exigir.

Caso de uso 2: biblioteca interna de UI

Em uma UI library interna, import { Button } from "@acme/ui" pode avaliar Modal, DatePicker, Chart, conjunto de ícones, CSS e setup de tema. Se todos os componentes compartilham uma entrada grande, named exports não resolvem tudo.

Separe subpath entries.

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

O consumidor importa só o que precisa.

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

Não use sideEffects: false sem auditoria. Esse campo informa se importar um módulo executa trabalho externo que precisa ser mantido. CSS, polyfills, registro de custom elements e setup de tema devem ficar no array sideEffects quando necessários.

Caso de uso 3: carregar dependências de admin sob demanda

Markdown, PDF, gráficos e editores rich text raramente são necessários no primeiro carregamento público. Use tree shaking para remover exports não usados e code splitting para mover funcionalidades de admin para chunks tardios.

// 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 não substitui tree shaking. Ele move código para um chunk posterior; não garante que esse chunk será pequeno. Meça separadamente o que saiu do bundle inicial e o que foi removido dentro do chunk lazy.

Caso de uso 4: publicar um pacote npm

Se você publica uma biblioteca, ofereça entradas ESM que o bundler do consumidor consiga analisar. Expor apenas um main CommonJS reduz as chances de bom tree shaking em aplicações 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"
    }
  }
}

Use sideEffects: false apenas quando o pacote realmente não tem efeitos ao ser importado. Se ele importa CSS, instala polyfills, registra globals ou inicia analytics, liste esses arquivos em sideEffects.

Armadilhas e falhas comuns

ArmadilhaSintomaCorreção
Babel ou TypeScript emite CommonJS cedoexports não usados permanecemmanter ESM até o bundler
sideEffects: false amplo demaisCSS ou polyfills somemlistar arquivos com efeito
default object exporthelpers não usados ficam juntosusar named exports
barrel com setup no topoimportar um componente pesa muitobarrel só com re-export
medir dev buildnúmeros enganososcomparar production, minify e gzip
moduleSideEffects: false globalsetup desaparecevalidar por pacote ou arquivo
namespace importanálise mais conservadorausar named imports específicos

As regressões visuais discretas são perigosas. Um teste que só confirma a existência do DOM pode passar mesmo com CSS faltando. Trate isso como otimização de performance: build, telas críticas e comportamento visível.

Orçamento de bundle no CI

Sem verificação contínua, o tamanho volta a crescer na próxima dependência. Comece com um orçamento gzip simples.

// 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
});

Rode após o build.

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

O primeiro orçamento deve ser realista. Comece perto do gzip atual com alguma margem, e peça explicação quando um PR aumentar o tamanho. Se a aplicação ainda parecer lenta, revise imagens, fontes, latência de API e hidratação com o guia de speed optimization.

Checklist de revisão para Claude Code

Revise este PR de tree shaking.
1. Exports não usados realmente saíram do production bundle?
2. CSS, polyfills e arquivos de registro foram preservados?
3. ESM foi mantido até a análise do bundler?
4. Imports diretos quebraram compatibilidade de API pública?
5. Quais são os resultados de build, testes, telas críticas e bundle budget?
Inclua arquivos e evidências de comando para cada resposta.

Esse checklist transforma refatoração em revisão de qualidade publicável. Nos trabalhos de Masa, uma mudança em sideEffects só termina depois de abrir login, cobrança e admin para conferir estilos e inicialização.

Visão de monetização

Tree shaking não é apenas limpeza técnica. Um primeiro carregamento mais leve reduz atrito antes de leitura de artigos, páginas de produto, cadastro e formulário de consultoria. Em um site técnico como ClaudeCodeLab, uma página de exemplo ou landing page pesada enfraquece anúncios e pedidos de contato.

ClaudeCodeLab pode auditar bundles Vite, Next.js, Astro e UI libraries internas, e transformar o diagnóstico em tree shaking, code splitting e orçamento de CI. Para uma consultoria objetiva, traga package.json, configuração de build, rotas principais e um relatório recente de bundle.

Resumo

Tree shaking funciona quando ESM, sideEffects preciso, efeitos controlados e medição contínua estão alinhados. Claude Code é mais útil com tarefas pequenas e verificáveis: investigar, separar, corrigir imports, medir e revisar falhas.

Executei o exemplo mínimo deste artigo com npm run measure e confirmei que as entradas bad e good geram tamanhos diferentes. Em projetos reais, os números dependem das dependências e da configuração; meça sempre seu production build e documente quais efeitos colaterais precisam permanecer.

#Claude Code #tree shaking #bundle size #ES Modules #frontend optimization
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.