ESM em 2026: O Fim do CommonJS e a Era do JavaScript Moderno
Olá HaWkers, 2026 está sendo oficialmente o ano da adoção completa de ES Modules (ESM) no ecossistema JavaScript. Com o Node.js 20+ permitindo importar ESM de código CommonJS, a última grande barreira caiu.
Vamos entender essa transição e como ela afeta seu código.
O Que Mudou em 2026
Node.js Unificou os Mundos
// A grande mudança: CJS pode importar ESM
// Antes (até 2025): Isso dava erro
// arquivo.cjs
const esModule = require('./modulo-esm.mjs'); // ❌ Não funcionava
// Agora (2026): Funciona!
// arquivo.cjs
const esModule = require('./modulo-esm.mjs'); // ✅ Funciona
// O Node.js 20+ (backportado) permite isso
// Isso remove a principal dor de cabeça de migraçãoPor Que Isso É Grande
// O problema histórico
const esmAdoptionBlocker = {
before: {
problem: 'Bibliotecas ESM não podiam ser usadas em projetos CJS',
symptom: 'ERR_REQUIRE_ESM',
workaround: 'Dynamic import() - feio e assíncrono',
result: 'Muitas libs mantinham dual publish'
},
after: {
solution: 'CJS pode require() ESM diretamente',
impact: 'Libs podem ser ESM-only sem quebrar users',
trend: 'Novas libs são ESM-only por padrão'
},
timeline: {
node22: 'Feature adicionada',
node20: 'Backportado',
node18: 'Backportado (LTS)',
adoption: 'Todos Node.js não-EOL suportam'
}
};
CommonJS vs ESM: As Diferenças
Sintaxe
// CommonJS (o jeito antigo)
// Exportar
module.exports = { foo, bar };
// ou
exports.foo = foo;
// Importar
const { foo, bar } = require('./module');
const fs = require('fs');
// ESM (o jeito moderno)
// Exportar
export { foo, bar };
export default myFunction;
// Importar
import { foo, bar } from './module.js';
import fs from 'node:fs';Diferenças Importantes
// 1. Timing de carregamento
// CommonJS: Síncrono, em runtime
const a = require('./a'); // Executa agora
if (condition) {
const b = require('./b'); // Executa condicionalmente
}
// ESM: Assíncrono, antes de runtime
import a from './a.js'; // Carregado antes do código executar
// import condicional requer dynamic import
const b = condition ? await import('./b.js') : null;
// 2. __dirname e __filename
// CommonJS: Disponível automaticamente
console.log(__dirname); // /path/to/folder
console.log(__filename); // /path/to/folder/file.js
// ESM: Precisa criar
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// 3. JSON imports
// CommonJS
const config = require('./config.json'); // Funciona
// ESM
import config from './config.json' with { type: 'json' }; // Sintaxe nova
// ou
import { readFileSync } from 'node:fs';
const config = JSON.parse(readFileSync('./config.json', 'utf8'));
// 4. Top-level await
// CommonJS: Não permitido
// await fetch(...); // ❌ SyntaxError
// ESM: Funciona!
const data = await fetch('...'); // ✅ Top-level await
export const result = await processData(data);
Por Que Migrar Para ESM
Benefícios Técnicos
// Vantagens de ESM
const esmBenefits = {
// 1. Tree shaking
treeShaking: {
what: 'Bundlers removem código não usado',
requirement: 'Imports estáticos (ESM)',
impact: 'Bundles menores',
example: `
// Você importa só o que usa
import { debounce } from 'lodash-es';
// Bundler inclui só debounce, não lodash inteiro
`
},
// 2. Static analysis
staticAnalysis: {
what: 'Ferramentas entendem imports antes de executar',
benefits: [
'Melhor autocomplete em IDEs',
'Detecção de imports quebrados',
'Refatoração mais segura'
]
},
// 3. Browser-native
browserNative: {
what: 'ESM funciona no browser sem bundler',
use: 'Desenvolvimento local, apps pequenas',
syntax: '<script type="module" src="app.js"></script>'
},
// 4. Top-level await
topLevelAwait: {
what: 'await fora de funções async',
useful: 'Inicialização de módulos',
example: 'export const db = await connectDB();'
},
// 5. Futuro-proof
futureProof: {
what: 'ESM é o padrão oficial',
cjs: 'CommonJS é específico do Node.js',
trend: 'Ecossistema migrando para ESM'
}
};O Ecossistema Está Mudando
// Estado do ecossistema em 2026
const ecosystem2026 = {
// Libs que são ESM-only agora
esmOnly: [
'chalk 5+',
'node-fetch 3+',
'execa 6+',
'got 12+',
'globby 13+',
'p-limit 4+',
'nanoid 4+',
// E muitas outras...
],
// Frameworks que preferem ESM
frameworks: {
vite: 'ESM-first',
nuxt3: 'ESM por padrão',
astro: 'ESM por padrão',
sveltekit: 'ESM por padrão'
},
// O que ainda é CommonJS
stillCJS: {
express: 'Funciona em ambos',
lodash: 'Tem lodash-es para ESM',
moment: 'Use dayjs ou date-fns',
legacy: 'Projetos não mantidos'
}
};
Como Migrar Para ESM
Passo 1: Configure o package.json
// Adicione "type": "module"
// package.json
{
"name": "meu-projeto",
"version": "1.0.0",
"type": "module", // ← Isso faz .js ser tratado como ESM
"main": "index.js",
"exports": {
".": "./index.js",
"./utils": "./utils/index.js"
}
}
// Com "type": "module":
// - arquivos .js são ESM
// - arquivos .cjs são CommonJS (se precisar)
// Sem "type": "module" (ou "type": "commonjs"):
// - arquivos .js são CommonJS
// - arquivos .mjs são ESMPasso 2: Atualize os Imports
// Transformações necessárias
// De:
const path = require('path');
const { readFile } = require('fs/promises');
const myModule = require('./my-module');
// Para:
import path from 'node:path';
import { readFile } from 'node:fs/promises';
import myModule from './my-module.js'; // Note o .js!
// IMPORTANTE: ESM requer extensão de arquivo
import utils from './utils'; // ❌ Não funciona
import utils from './utils.js'; // ✅ Funciona
// Para módulos do Node, use prefixo node:
import fs from 'node:fs'; // ✅ Recomendado
import fs from 'fs'; // Funciona, mas node: é mais claroPasso 3: Atualize os Exports
// Transformações de export
// De:
module.exports = myFunction;
module.exports = { foo, bar };
exports.something = something;
// Para:
export default myFunction;
export { foo, bar };
export const something = something;
// Mixed exports
export default mainFunction;
export { helper1, helper2 };Passo 4: Corrija __dirname/__filename
// Crie um helper ou adicione no topo dos arquivos que precisam
// esm-utils.js
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
export function getDirname(importMetaUrl) {
return dirname(fileURLToPath(importMetaUrl));
}
// uso em qualquer arquivo:
import { getDirname } from './esm-utils.js';
const __dirname = getDirname(import.meta.url);
// Ou inline (mais comum):
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Passo 5: Atualize JSON Imports
// Opção 1: Import assertion (Node 20+)
import config from './config.json' with { type: 'json' };
// Opção 2: Leitura manual (mais compatível)
import { readFileSync } from 'node:fs';
const config = JSON.parse(
readFileSync(new URL('./config.json', import.meta.url), 'utf8')
);
// Opção 3: createRequire (hack útil)
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const config = require('./config.json');Estratégias de Migração
Migração Gradual
// Para projetos grandes, migre gradualmente
const migrationStrategy = {
// Fase 1: Prepare
phase1: {
actions: [
'Identifique dependências ESM-only',
'Verifique versão do Node (18+)',
'Configure ESLint para ESM'
]
},
// Fase 2: Dual mode
phase2: {
how: 'Use .mjs para novos arquivos ESM',
keep: 'Arquivos existentes .js como CJS',
benefit: 'Migração incremental'
},
// Fase 3: Flip the switch
phase3: {
action: 'Adicione "type": "module"',
rename: 'Arquivos CJS restantes para .cjs',
test: 'Rode todos os testes'
},
// Fase 4: Cleanup
phase4: {
actions: [
'Renomeie .mjs para .js',
'Remova workarounds',
'Atualize documentação'
]
}
};Para Bibliotecas
// Se você mantém uma lib npm
const libraryMigration = {
// Opção 1: ESM-only (recomendado em 2026)
esmOnly: {
packageJson: {
"type": "module",
"exports": {
".": "./dist/index.js"
}
},
pros: 'Simples, moderno',
cons: 'Quebra users CJS em Node < 20',
when: 'Nova lib ou major version'
},
// Opção 2: Dual package (CJS + ESM)
dual: {
packageJson: {
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
},
pros: 'Compatibilidade máxima',
cons: 'Build mais complexo, dual package hazard',
when: 'Lib popular com users legados'
}
};
Ferramentas Que Ajudam
Codemods e Linters
// Ferramentas para migração
const migrationTools = {
// ESLint
eslint: {
plugin: 'eslint-plugin-import',
rules: {
'import/extensions': ['error', 'always'],
'import/no-commonjs': 'error'
}
},
// Codemod
codemod: {
tool: 'jscodeshift',
transforms: 'cjs-to-esm transforms disponíveis',
command: 'npx cjs-to-esm ./src/**/*.js'
},
// TypeScript
typescript: {
config: {
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "NodeNext"
}
},
benefit: 'TS gera ESM automaticamente'
},
// Bundlers
bundlers: {
vite: 'ESM nativo',
rollup: 'ESM-first',
esbuild: 'Suporte excelente'
}
};Erros Comuns e Soluções
Troubleshooting
// Erros comuns na migração
const commonErrors = {
// Erro 1: Cannot use import statement outside a module
error1: {
message: 'Cannot use import statement outside a module',
cause: 'Arquivo tratado como CJS',
solutions: [
'Adicione "type": "module" no package.json',
'Use extensão .mjs',
'Verifique se não há package.json pai'
]
},
// Erro 2: ERR_MODULE_NOT_FOUND
error2: {
message: 'ERR_MODULE_NOT_FOUND',
cause: 'Falta extensão no import',
solution: 'import x from "./file.js" (não "./file")'
},
// Erro 3: __dirname is not defined
error3: {
message: '__dirname is not defined',
cause: '__dirname não existe em ESM',
solution: 'Use import.meta.url + fileURLToPath'
},
// Erro 4: require is not defined
error4: {
message: 'require is not defined',
cause: 'Usando require em módulo ESM',
solution: 'Use import ou createRequire'
},
// Erro 5: JSON import não funciona
error5: {
message: 'Unknown file extension ".json"',
cause: 'JSON precisa de assertion',
solution: 'import x from "./x.json" with { type: "json" }'
}
};
Conclusão
2026 marca o ponto de virada para ESM no JavaScript. Com o Node.js removendo a última grande barreira (CJS importando ESM), não há mais desculpa para não migrar.
Ações recomendadas:
- Novos projetos: Use ESM desde o início
- Projetos existentes: Planeje migração gradual
- Bibliotecas: Considere ESM-only para próxima major
- Atualize Node.js: 20+ para melhor experiência
O CommonJS serviu bem por 15 anos, mas seu tempo está chegando ao fim. ESM é o futuro - e o futuro é agora.
Para entender mais sobre o ecossistema JavaScript moderno, leia: VoidZero 2026.

