O esbuild no Node.js é uma ferramenta de build extremamente rápida para transformar TypeScript e JavaScript, gerar bundles, minificar código e produzir sourcemaps. Ela pode ser usada pela linha de comando ou pela API JavaScript, o que facilita integrar o build a scripts próprios, monorepos e pipelines de CI.
Apesar da velocidade, esbuild não substitui todas as etapas de um projeto. Ele remove tipos, mas não realiza verificação completa de TypeScript; também não executa testes, não publica pacotes e não corrige automaticamente incompatibilidades de runtime. O melhor uso combina esbuild com tsc --noEmit, testes e validação do artefato final.
Neste guia, você aprenderá a instalar esbuild, criar bundles para Node.js, configurar ESM e CommonJS, marcar dependências externas, gerar sourcemaps, usar plugins, watch mode, metafile e boas práticas para aplicações e bibliotecas.
Instalação
npm install --save-dev esbuildUse uma versão local fixada pelo lockfile. Evite depender de instalação global em equipes e pipelines.
Primeiro build
npx esbuild src/server.ts \
--bundle \
--platform=node \
--target=node22 \
--outfile=dist/server.jsO comando lê o entry point, resolve imports analisáveis e produz um único arquivo. A opção platform=node ajusta resolução e mantém módulos nativos como node:fs externos.
Script no package.json
{
"scripts": {
"build": "node scripts/build.mjs",
"typecheck": "tsc --noEmit"
}
}Veja npm Scripts no Node.js para organizar comandos previsíveis.
API JavaScript
import * as esbuild from 'esbuild';
await esbuild.build({
entryPoints: ['src/server.ts'],
bundle: true,
platform: 'node',
target: 'node22',
format: 'esm',
outfile: 'dist/server.js',
sourcemap: true,
logLevel: 'info'
});A API oficial do esbuild documenta opções de build, transform, loaders, plugins, metafile e context. Para scripts mais complexos, a API evita problemas de escaping específicos do shell.
ESM no output
Para projetos modernos:
{
"type": "module"
}await esbuild.build({
entryPoints: ['src/server.ts'],
bundle: true,
platform: 'node',
format: 'esm',
target: 'node22',
outfile: 'dist/server.js'
});O artigo TypeScript ESM no Node.js explica NodeNext, extensões e comportamento do runtime.
CommonJS
await esbuild.build({
entryPoints: ['src/server.ts'],
bundle: true,
platform: 'node',
format: 'cjs',
target: 'node20',
outfile: 'dist/server.cjs'
});Use CommonJS apenas quando consumidores ou ferramentas exigem esse formato. Publicar ESM e CJS simultaneamente aumenta testes e risco de dual package hazard.
Bundling para aplicações
Aplicações podem se beneficiar de um artefato menor e com menos arquivos. Porém, nem toda dependência deve ser incorporada. Bibliotecas com addons nativos, arquivos dinâmicos ou resolução em runtime podem falhar quando empacotadas.
await esbuild.build({
entryPoints: ['src/server.ts'],
bundle: true,
packages: 'external',
platform: 'node',
format: 'esm',
outdir: 'dist'
});packages: external mantém pacotes npm fora do bundle. O deploy precisa instalar as dependências de produção.
Dependências externas seletivas
external: [
'pg-native',
'sharp',
'@aws-sdk/*'
]Use quando apenas alguns pacotes precisam permanecer externos. Teste o artefato em ambiente limpo para confirmar que todos estão disponíveis.
Módulos nativos
Com platform: node, imports como node:fs, node:path e node:crypto são tratados como externos. Isso preserva o uso das APIs do runtime.
TypeScript sem typecheck
esbuild remove anotações de tipos rapidamente, mas não valida todo o programa:
const value: number = 'texto';O build pode transformar esse arquivo. Mantenha:
npm run typecheckO CI deve executar typecheck antes ou em paralelo com o build. Veja CI para Node.js com GitHub Actions.
tsconfig
esbuild descobre tsconfig.json e respeita opções relevantes de transformação. Ele não implementa todas as verificações do compilador. Evite assumir que uma opção de emissão do TypeScript terá efeito idêntico.
tsconfig: 'tsconfig.build.json'Path aliases
Mappings de paths podem ser resolvidos durante o bundle. Isso funciona no artefato empacotado, mas pode divergir de uma publicação sem bundle. Para bibliotecas, prefira imports por nome real de pacote ou imports do package.json.
Sourcemaps
sourcemap: 'external',
sourcesContent: falseSourcemaps ajudam stacks a apontar para TypeScript. Em produção, avalie se o conteúdo-fonte deve ser incluído e proteja os arquivos.
node --enable-source-maps dist/server.jsMinificação
minify: true,
keepNames: trueMinificar aplicações backend geralmente oferece benefício menor do que no navegador. Pode dificultar diagnóstico e alterar nomes usados por frameworks ou decorators. Meça tamanho, startup e qualidade das stacks antes de habilitar.
Define
define: {
__BUILD_VERSION__: JSON.stringify(process.env.APP_VERSION ?? 'dev')
}Use apenas valores não sensíveis. Tudo definido no bundle pode ser inspecionado no artefato.
Removendo logs
drop: ['debugger']Remover console globalmente pode apagar logs operacionais importantes. Prefira níveis de logger configuráveis.
Múltiplos entry points
entryPoints: [
'src/server.ts',
'src/worker.ts',
'src/cli.ts'
],
outdir: 'dist',
outbase: 'src'Cada entry point gera um arquivo correspondente. Esse modelo é útil para API, workers e comandos administrativos.
Code splitting
splitting: true,
format: 'esm',
outdir: 'dist'Code splitting exige ESM e múltiplos arquivos. Em backend, avalie se compartilhar chunks compensa a complexidade de deploy.
Assets
loader: {
'.sql': 'text',
'.graphql': 'text',
'.wasm': 'file'
}Loaders permitem incorporar texto ou copiar arquivos. Não use para incluir segredos ou configurações específicas de ambiente.
Watch mode com context
const context = await esbuild.context({
entryPoints: ['src/server.ts'],
bundle: true,
platform: 'node',
format: 'esm',
outdir: 'dist'
});
await context.watch();Ao encerrar:
await context.dispose();Para reiniciar processos backend após o build, combine com um watcher externo ou o Watch Mode no Node.js.
Rebuild manual
const context = await esbuild.context(options);
await context.rebuild();
await context.dispose();Rebuild reutiliza trabalho anterior e é útil em ferramentas próprias.
Plugins
const virtualPlugin = {
name: 'virtual-config',
setup(build) {
build.onResolve({ filter: /^virtual:config$/ }, () => ({
path: 'config',
namespace: 'virtual'
}));
build.onLoad({ filter: /.*/, namespace: 'virtual' }, () => ({
contents: `export default ${JSON.stringify({ mode: 'production' })}`,
loader: 'js'
}));
}
};Plugins executam código durante o build. Avalie manutenção e supply chain antes de adicionar plugins de terceiros.
Metafile
const result = await esbuild.build({
...options,
metafile: true
});
await fs.writeFile(
'dist/meta.json',
JSON.stringify(result.metafile, null, 2)
);O metafile mostra inputs, outputs, tamanhos e dependências. Use para detectar pacotes grandes ou inclusões inesperadas.
Analisando o bundle
const report = await esbuild.analyzeMetafile(result.metafile, {
verbose: true
});
console.log(report);Bibliotecas npm
Para bibliotecas, muitas vezes é melhor preservar módulos em vez de gerar um único bundle. Consumidores e bundlers conseguem aplicar tree shaking e resolver peer dependencies.
Se decidir bundlar:
- marque peer dependencies como externas;
- gere declarations com
tsc; - teste ESM e CJS;
- confirme exports;
- inspecione o tarball.
Veja Publicar Pacote no npm.
Docker
FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run typecheck && npm run build
FROM node:22-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
CMD ["node", "--enable-source-maps", "dist/server.js"]Se todas as dependências estiverem incorporadas e não houver pacotes externos, a imagem final pode não precisar de node_modules. Confirme com testes reais.
CI
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npm run build
- run: node dist/smoke-test.jsArmazene metafile e sourcemaps como artifacts protegidos quando úteis.
Segurança
- Fixe a versão do esbuild.
- Revise plugins.
- Não incorpore segredos com define.
- Não exponha sourcemaps publicamente sem necessidade.
- Teste pacotes nativos.
- Execute o artefato em ambiente limpo.
- Use lockfile e
npm ci.
Erros comuns
- Confundir transpile com typecheck: erros de tipo passam.
- Bundlar addon nativo: runtime não encontra binário.
- Esquecer external: pacote dinâmico quebra.
- Minificar sem testar: stacks e nomes ficam ruins.
- Paths só no editor: publicação diverge.
- Não executar dist: CI valida apenas fontes.
- Plugin não confiável: build acessa credenciais.
- Target incorreto: sintaxe não corresponde ao Node.js usado.
Configuração recomendada
import * as esbuild from 'esbuild';
await esbuild.build({
entryPoints: ['src/server.ts'],
bundle: true,
packages: 'external',
platform: 'node',
target: 'node22',
format: 'esm',
outfile: 'dist/server.js',
sourcemap: 'external',
metafile: true,
logLevel: 'info'
});Conclusão
O esbuild no Node.js oferece builds rápidos e uma API simples para aplicações, CLIs e ferramentas. Com platform: node, target explícito, dependências externas e sourcemaps, ele produz artefatos previsíveis sem exigir uma configuração extensa.
Mantenha typecheck, testes e smoke test do output. Quando bundles, plugins e assets são tratados conscientemente, esbuild acelera o desenvolvimento sem esconder incompatibilidades que só apareceriam em produção.




