Skip to content

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 e module.exports ou exports para 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:

    javascript
    exports.soma = function (a, b) {
      return a + b;
    };
  • Importação:

    javascript
    // arquivo: index.js
    const math = require('./math');
    console.log(math.soma(2, 3)); // 5
  • Notas:

    • O caminho ./math indica 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).

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:

  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().

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 import para importar e export para 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:

    javascript
    export 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)); // 5
  • Exportaçã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)); // 5
  • Notas:

    • Arquivos ESM usam a extensão .mjs ou exigem "type": "module" no package.json.
    • O caminho deve incluir a extensão .mjs (ou .js com configuração apropriada).

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 que require).
  • Suporte a Top-Level Await: Permite usar await diretamente 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:

  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:

javascript
// arquivo: counter.mjs
export let count = 0;
export function increment() {
  count++;
}
javascript
// arquivo: index.mjs
import { count, increment } from './counter.mjs';
console.log(count); // 0
increment();
console.log(count); // 1

4. Diferenças Chave entre CommonJS e ES Modules

CaracterísticaCommonJSES Modules
Sintaxerequire / module.exportsimport / export
CarregamentoSíncronoAssíncrono
Extensão do Arquivo.js (padrão).mjs ou .js com "type": "module"
EscopoEscopo isolado, mas não estritoModo estrito por padrão
Tree ShakingNão suportadoSuportado
Live BindingsCópia de valoresVinculação ao vivo
Top-Level AwaitNão suportadoSuportado
CompatibilidadeAmpla (todos os Node.js, pacotes npm)Nativo desde Node.js 12, mas requer configuração
Uso Dinâmicorequire(variavel)import() (importação dinâmica)

4.1. Exemplo de Diferenças Práticas

CommonJS:

javascript
// math.js
module.exports = {
  soma: (a, b) => a + b
};
javascript
// index.js
const { soma } = require('./math');
console.log(soma(2, 3)); // 5

ES Modules:

javascript
// math.mjs
export const soma = (a, b) => a + b;
javascript
// index.mjs
import { soma } from './math.mjs';
console.log(soma(2, 3)); // 5

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).

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:

    javascript
    const { 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 math que exporta funções matemáticas.
  • Importar o módulo em um arquivo principal (index.js ou index.mjs) e usar suas funções.
  • Testar ambos os formatos (CommonJS e ES Modules) em um projeto Node.js.

Passo 1: Configurar o Projeto

  1. Crie uma nova pasta para o projeto:

    bash
    mkdir modulo-personalizado
    cd modulo-personalizado
    npm init -y
  2. O package.json será 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"
    }
  3. 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"
    }
  4. Instale o nodemon para reiniciar o servidor automaticamente (opcional):

    bash
    npm install --save-dev nodemon

    Adicione ao package.json:

    json
    "scripts": {
      "start": "node index.js",
      "dev": "nodemon index.js"
    }

Passo 2: Implementação com CommonJS

  1. 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
    };
  2. 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: 15
  3. Executar o Projeto:

    bash
    node index.js

    Ou, com nodemon:

    bash
    npm run dev

    Saída esperada:

    Soma: 8
    Subtração: 2
    Multiplicação: 15
  4. Explicação:

    • O módulo math.js exporta um objeto contendo três funções usando module.exports.
    • O index.js importa o módulo com require e acessa as funções via math.soma, math.subtracao, etc.
    • O carregamento é síncrono, e o Node.js armazena o módulo em cache após a primeira importação.

Passo 3: Implementação com ES Modules

  1. Atualizar o package.json: Certifique-se de que o package.json inclui:

    json
    "type": "module"
  2. 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
    };
  3. 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: 15
  4. Executar o Projeto:

    bash
    node index.mjs

    Ou, com nodemon:

    bash
    npm run dev

    Saída esperada:

    Soma: 8
    Subtração: 2
    Multiplicação: 15
  5. Explicação:

    • O módulo math.mjs usa export para exportar funções individualmente e, opcionalmente, um objeto padrão com export default.
    • O index.mjs importa as funções com import, 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.

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.

  1. 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)
  2. 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)
  3. 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: 0

    Saída do ES Modules:

    Count inicial: 0
    Count no módulo: 1
    Count após increment: 1
  4. 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.

Passo 5: Boas Práticas

  1. Organize Módulos:

    • Crie uma pasta lib ou utils para módulos reutilizáveis.
    • Nomeie arquivos de forma descritiva (ex.: mathOperations.js).
  2. Use ES Modules em Projetos Novos:

    • Configure "type": "module" no package.json para consistência.
    • Prefira .js com ESM em vez de .mjs para evitar extensões específicas.
  3. 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.
  4. Versionamento:

    • Adicione os arquivos ao Git:
      bash
      git init
      git add .
      git commit -m "Módulo personalizado com CommonJS e ES Modules"
  5. Depuração:

    • Use o VS Code para depurar:
      • Adicione breakpoints nos arquivos index.js ou index.mjs.
      • Configure o launch.json para ESM, se necessário:
        json
        {
          "version": "0.2.0",
          "configurations": [
            {
              "type": "node",
              "request": "launch",
              "name": "Launch ESM",
              "program": "${workspaceFolder}/index.mjs"
            }
          ]
        }

Conclusão

Nesta aula, 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.

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! 🚀

Todos os direitos reservados.