17 Módulos e CommonJS vs. ES Modules
O objetivo é que você compreenda as características, vantagens e desvantagens de cada sistema de módulos, além de dominar a criação e importação de módulos personalizados. O material é estruturado em duas partes: uma teoria detalhada sobre CommonJS e ES Modules, seguida de um exemplo prático que demonstra a implementação em ambos os formatos.
17.1 Teoria: Diferenças entre CommonJS e ES Modules
17.1.1 Introdução aos Módulos no Node.js
Módulos são blocos de código reutilizáveis que permitem organizar aplicações Node.js de forma modular. Eles encapsulam funcionalidades específicas, como funções, objetos ou classes, e podem ser importados em outros arquivos para evitar repetição de código e melhorar a manutenibilidade. No Node.js, os módulos são essenciais para estruturar projetos escaláveis, desde pequenos scripts até APIs complexas.
O Node.js suporta dois sistemas de módulos principais: - CommonJS: O sistema original do Node.js, introduzido em 2009. - ES Modules (ESM): O padrão oficial do JavaScript (ECMAScript), introduzido no ES6 (2015) e totalmente suportado no Node.js a partir da versão 12.
Ambos os sistemas têm sintaxes e comportamentos distintos, e entender suas diferenças é crucial para escolher o mais adequado ao seu projeto e escrever código compatível com as práticas modernas.
17.1.2 CommonJS: O Sistema de Módulos Original do Node.js
CommonJS é o sistema de módulos nativo do Node.js desde sua criação. Ele foi projetado para permitir modularidade em ambientes JavaScript fora do navegador, onde o ES6 ainda não existia. CommonJS é amplamente usado em projetos legados e em muitos pacotes npm, embora esteja sendo gradualmente substituído pelos ES Modules em projetos modernos.
17.1.2.1 Características do CommonJS
- Sintaxe: Usa
require()para importar módulos emodule.exportsouexportspara exportá-los. - Carregamento Síncrono: Módulos CommonJS são carregados de forma síncrona, ou seja, o Node.js lê e executa o módulo no momento da importação.
- Escopo do Módulo: Cada arquivo é tratado como um módulo independente, com seu próprio escopo. Variáveis locais não são acessíveis fora do módulo, a menos que sejam explicitamente exportadas.
- Cópia de Valores: Quando um módulo é importado, o Node.js cria uma cópia dos valores exportados. Alterações no módulo original após a importação não afetam a cópia importada (exceto para objetos, que são passados por referência).
- Suporte Nativo: CommonJS é suportado em todas as versões do Node.js, sem necessidade de configuração adicional.
17.1.2.2 Sintaxe do CommonJS
Exportação:
// arquivo: math.js function soma(a, b) { return a + b; } module.exports = { soma };Ou, alternativamente:
exports.soma = function (a, b) { return a + b; };Importação:
// arquivo: index.js const math = require('./math'); console.log(math.soma(2, 3)); // 5Notas:
- O caminho
./mathindica um módulo local no mesmo diretório. O sufixo.jsé opcional. require()pode importar módulos nativos (ex.:fs,http) ou pacotes npm (ex.:lodash).
- O caminho
17.1.2.3 Vantagens do CommonJS
- Simplicidade: A sintaxe é direta e fácil de entender para iniciantes.
- Compatibilidade: Funciona em todas as versões do Node.js e é amplamente usado em pacotes npm legados.
- Carregamento Dinâmico: Permite importar módulos condicionalmente ou dinamicamente (ex.:
require(algumaVariavel)). - Suporte em Ferramentas: Muitas ferramentas de build e bundlers (ex.: Webpack) suportam CommonJS nativamente.
17.1.2.4 Desvantagens do CommonJS
- Carregamento Síncrono: Pode causar atrasos em aplicações grandes, especialmente ao carregar muitos módulos no início.
- Falta de Tree Shaking: Não suporta tree shaking (remoção de código morto), o que pode aumentar o tamanho do bundle em aplicações frontend.
- Incompatibilidade com ES Modules: Não pode ser usado diretamente com ES Modules sem ferramentas de conversão (ex.: Babel).
- Sintaxe Menos Moderna: Comparada aos ES Modules, a sintaxe é considerada menos elegante e menos alinhada com o JavaScript moderno.
17.1.2.5 Como o CommonJS Funciona Internamente
Quando você usa require('./math'), o Node.js segue este processo: 1. Resolução do Módulo: O Node.js localiza o arquivo ou pacote com base no caminho fornecido. 2. Carregamento: O arquivo é lido e executado. 3. Wrapping: O Node.js envolve o código do módulo em uma função para criar um escopo isolado: javascript (function (exports, require, module, __filename, __dirname) { // Código do módulo }); 4. Caching: O módulo é armazenado em cache, garantindo que múltiplas chamadas a require() retornem a mesma instância. 5. Exportação: O objeto module.exports é retornado para o código que chamou require().
17.1.3 ES Modules: O Padrão Moderno do JavaScript
ES Modules (ESM) é o sistema de módulos oficial do ECMAScript, introduzido no ES6 (2015). Ele foi projetado para ser um padrão unificado para JavaScript, funcionando tanto no navegador quanto no servidor (Node.js). Desde a versão 12 do Node.js (2019), os ES Modules são suportados nativamente, e em 2025, eles são o padrão recomendado para novos projetos devido à sua integração com o ecossistema JavaScript moderno.
17.1.3.1 Características dos ES Modules
- Sintaxe: Usa
importpara importar eexportpara exportar. - Carregamento Assíncrono: Módulos ESM são carregados de forma assíncrona, permitindo melhor performance em aplicações modernas.
- Escopo Estrito: Funcionam automaticamente em modo estrito (
"use strict"), garantindo maior segurança. - Suporte a Tree Shaking: Permite que ferramentas de build (ex.: Rollup, Vite) removam código não utilizado, otimizando o tamanho do bundle.
- Interoperabilidade: Compatível com navegadores e ferramentas modernas, facilitando o desenvolvimento full-stack.
17.1.3.2 Sintaxe dos ES Modules
Exportação:
// arquivo: math.mjs export function soma(a, b) { return a + b; }Ou, exportando múltiplos itens:
export const soma = (a, b) => a + b; export const subtracao = (a, b) => a - b;Importação:
// arquivo: index.mjs import { soma } from './math.mjs'; console.log(soma(2, 3)); // 5Exportação Padrão (Default):
// arquivo: math.mjs export default function soma(a, b) { return a + b; }// arquivo: index.mjs import soma from './math.mjs'; console.log(soma(2, 3)); // 5Notas:
- Arquivos ESM usam a extensão
.mjsou exigem"type": "module"nopackage.json. - O caminho deve incluir a extensão
.mjs(ou.jscom configuração apropriada).
- Arquivos ESM usam a extensão
17.1.3.3 Vantagens dos ES Modules
- Padrão Oficial: Alinhado com o ECMAScript, garantindo compatibilidade com navegadores e Node.js.
- Carregamento Assíncrono: Melhora a performance ao carregar módulos sob demanda.
- Tree Shaking: Reduz o tamanho do código em aplicações frontend.
- Sintaxe Moderna: Mais clara e consistente com o JavaScript moderno (ex.:
importé mais intuitivo querequire). - Suporte a Top-Level Await: Permite usar
awaitdiretamente no nível superior do módulo (ex.:const data = await fetch(...)).
17.1.3.4 Desvantagens dos ES Modules
- Configuração Adicional: Em projetos Node.js, é necessário configurar
"type": "module"ou usar a extensão.mjs. - Compatibilidade com CommonJS: Nem todos os pacotes npm são compatíveis com ESM, exigindo soluções como
import()dinâmico ou ferramentas de conversão. - Curva de Aprendizado: A sintaxe e as regras (ex.: necessidade de extensões nos caminhos) podem confundir iniciantes.
- Suporte Parcial em Versões Antigas: Embora irrelevante em 2025, projetos legados em versões antigas do Node.js (pré-v12) não suportam ESM nativamente.
17.1.3.5 Como os ES Modules Funcionam Internamente
Quando você usa import, o Node.js segue este processo: 1. Resolução do Módulo: O Node.js localiza o arquivo com base no caminho, exigindo a extensão (ex.: .mjs). 2. Parsing: O módulo é analisado para identificar todas as dependências (import e export). 3. Carregamento Assíncrono: As dependências são carregadas em paralelo, usando promises. 4. Linkagem: Os exports são vinculados aos imports, criando referências ao vivo (live bindings). 5. Execução: O código do módulo é executado, e os valores exportados ficam disponíveis.
Live Bindings: Diferentemente do CommonJS, os ES Modules usam vinculação ao vivo. Se o valor exportado mudar no módulo original, a mudança é refletida no módulo que importou:
// arquivo: counter.mjs
export let count = 0;
export function increment() {
count++;
}// arquivo: index.mjs
import { count, increment } from './counter.mjs';
console.log(count); // 0
increment();
console.log(count); // 117.1.4 Diferenças Chave entre CommonJS e ES Modules
| Característica | CommonJS | ES Modules |
|---|---|---|
| Sintaxe | require / module.exports |
import / export |
| Carregamento | Síncrono | Assíncrono |
| Extensão do Arquivo | .js (padrão) |
.mjs ou .js com "type": "module" |
| Escopo | Escopo isolado, mas não estrito | Modo estrito por padrão |
| Tree Shaking | Não suportado | Suportado |
| Live Bindings | Cópia de valores | Vinculação ao vivo |
| Top-Level Await | Não suportado | Suportado |
| Compatibilidade | Ampla (todos os Node.js, pacotes npm) | Nativo desde Node.js 12, mas requer configuração |
| Uso Dinâmico | require(variavel) |
import() (importação dinâmica) |
17.1.4.1 Exemplo de Diferenças Práticas
CommonJS:
// math.js
module.exports = {
soma: (a, b) => a + b
};// index.js
const { soma } = require('./math');
console.log(soma(2, 3)); // 5ES Modules:
// math.mjs
export const soma = (a, b) => a + b;// index.mjs
import { soma } from './math.mjs';
console.log(soma(2, 3)); // 517.1.4.2 Configuração no Node.js
Para usar ES Modules no Node.js, você deve: - Usar a extensão .mjs para arquivos ESM. - Ou adicionar "type": "module" ao package.json: json { "name": "meu-projeto", "type": "module" } - Para CommonJS, o padrão é "type": "commonjs" (ou ausência do campo).
17.1.4.3 Interoperabilidade
Misturar CommonJS e ES Modules pode ser desafiador: - Importar CommonJS em ESM: javascript import { createRequire } from 'module'; const require = createRequire(import.meta.url); const modulo = require('./modulo.cjs'); Ou usar importação dinâmica: javascript const modulo = await import('./modulo.cjs');
Importar ESM em CommonJS:
const { soma } = await import('./math.mjs');
Para projetos grandes, ferramentas como Babel ou esbuild podem converter entre formatos.
17.1.5 Quando Usar CommonJS vs. ES Modules?
- Use CommonJS:
- Em projetos legados ou pacotes npm que não suportam ESM.
- Quando a simplicidade e a compatibilidade são prioridades.
- Em scripts rápidos onde a configuração extra do ESM não é necessária.
- Use ES Modules:
- Em projetos novos ou modernos (recomendado em 2025).
- Quando você precisa de tree shaking ou integração com navegadores.
- Para aproveitar recursos como top-level await e sintaxe moderna.
- Em projetos full-stack onde a consistência entre frontend e backend é importante.
Em 2025, ES Modules é o padrão recomendado para novos projetos Node.js devido à sua integração com o ecossistema JavaScript moderno e suporte a ferramentas como Vite, Rollup e TypeScript.
17.2 Exemplo Prático: Criar e Importar um Módulo Personalizado
Neste exemplo prático, vamos criar um módulo personalizado para realizar operações matemáticas (soma, subtração e multiplicação) e importá-lo em um arquivo principal. Implementaremos o exemplo em CommonJS e ES Modules para demonstrar as diferenças na prática.
17.2.1 Objetivo do Exemplo
- Criar um módulo personalizado chamado
mathque exporta funções matemáticas. - Importar o módulo em um arquivo principal (
index.jsouindex.mjs) e usar suas funções. - Testar ambos os formatos (CommonJS e ES Modules) em um projeto Node.js.
17.2.2 Passo 1: Configurar o Projeto
Crie uma nova pasta para o projeto:
mkdir modulo-personalizado cd modulo-personalizado npm init -yO
package.jsonserá criado com configurações padrão:{ "name": "modulo-personalizado", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }Para ES Modules, modifique o
package.json(opcional, usado na segunda parte):{ "name": "modulo-personalizado", "version": "1.0.0", "type": "module", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }Instale o
nodemonpara reiniciar o servidor automaticamente (opcional):npm install --save-dev nodemonAdicione ao
package.json:"scripts": { "start": "node index.js", "dev": "nodemon index.js" }
17.2.3 Passo 2: Implementação com CommonJS
Criar o Módulo Personalizado: Crie um arquivo
math.js:// math.js function soma(a, b) { return a + b; } function subtracao(a, b) { return a - b; } function multiplicacao(a, b) { return a * b; } module.exports = { soma, subtracao, multiplicacao };Importar e Usar o Módulo: Crie um arquivo
index.js:// index.js const math = require('./math'); console.log('Soma: ', math.soma(5, 3)); // Soma: 8 console.log('Subtração: ', math.subtracao(5, 3)); // Subtração: 2 console.log('Multiplicação: ', math.multiplicacao(5, 3)); // Multiplicação: 15Executar o Projeto:
node index.jsOu, com nodemon:
npm run devSaída esperada:
Soma: 8 Subtração: 2 Multiplicação: 15Explicação:
- O módulo
math.jsexporta um objeto contendo três funções usandomodule.exports. - O
index.jsimporta o módulo comrequiree acessa as funções viamath.soma,math.subtracao, etc. - O carregamento é síncrono, e o Node.js armazena o módulo em cache após a primeira importação.
- O módulo
17.2.4 Passo 3: Implementação com ES Modules
Atualizar o package.json: Certifique-se de que o
package.jsoninclui:"type": "module"Criar o Módulo Personalizado: Crie um arquivo
math.mjs:// math.mjs export function soma(a, b) { return a + b; } export function subtracao(a, b) { return a - b; } export function multiplicacao(a, b) { return a * b; } // Exportação padrão (opcional) export default { soma, subtracao, multiplicacao };Importar e Usar o Módulo: Crie um arquivo
index.mjs:// index.mjs import { soma, subtracao, multiplicacao } from './math.mjs'; // Ou, usando importação padrão: // import math from './math.mjs'; console.log('Soma: ', soma(5, 3)); // Soma: 8 console.log('Subtração: ', subtracao(5, 3)); // Subtração: 2 console.log('Multiplicação: ', multiplicacao(5, 3)); // Multiplicação: 15Executar o Projeto:
node index.mjsOu, com nodemon:
npm run devSaída esperada:
Soma: 8 Subtração: 2 Multiplicação: 15Explicação:
- O módulo
math.mjsusaexportpara exportar funções individualmente e, opcionalmente, um objeto padrão comexport default. - O
index.mjsimporta as funções comimport, especificando o caminho com a extensão.mjs. - O carregamento é assíncrono, e os ES Modules suportam live bindings, permitindo que alterações no módulo original sejam refletidas.
- O módulo
17.2.5 Passo 4: Testando Diferenças com Live Bindings
Para demonstrar os live bindings dos ES Modules, vamos criar um exemplo que mostra a diferença em relação ao CommonJS.
CommonJS (Cópia de Valores):
// counter.js let count = 0; function increment() { count++; console.log('Count no módulo:', count); } module.exports = { count, increment };// index.js const { count, increment } = require('./counter'); console.log('Count inicial:', count); // 0 increment(); console.log('Count após increment:', count); // 0 (não reflete a mudança)ES Modules (Live Bindings):
// counter.mjs export let count = 0; export function increment() { count++; console.log('Count no módulo:', count); }// index.mjs import { count, increment } from './counter.mjs'; console.log('Count inicial:', count); // 0 increment(); console.log('Count após increment:', count); // 1 (reflete a mudança)Executar:
- Para CommonJS:
node index.js - Para ES Modules:
node index.mjs
Saída do CommonJS:
Count inicial: 0 Count no módulo: 1 Count após increment: 0Saída do ES Modules:
Count inicial: 0 Count no módulo: 1 Count após increment: 1- Para CommonJS:
Explicação:
- No CommonJS,
counté uma cópia do valor exportado, então mudanças no módulo original não afetam o valor importado. - Nos ES Modules,
counté uma referência ao vivo, refletindo mudanças no módulo original.
- No CommonJS,
17.2.6 Passo 5: Boas Práticas
- Organize Módulos:
- Crie uma pasta
libouutilspara módulos reutilizáveis. - Nomeie arquivos de forma descritiva (ex.:
mathOperations.js).
- Crie uma pasta
- Use ES Modules em Projetos Novos:
- Configure
"type": "module"nopackage.jsonpara consistência. - Prefira
.jscom ESM em vez de.mjspara evitar extensões específicas.
- Configure
- Evite Misturar Formatos:
- Escolha um sistema (CommonJS ou ESM) para todo o projeto para evitar problemas de interoperabilidade.
- Use
import()dinâmico para casos excepcionais.
- Versionamento:
Adicione os arquivos ao Git:
git init git add . git commit -m "Módulo personalizado com CommonJS e ES Modules"
- Depuração:
- Use o VS Code para depurar:
Adicione breakpoints nos arquivos
index.jsouindex.mjs.Configure o
launch.jsonpara ESM, se necessário:{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Launch ESM", "program": "${workspaceFolder}/index.mjs" } ] }
- Use o VS Code para depurar:
17.3 Conclusão
Neste capítulo, você aprendeu: - CommonJS: Sistema de módulos síncrono, usando require e module.exports, ideal para projetos legados. - ES Modules: Padrão moderno, assíncrono, com import e export, recomendado para projetos novos em 2025. - Diferenças: Carregamento, sintaxe, live bindings, tree shaking e interoperabilidade. - Exemplo Prático: Criou um módulo personalizado (math) e o importou em ambos os formatos, testando diferenças como live bindings.
Este conhecimento é fundamental para organizar projetos Node.js de forma modular e escalável. Nos próximos módulos, você aplicará esses conceitos para construir APIs, manipular arquivos e integrar bancos de dados.
17.3.1 Próximos Passos
- Experimente criar novos módulos personalizados (ex.: um módulo para strings ou cálculos avançados).
- Explore a documentação de módulos no Node.js: nodejs.org/api/modules.html.
- Prepare-se para o próximo módulo, onde abordaremos o Sistema de Arquivos (fs module).