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 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.
| Tema | Link oficial | O que conferir |
|---|---|---|
| webpack | Tree Shaking | sideEffects, ESM e production build |
| opção webpack | optimization.sideEffects | como webpack lê o campo sideEffects |
| Rollup/Vite | Rollup treeshake | evitar desligamentos globais agressivos |
| detalhe Rollup | treeshake.moduleSideEffects | preservar módulos de inicialização |
| esbuild | Tree shaking | aná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
| Armadilha | Sintoma | Correção |
|---|---|---|
| Babel ou TypeScript emite CommonJS cedo | exports não usados permanecem | manter ESM até o bundler |
sideEffects: false amplo demais | CSS ou polyfills somem | listar arquivos com efeito |
| default object export | helpers não usados ficam juntos | usar named exports |
| barrel com setup no topo | importar um componente pesa muito | barrel só com re-export |
| medir dev build | números enganosos | comparar production, minify e gzip |
moduleSideEffects: false global | setup desaparece | validar por pacote ou arquivo |
| namespace import | análise mais conservadora | usar 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.
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.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Permission receipt no Claude Code: escopo, prova e rollback
Padrão de permission receipt para Claude Code: ações permitidas, limites de aprovação, comandos de prova, rollback e CTA de receita.
Agent Harness seguro para Claude Code e Codex: permissoes, verificacao e rollback
Monte uma base segura para agentes com Claude Code e Codex usando politicas, plano, verificacao e recuperacao.
Subagentes no Claude Code: guia prático para delegar trabalho com segurança
Guia prático de subagentes no Claude Code para dividir artigos e código: regras, prompts, riscos e checklist.