Módulos e CommonJS vs. ES Modules
INFO
Criado por Kleber Galvão.
Bem-vindo à segunda aula do curso Node.js do Básico ao Avançado! Esta aula foca em um conceito fundamental do Node.js: módulos. Vamos explorar as diferenças entre CommonJS e ES Modules, os dois sistemas de módulos disponíveis no Node.js, e como eles são usados para organizar e reutilizar código. Além disso, você aprenderá a criar e importar módulos personalizados por meio de um exemplo prático, aplicando boas práticas e utilizando as versões mais recentes do Node.js (v20.x ou superior, em 2025).
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.
Teoria: Diferenças entre CommonJS e ES Modules
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.
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.
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.
2.2. Sintaxe do CommonJS
Exportação:
javascript// arquivo: math.js function soma(a, b) { return a + b; } module.exports = { soma };Ou, alternativamente:
javascriptexports.soma = function (a, b) { return a + b; };Importação:
javascript// 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
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.
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.
2.5. Como o CommonJS Funciona Internamente
Quando você usa require('./math'), o Node.js segue este processo:
- Resolução do Módulo: O Node.js localiza o arquivo ou pacote com base no caminho fornecido.
- Carregamento: O arquivo é lido e executado.
- 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 }); - Caching: O módulo é armazenado em cache, garantindo que múltiplas chamadas a
require()retornem a mesma instância. - Exportação: O objeto
module.exportsé retornado para o código que chamourequire().
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.
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.
3.2. Sintaxe dos ES Modules
Exportação:
javascript// arquivo: math.mjs export function soma(a, b) { return a + b; }Ou, exportando múltiplos itens:
javascriptexport const soma = (a, b) => a + b; export const subtracao = (a, b) => a - b;Importação:
javascript// arquivo: index.mjs import { soma } from './math.mjs'; console.log(soma(2, 3)); // 5Exportação Padrão (Default):
javascript// arquivo: math.mjs export default function soma(a, b) { return a + b; }javascript// 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
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(...)).
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.
3.5. Como os ES Modules Funcionam Internamente
Quando você usa import, o Node.js segue este processo:
- Resolução do Módulo: O Node.js localiza o arquivo com base no caminho, exigindo a extensão (ex.:
.mjs). - Parsing: O módulo é analisado para identificar todas as dependências (
importeexport). - Carregamento Assíncrono: As dependências são carregadas em paralelo, usando promises.
- Linkagem: Os exports são vinculados aos imports, criando referências ao vivo (live bindings).
- 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); // 14. 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) |
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)); // 54.2. Configuração no Node.js
Para usar ES Modules no Node.js, você deve:
- Usar a extensão
.mjspara arquivos ESM. - Ou adicionar
"type": "module"aopackage.json:json{ "name": "meu-projeto", "type": "module" } - Para CommonJS, o padrão é
"type": "commonjs"(ou ausência do campo).
4.3. Interoperabilidade
Misturar CommonJS e ES Modules pode ser desafiador:
Importar CommonJS em ESM:
javascriptimport { createRequire } from 'module'; const require = createRequire(import.meta.url); const modulo = require('./modulo.cjs');Ou usar importação dinâmica:
javascriptconst modulo = await import('./modulo.cjs');Importar ESM em CommonJS:
javascriptconst { soma } = await import('./math.mjs');
Para projetos grandes, ferramentas como Babel ou esbuild podem converter entre formatos.
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.
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.
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.
Passo 1: Configurar o Projeto
Crie uma nova pasta para o projeto:
bashmkdir modulo-personalizado cd modulo-personalizado npm init -yO
package.jsonserá criado com configurações padrão:json{ "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):json{ "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):bashnpm install --save-dev nodemonAdicione ao
package.json:json"scripts": { "start": "node index.js", "dev": "nodemon index.js" }
Passo 2: Implementação com CommonJS
Criar o Módulo Personalizado: Crie um arquivo
math.js:javascript// 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:javascript// 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:
bashnode index.jsOu, com nodemon:
bashnpm 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
Passo 3: Implementação com ES Modules
Atualizar o package.json: Certifique-se de que o
package.jsoninclui:json"type": "module"Criar o Módulo Personalizado: Crie um arquivo
math.mjs:javascript// 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:javascript// 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:
bashnode index.mjsOu, com nodemon:
bashnpm 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
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):
javascript// counter.js let count = 0; function increment() { count++; console.log('Count no módulo:', count); } module.exports = { count, increment };javascript// 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):
javascript// counter.mjs export let count = 0; export function increment() { count++; console.log('Count no módulo:', count); }javascript// 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,
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:bash
git init git add . git commit -m "Módulo personalizado com CommonJS e ES Modules"
- Adicione os arquivos ao Git:
Depuração:
- Use o VS Code para depurar:
- Adicione breakpoints nos arquivos
index.jsouindex.mjs. - Configure o
launch.jsonpara ESM, se necessário:json{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Launch ESM", "program": "${workspaceFolder}/index.mjs" } ] }
- Adicione breakpoints nos arquivos
- Use o VS Code para depurar:
Conclusão
Nesta aula, você aprendeu:
- CommonJS: Sistema de módulos síncrono, usando
requireemodule.exports, ideal para projetos legados. - ES Modules: Padrão moderno, assíncrono, com
importeexport, 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.
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).
Se tiver dúvidas ou quiser mais exemplos, é só pedir! 🚀