esbuild é uma ferramenta de transformação e empacotamento de JavaScript e TypeScript projetada para alta velocidade. Em projetos Node.js, ela pode compilar TypeScript, converter módulos, agrupar dependências, gerar source maps, minificar artefatos e produzir builds para APIs, workers, funções serverless e ferramentas de linha de comando.
A ferramenta oferece duas APIs principais: build, usada para processar arquivos e dependências, e transform, usada para transformar uma string isolada em memória. Para aplicações reais com imports, plugins e bundling, a API de build é normalmente a escolha correta.
Instalação
npm install -D esbuild typescriptAdicione scripts básicos:
{
"scripts": {
"typecheck": "tsc --noEmit",
"build": "node scripts/build.mjs",
"build:watch": "node scripts/build.mjs --watch"
}
}esbuild remove tipos TypeScript, mas não verifica se eles estão corretos. Por isso, mantenha tsc --noEmit no fluxo local e no CI.
Primeiro build para Node.js
Crie scripts/build.mjs:
import * as esbuild from 'esbuild';
await esbuild.build({
entryPoints: ['src/server.ts'],
bundle: true,
platform: 'node',
target: 'node24',
format: 'esm',
outfile: 'dist/server.js',
sourcemap: true,
packages: 'external',
logLevel: 'info',
});platform: 'node' adapta a resolução ao ambiente Node.js e trata módulos internos como node:fs e node:path como externos. target evita transformar recursos já suportados pelo runtime escolhido.
Bundling ou arquivos separados
Com bundle: true, esbuild percorre imports estaticamente analisáveis e inclui o código no artefato. Isso pode reduzir a quantidade de arquivos e simplificar deploys serverless.
Entretanto, bundling não é obrigatório. Para aplicações tradicionais, manter dependências externas e instalar node_modules em produção pode ser mais previsível. Bibliotecas com módulos nativos, imports dinâmicos ou leitura de arquivos por caminho relativo precisam de testes específicos.
Dependências externas
Uma aplicação Node.js geralmente não precisa incorporar todas as dependências. A opção:
packages: 'external'mantém pacotes npm como imports de runtime. Também é possível selecionar pacotes:
external: [
'pg',
'sharp',
'@aws-sdk/*',
]Isso é especialmente importante para módulos nativos e pacotes que carregam arquivos auxiliares em tempo de execução.
Formato ESM
Para saída ESM:
{
platform: 'node',
format: 'esm',
target: 'node24',
}No package.json:
{
"type": "module",
"main": "./dist/server.js"
}Teste o artefato com Node puro:
node --enable-source-maps dist/server.jsFormato CommonJS
Para sistemas legados:
{
platform: 'node',
format: 'cjs',
outfile: 'dist/server.cjs',
}Quando platform é node e o bundle está habilitado, o formato padrão tende a CommonJS. Prefira declarar format explicitamente para evitar alterações inesperadas quando o projeto muda.
Vários entry points
APIs, workers e scripts podem ser construídos juntos:
await esbuild.build({
entryPoints: {
server: 'src/server.ts',
worker: 'src/worker.ts',
migrate: 'scripts/migrate.ts',
},
outdir: 'dist',
bundle: true,
platform: 'node',
format: 'esm',
target: 'node24',
packages: 'external',
});Cada entrada gera um arquivo separado. Dependendo do formato e da configuração, chunks compartilhados podem ser usados.
TypeScript e tsconfig
esbuild descobre tsconfig.json e usa opções relevantes à transformação, como JSX e comportamento de campos de classe. Porém, várias opções do compilador relacionadas a tipos e emissão de declarações não são aplicáveis.
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noEmit": true,
"types": ["node"]
},
"include": ["src", "scripts", "test"]
}Para usar um arquivo específico:
tsconfig: 'tsconfig.build.json'Source maps
Ative:
sourcemap: trueEm produção, execute Node com --enable-source-maps. Defina uma política para armazenar os mapas, pois eles podem revelar caminhos e trechos do código. Serviços de observabilidade geralmente permitem upload separado.
Minificação em aplicações Node.js
esbuild oferece:
minify: truePara servidores, minificar raramente traz benefício proporcional. O código fica mais difícil de depurar e stack traces dependem ainda mais dos source maps. Use quando tamanho de pacote afeta cold start ou distribuição, medindo o resultado.
Define e variáveis de build
Valores conhecidos no build podem ser substituídos:
define: {
__BUILD_VERSION__: JSON.stringify(process.env.GIT_SHA ?? 'dev'),
}No código:
declare const __BUILD_VERSION__: string;
console.log({ version: __BUILD_VERSION__ });Não injete segredos. Tudo que entra no bundle pode ser inspecionado.
Metafile e análise
Para entender o conteúdo do build:
const result = await esbuild.build({
entryPoints: ['src/server.ts'],
bundle: true,
platform: 'node',
format: 'esm',
outfile: 'dist/server.js',
metafile: true,
});
console.log(await esbuild.analyzeMetafile(result.metafile, {
verbose: true,
}));O relatório mostra arquivos, dependências e tamanhos. Use-o para descobrir pacotes inesperados no bundle.
Watch e rebuild incremental
A API moderna usa contexto:
const ctx = await esbuild.context({
entryPoints: ['src/server.ts'],
bundle: true,
platform: 'node',
format: 'esm',
outfile: 'dist/server.js',
sourcemap: true,
});
await ctx.watch();
process.on('SIGINT', async () => {
await ctx.dispose();
process.exit(0);
});O contexto reutiliza trabalho entre builds. Sempre chame dispose() ao encerrar para liberar recursos.
Rebuild controlado
Uma ferramenta própria pode chamar:
const ctx = await esbuild.context(options);
await ctx.rebuild();
await ctx.rebuild();
await ctx.dispose();Também existe cancel() para interromper um build em andamento. Aguarde o cancelamento antes de iniciar outro.
API transform
Para transformar uma string isolada:
const result = await esbuild.transform(
'const valor: number = 10',
{
loader: 'ts',
target: 'es2022',
sourcemap: true,
},
);
console.log(result.code);transform não resolve imports, não faz bundling e não usa plugins. É indicado para editores, geração de código e serviços que processam pequenos trechos.
API assíncrona e síncrona
A API assíncrona é recomendada: permite plugins, não bloqueia a thread e consegue paralelizar chamadas. As variantes síncronas podem ser úteis em integrações antigas, mas bloqueiam e não suportam plugins assíncronos.
Plugins
Plugins usam callbacks como onResolve e onLoad:
const virtualPlugin = {
name: 'virtual-config',
setup(build) {
build.onResolve({ filter: /^virtual:config$/ }, () => ({
path: 'config',
namespace: 'virtual',
}));
build.onLoad({ filter: /.*/, namespace: 'virtual' }, () => ({
contents: `export default { ambiente: 'producao' }`,
loader: 'js',
}));
},
};Plugins executam código durante o build. Fixe versões e revise a origem.
Imports dinâmicos
esbuild agrupa imports analisáveis. Caminhos construídos inteiramente em runtime podem permanecer no resultado:
const modulo = await import(`pacote/${nome}`);Quando a ferramenta não consegue determinar os alvos, marque o pacote como externo ou altere a arquitetura. Teste recursos que carregam plugins, locales e templates dinamicamente.
Arquivos estáticos
Templates, certificados, migrations e schemas podem não ser incluídos automaticamente. Copie-os explicitamente:
{
"scripts": {
"build": "node scripts/build.mjs && cp -R src/templates dist/templates"
}
}Para portabilidade entre sistemas, prefira um script Node.js de cópia.
Docker multi-stage
FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run typecheck && npm run build
FROM node:24-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
CMD ["node", "--enable-source-maps", "dist/server.js"]Se o bundle inclui todas as dependências JavaScript, a instalação de produção pode ser reduzida, mas módulos externos ainda precisam existir.
Serverless
Para uma função, use uma entrada por handler, platform: node, target alinhado ao runtime e dependências externas quando fornecidas pela plataforma. Meça cold start e tamanho do pacote em vez de assumir que um único bundle sempre é melhor.
esbuild não é sandbox
O processo de build lê arquivos, resolve dependências e executa plugins. Não processe código não confiável no mesmo ambiente sem isolamento. Builds de pull requests externos precisam de permissões e segredos limitados.
Fluxo recomendado
Use a API assíncrona, defina platform, target e format, mantenha typecheck separado, teste o artefato com Node puro e analise o metafile. Combine com tsx no Node.js para desenvolvimento, SWC no Node.js para comparar estratégias, Exports e Imports para contratos de pacote e GitHub Container Registry no deploy.
Consulte a API oficial do esbuild e a documentação oficial de primeiros passos.




