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
Crie um log de orçamento Claude Code antes do custo ficar confuso
Registre quem usou Claude Code, para qual trabalho e qual resultado gerou no time.
Checagem de 3 minutos antes do commit: confira o que o Claude Code mexeu antes de fechar
Roteiro de 3 minutos para flagrar antes do commit as mudanças que o Claude Code ampliou sozinho: escopo, diff e quais arquivos stagear.
Registro de riscos: o que montar antes de levar Claude Code para a equipe
Como montar um registro de riscos para levar Claude Code à equipe sem acidentes de permissão, CI e deploy. Com exemplos e código.