O Rollup para bibliotecas Node.js é uma opção madura para gerar pacotes ESM e CommonJS, aplicar tree shaking, preservar módulos e controlar entry points públicos. Diferente de um build focado apenas em aplicações, uma biblioteca precisa cuidar de exports, peer dependencies, declarations TypeScript, compatibilidade e tamanho entregue aos consumidores.
Rollup analisa o grafo de módulos, remove código não utilizado quando possível e oferece uma API de plugins extensa. Porém, bundlar tudo nem sempre é desejável: dependências externas, módulos nativos e subpaths públicos precisam permanecer coerentes com o package.json.
Neste guia, você aprenderá a configurar Rollup para bibliotecas Node.js, gerar ESM e CJS, marcar dependências externas, compilar TypeScript, preservar módulos, criar sourcemaps, publicar tipos e testar o tarball.
Instalação
npm install --save-dev rollupPara TypeScript e resolução de pacotes:
npm install --save-dev \
@rollup/plugin-node-resolve \
@rollup/plugin-commonjs \
@rollup/plugin-typescript \
typescript \
tslibPrimeiro arquivo de configuração
Crie rollup.config.mjs:
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
export default {
input: 'src/index.ts',
plugins: [
resolve(),
commonjs(),
typescript({ tsconfig: './tsconfig.build.json' })
],
output: [
{
file: 'dist/index.js',
format: 'es',
sourcemap: true
},
{
file: 'dist/index.cjs',
format: 'cjs',
sourcemap: true,
exports: 'named'
}
]
};A documentação oficial de opções de configuração do Rollup detalha input, external, outputs, plugins, preserveModules e tree shaking.
Script de build
{
"scripts": {
"build": "rollup --config",
"build:watch": "rollup --config --watch",
"typecheck": "tsc --noEmit"
}
}Veja npm Scripts no Node.js.
Package.json da biblioteca
{
"name": "@empresa/domain",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"files": ["dist"]
}O artigo Exports e Imports no package.json explica o contrato público.
Dependências externas
Bibliotecas geralmente não devem incorporar todas as dependências. Peer dependencies precisam permanecer externas:
external: [
'react',
'fastify',
/^node:/
]Uma função pode usar o package.json:
import pkg from './package.json' with { type: 'json' };
const externalPackages = new Set([
...Object.keys(pkg.dependencies ?? {}),
...Object.keys(pkg.peerDependencies ?? {})
]);
export default {
external(id) {
return id.startsWith('node:') ||
[...externalPackages].some(name =>
id === name || id.startsWith(`${name}/`)
);
}
};Decida se dependencies devem ser bundladas ou externas conforme o produto. Para SDKs, externas preservam deduplicação; para CLIs, bundle pode simplificar distribuição.
Peer dependencies
{
"peerDependencies": {
"fastify": "^5.0.0"
}
}Declare apenas versões testadas. Não inclua Fastify no bundle de um plugin, pois o consumidor deve fornecer a instância compatível.
TypeScript e declarations
O plugin transforma TypeScript, mas a geração de tipos precisa ser verificada:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"declaration": true,
"declarationDir": "dist/types",
"sourceMap": true,
"rootDir": "src"
}
}Para controlar melhor, gere declarations em etapa separada:
tsc --emitDeclarationOnly -p tsconfig.types.jsonDepois copie ou agrupe os tipos para corresponder aos exports.
Typecheck separado
Não trate o plugin TypeScript como única validação:
npm run typecheck
npm run buildO CI deve executar ambos. Consulte CI para Node.js com GitHub Actions.
Múltiplos entry points
input: {
index: 'src/index.ts',
errors: 'src/errors.ts',
testing: 'src/testing.ts'
},
output: {
dir: 'dist',
format: 'es',
entryFileNames: '[name].js',
sourcemap: true
}No package.json:
{
"exports": {
".": "./dist/index.js",
"./errors": "./dist/errors.js",
"./testing": "./dist/testing.js"
}
}Preserve modules
output: {
dir: 'dist',
format: 'es',
preserveModules: true,
preserveModulesRoot: 'src'
}Essa opção mantém arquivos separados, facilitando imports por subpath e tree shaking do consumidor. Porém, plugins podem gerar módulos virtuais e nomes inesperados. A documentação recomenda considerar múltiplos entry points quando você quer preservar uma estrutura pública controlada.
Tree shaking
Rollup remove exports não utilizados quando consegue provar que não possuem efeitos colaterais. Código dinâmico, CommonJS e propriedades acessadas podem reduzir a eficiência.
treeshake: {
moduleSideEffects: false
}Não defina moduleSideEffects: false se módulos executam registro, polyfill, CSS ou inicialização ao importar. Uma configuração errada remove comportamento necessário.
Side effects no package.json
{
"sideEffects": false
}Esse campo é consumido por bundlers, não apenas Rollup. Só use quando todos os módulos podem ser removidos sem alterar comportamento.
CommonJS
O plugin CommonJS converte dependências que usam require:
commonjs({
include: /node_modules/
})Evite aplicar transformação a todo o projeto sem necessidade. ESM nativo produz análise melhor.
node-resolve
resolve({
exportConditions: ['node'],
preferBuiltins: true
})preferBuiltins ajuda a usar módulos nativos. Confirme condições de exports para o runtime.
Interop
Ao gerar CommonJS, imports default de dependências externas podem exigir helpers:
output: {
format: 'cjs',
interop: 'auto'
}Teste dependências CommonJS reais. O comportamento de Rollup pode diferir do esModuleInterop do TypeScript.
Sourcemaps
sourcemap: true,
sourcemapExcludeSources: truePublique sourcemaps apenas se fizer sentido. Eles melhoram debug, mas podem expor código-fonte.
Minificação
import terser from '@rollup/plugin-terser';
output: {
file: 'dist/index.min.js',
format: 'es',
plugins: [terser()]
}Bibliotecas normalmente não precisam ser minificadas; o consumidor pode aplicar sua própria estratégia. Minificação também prejudica stacks e auditoria.
Assets e plugins
Plugins conseguem emitir arquivos com this.emitFile. Use para schemas, WASM ou templates necessários. Garanta que todos os assets sejam incluídos no tarball.
Watch mode
npm run build:watchRollup reutiliza cache para acelerar rebuilds. Evite escrever output dentro da pasta observada sem configuração apropriada.
Warnings como erros
onLog(level, log, handler) {
if (level === 'warn') {
handler('error', log);
return;
}
handler(level, log);
}Alguns warnings, como dependências circulares conhecidas, podem ser tratados explicitamente. Não ignore todos indiscriminadamente.
Dependências circulares
Um ciclo não é automaticamente bug, mas pode causar valores parcialmente inicializados e dificultar tree shaking. Use warnings para revisar arquitetura.
Bundle de CLI
Para uma CLI Node.js:
output: {
file: 'dist/cli.js',
format: 'es',
banner: '#!/usr/bin/env node'
}No package.json:
{
"bin": {
"minha-cli": "./dist/cli.js"
}
}Garanta permissão executável no pacote.
Build dual ESM e CJS
Dois outputs são simples quando o código é puro, mas tipos e exports precisam ser coerentes. Teste:
node --input-type=module -e "import('@empresa/domain').then(console.log)"
node -e "console.log(require('@empresa/domain'))"Evite estado global duplicado entre os formatos.
Teste do tarball
npm pack --dry-run
npm packInstale em projetos consumidores ESM, CommonJS e TypeScript. Veja Publicar Pacote no npm.
Comparação com esbuild
Rollup oferece controle detalhado de chunks, plugins e bibliotecas. esbuild prioriza velocidade e simplicidade. Consulte esbuild no Node.js. Escolha com base em formato, plugins, declarations e desempenho real.
CI
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npm run build
- run: npm pack --dry-runArmazene o tarball como artifact e execute testes de instalação antes de publicar.
Segurança
- Fixe versões de plugins.
- Revise código executado no build.
- Não incorpore segredos.
- Restrinja arquivos publicados.
- Teste peer dependencies.
- Proteja sourcemaps.
- Use OIDC na publicação.
Erros comuns
- Bundlar peer dependency: consumidor recebe duas instâncias.
- Tipos fora dos exports: editor falha.
- Side effects falsos: código necessário é removido.
- CJS sem interop testado: import default quebra.
- PreserveModules sem revisar: internals são publicados.
- Minificar biblioteca: diagnóstico piora.
- Tarball não testado: arquivos faltam.
- Plugin não confiável: supply chain aumenta.
Configuração recomendada
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
export default {
input: {
index: 'src/index.ts',
errors: 'src/errors.ts'
},
external: id => id.startsWith('node:') ||
id === 'fastify' || id.startsWith('fastify/'),
plugins: [
resolve({ preferBuiltins: true }),
commonjs(),
typescript({ tsconfig: './tsconfig.build.json' })
],
output: {
dir: 'dist',
format: 'es',
entryFileNames: '[name].js',
sourcemap: true
}
};Conclusão
O Rollup para bibliotecas Node.js oferece controle sobre exports, tree shaking, múltiplos entry points e formatos de saída. O maior valor aparece quando o build preserva o contrato público e mantém dependências corretas fora do bundle.
Gere declarations separadas, teste ESM e CommonJS, inspecione o tarball e execute CI completa. Com external, exports e side effects bem definidos, Rollup produz bibliotecas previsíveis para consumidores Node.js.




