W3docs

Introdução aos Módulos JavaScript

Os módulos JavaScript ajudam a gerenciar projetos grandes dividindo o código em partes reutilizáveis. Aprenda import/export, escopos e boas práticas.

Os módulos JavaScript ajudam a gerenciar projetos de código grandes, permitindo que você divida seu código em partes separadas e reutilizáveis. Este guia explora como usar módulos JavaScript de forma eficaz, aprimorando suas habilidades de programação e a organização do projeto.

Esta página aborda o que é um módulo, a sintaxe import/export, a diferença entre exportações nomeadas e padrão, como funciona o escopo de módulo, os sistemas de módulos mais antigos que você ainda vai encontrar (CommonJS e AMD) e um exemplo completo e funcional que você pode executar.

Entendendo os Módulos JavaScript

Um módulo é simplesmente um arquivo. Cada arquivo tem seu próprio escopo privado: variáveis e funções declaradas dentro de um módulo não são visíveis fora dele, a menos que você as export explicitamente. Para usar algo de outro módulo, você o import. Esse é o fundamento de todas as demais ideias desta página.

Os módulos também trazem dois comportamentos automáticos que você deve conhecer:

  • O modo estrito está sempre ativado. Todo módulo é executado como se começasse com 'use strict', então variáveis globais acidentais lançam um ReferenceError em vez de criar uma variável silenciosamente.
  • O this no nível superior é undefined, não o objeto global. Essa é uma das formas mais fáceis de identificar se o código está sendo executado como um módulo.

A seguir, abordamos a sintaxe básica e o uso de módulos.

Introdução à Sintaxe de Módulos

Os módulos usam as instruções import e export para compartilhar código entre arquivos.

Exemplo:

// Exporting functions
export const add = (a, b) => a + b;
export function multiply(a, b) {
  return a * b;
}

// Importing them in another file
import { add, multiply } from './mathFunctions.js';

A instrução export permite disponibilizar partes do seu módulo para outros arquivos. A instrução import permite trazer essas partes onde você precisar delas.

Informação

Use nomes claros e descritivos para seus módulos e funções. Isso torna o código mais fácil de ler e manter.

Exportações Padrão vs Exportações Nomeadas

Você pode usar exportações padrão ou nomeadas para compartilhar diferentes partes do seu código. As exportações nomeadas compartilham múltiplas funcionalidades pelo nome, e as exportações padrão compartilham uma única funcionalidade sem especificar um nome.

Exemplo:

// mathFunctions.js
export default function subtract(a, b) {
  return a - b;
}

// Using the default export
import subtract from './mathFunctions.js';

A palavra-chave default é usada para exportar uma única função ou variável. Ao importar uma exportação padrão, você pode usar qualquer nome para referenciá-la.

Informação

As exportações nomeadas são boas para funções utilitárias, e as exportações padrão são úteis para a principal funcionalidade de um arquivo, como um componente React ou uma classe.

Um arquivo pode combinar uma exportação padrão com qualquer número de exportações nomeadas. Você as importa juntas, a padrão primeiro e as nomeadas entre chaves:

// mathFunctions.js
export const add = (a, b) => a + b;        // named export
export default function subtract(a, b) {    // default export
  return a - b;
}

// app.js
import subtract, { add } from './mathFunctions.js';

Algumas regras que vale lembrar:

  • Um módulo pode ter apenas uma exportação padrão, mas muitas exportações nomeadas.
  • Importações nomeadas devem usar o nome exato exportado (ou renomear com as: import { add as sum } from './mathFunctions.js').
  • Uma importação padrão pode usar qualquer nome que você queira, pois o padrão não tem um nome fixo.

Renomeação e Re-exportação

Você pode renomear na entrada ou saída com as, e re-exportar de outro módulo para construir um único ponto de entrada (um arquivo "barril"):

// utils.js
export { add as sum } from './mathFunctions.js';
export { default as subtract } from './mathFunctions.js';

// app.js
import { sum, subtract } from './utils.js';

Aproveitando Módulos para um Código Limpo

Usar módulos pode tornar seu código mais fácil de lidar, especialmente à medida que os projetos crescem.

Estrutura de Diretórios

Uma boa organização de pastas ajuda a manter o código organizado.

Exemplo:

/src
  /components
  /helpers
  /models
  /services
index.js

Gerenciando Dependências

Gerenciar dependências significa garantir que seus arquivos de código funcionem corretamente em conjunto.

Exemplo:

// Webpack configuration for bundling modules
const path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      { test: /\.js$/, use: 'babel-loader' }
    ]
  }
};

Esta configuração instrui o Webpack a começar com index.js, empacotar ele e todas as suas dependências em bundle.js, e colocar o resultado na pasta dist.

Informação

Observação: Os arquivos de configuração do Webpack tradicionalmente usam a sintaxe CommonJS (module.exports), mesmo em projetos com muito ES6.

Técnicas Avançadas de Módulos

O uso de recursos avançados de módulos pode tornar o seu código mais eficiente e fácil de gerenciar.

Importações Dinâmicas

As importações dinâmicas permitem carregar código apenas quando necessário, o que pode acelerar sua aplicação.

Exemplo:

// Dynamically importing a module
// Assuming a button element exists
button.onclick = () => {
  import('./messageModule.js')
    .then(module => {
      module.showMessage('Dynamic Import Executed!');
    });
};

Este código carrega um módulo apenas quando um botão é clicado, o que pode reduzir os tempos de carregamento inicial.

Informação

Use importações dinâmicas para partes da sua aplicação que não são imediatamente necessárias, como funcionalidades adicionais acessadas posteriormente.

import() retorna uma promise, então você também pode usá-la com async/await:

async function loadFeature() {
  const module = await import('./messageModule.js');
  module.showMessage('Loaded on demand');
}

Consulte Importações Dinâmicas para uma análise mais aprofundada.

Comunicação Entre Módulos

Os módulos devem ser autocontidos, mas podem se comunicar por meio de recursos compartilhados.

Exemplo:

// stateManager.js
export let state = { count: 0 };

// counter.js
import { state } from './stateManager.js';
state.count++;

Este código mostra dois módulos compartilhando um objeto de estado. Como os módulos JavaScript são armazenados em cache como singletons, ambos os arquivos referenciam exatamente o mesmo objeto na memória. Quando um módulo altera o estado, o outro enxerga a mudança.

Informação

Observação: Este exemplo muta o objeto compartilhado diretamente. Para aplicações em produção, considere usar uma biblioteca de gerenciamento de estado ou um padrão reativo para lidar com as atualizações de forma previsível.

Entendendo os Sistemas de Módulos e seu Uso Hoje

No desenvolvimento web moderno, entender os diversos sistemas de módulos é essencial:

  • CommonJS: Utilizado principalmente em Node.js para código no lado do servidor.
  • AMD (Asynchronous Module Definition): Usado para carregamento assíncrono de módulos, adequado para navegadores.
  • Módulos ES6: O padrão no desenvolvimento web moderno, com suporte a carregamento síncrono e assíncrono.

Exemplos para Cada Sistema de Módulos

Para ilustrar esses conceitos em JavaScript, incluiremos trechos para cada tipo de módulo ao lado do exemplo ES6 existente.

Exemplo CommonJS:

// mathFunctions.js
exports.add = function(a, b) {
  return a + b;
};

// app.js
const math = require('./mathFunctions.js');
console.log(math.add(5, 3));

Explicação do CommonJS: Neste exemplo, usamos exports para tornar a função add disponível fora do arquivo. Em seguida, em outro arquivo, usamos require para importar a função add e poder utilizá-la. Esse sistema é comumente usado em Node.js.

Exemplo AMD:

// mathFunctions.js
define([], function() {
  return {
    add: function(a, b) {
      return a + b;
    }
  };
});

// app.js
require(['mathFunctions'], function(math) {
  console.log(math.add(5, 3));
});

Explicação do AMD: Este exemplo usa define para declarar um módulo sem dependências e retorna um object contendo a função add. Em seguida, require é usado para carregar o módulo de forma assíncrona. Isso é útil para carregar módulos dinamicamente no navegador.

Exemplo de Módulos ES6:

// mathFunctions.js
export const add = (a, b) => a + b;

// app.js
import { add } from './mathFunctions.js';
console.log(add(5, 3));

Explicação dos Módulos ES6: Aqui, usamos export para disponibilizar a função add e import para utilizá-la em outro arquivo. Este é o padrão moderno para gerenciar módulos em JavaScript e é suportado pela maioria dos navegadores.

Habilitando Módulos ES6 no Node.js

Ao usar módulos ES6 no Node.js, você pode aproveitar a mesma sintaxe de import/export normalmente usada no desenvolvimento JavaScript front-end. Isso permite uma sintaxe de módulo consistente tanto nos ambientes cliente quanto servidor.

Para usar a sintaxe de módulo ES6 no Node.js, você precisa garantir que seu ambiente a suporte. A partir do Node.js versão 14, os módulos ES6 são estáveis, e desde a versão 16, eles são habilitados por padrão quando "type": "module" é definido. Veja como configurar:

Atualize o package.json: No arquivo package.json do seu projeto Node.js, adicione a seguinte linha:

"type": "module"

Isso diz ao Node.js para tratar arquivos .js como módulos ES6 por padrão.

Extensões de Arquivo: Use .js para seus arquivos de módulo, ou use explicitamente .mjs se preferir. O Node.js reconhece ambos, mas se você estiver usando a configuração "type": "module", .js será tratado como um módulo ES6.

// mathFunctions.js
export function add(a, b) {
  return a + b;
}

// app.js
import { add } from './mathFunctions.js';
console.log(add(5, 3)); // 8
Informação

No ESM do Node.js, importações relativas exigem a extensão do arquivo (./mathFunctions.js, não ./mathFunctions). Isso difere do CommonJS, onde a extensão é opcional.

Armadilhas Comuns

Alguns comportamentos dos módulos pegam de surpresa os iniciantes. Conhecê-los com antecedência economiza tempo de depuração:

  • Caminhos relativos precisam de extensão no navegador. import { add } from './math' funciona em muitos bundlers, mas falha com o ESM nativo do navegador, onde você deve escrever './math.js'.
  • Os módulos são executados uma vez e armazenados em cache. Não importa quantos arquivos importem o mesmo módulo, seu código de nível superior é executado uma única vez e todo importador compartilha a mesma instância. Foi isso que fez o exemplo de estado compartilhado funcionar acima.
  • As importações são vínculos somente leitura, não cópias. Você não pode reatribuir um valor importado (add = 5 lança um erro). Você está olhando para um link ativo para a exportação, portanto, se o módulo exportador alterar o valor, os importadores verão o novo valor.
  • import/export deve estar no nível superior. Instruções estáticas de import e export não podem aparecer dentro de um bloco if ou de uma função. Quando precisar de carregamento condicional, use a função dinâmica import().
  • Scripts de módulo são diferidos. Um <script type="module"> no navegador não bloqueia a análise; ele é executado após o HTML ser analisado, semelhante ao uso de defer.

Para mais informações sobre o modo estrito sempre ativo, consulte Modo estrito. Para a referência completa de export/import, consulte Export e Import.

Boas Práticas para Usar Módulos JavaScript

  1. Mantenha a Simplicidade: Use nomes claros e simples para seus arquivos e exportações.
  2. Seja Consistente: Aplique os mesmos padrões e estruturas em todo o seu projeto para tornar o código previsível.
  3. Documente Tudo: Comente seu código e documente como usar seus módulos.
  4. Otimize Conforme Necessário: Revise e otimize regularmente seu código à medida que o projeto cresce.

Exemplo Completo

Abaixo está um exemplo completo que integra tudo o que você aprendeu neste artigo.

Estrutura do Projeto:

/src
  /math
    - mathFunctions.js
  - app.js
index.html

mathFunctions.js:

export const add = (a, b) => a + b;
export default function subtract(a, b) {
  return a - b;
}

app.js:

import subtract, { add } from './math/mathFunctions.js';

document.getElementById('add').addEventListener('click', function() {
  const result = add(5, 3);
  document.getElementById('result').textContent = `Adding: ${result}`;
});

document.getElementById('subtract').addEventListener('click', function() {
  const result = subtract(5, 3);
  document.getElementById('result').textContent = `Subtracting: ${result}`;
});

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>JavaScript Module Example</title>
</head>
<body>
  <button id="add">Add 5 + 3</button>
  <button id="subtract">Subtract 5 - 3</button>
  <div id="result"></div>
  <script type="module" src="src/app.js"></script>
</body>
</html>

Observação: Os módulos ES requerem um servidor de desenvolvimento local (por exemplo, npx serve ou Vite) para serem executados no navegador devido a restrições de CORS. Abrir index.html diretamente via file:// falhará.

Esta configuração usa uma página web simples com botões para demonstrar a adição e subtração de números usando funções importadas. Os resultados são exibidos diretamente na página.

Explicação do Exemplo

  • mathFunctions.js: Este arquivo contém duas funções (add e subtract) que são exportadas como módulos. add é uma exportação nomeada, e subtract é uma exportação padrão.
  • app.js: Este arquivo importa as funções de mathFunctions.js e as vincula a eventos de clique de botão para realizar cálculos quando o usuário interage com a página.
  • index.html: O arquivo HTML configura a interface do usuário com botões e uma área de exibição para os resultados. Ele vincula app.js como um módulo.

Este exemplo completo demonstra como os módulos JavaScript podem ser estruturados e usados dentro de uma aplicação real.

Conclusão

Os módulos JavaScript são uma ferramenta poderosa para organizar e manter aplicações web de grande escala. Ao entender e usar diferentes sistemas de módulos adequadamente, você pode aprimorar a escalabilidade e a manutenibilidade do seu projeto. Atualizar regularmente seu conhecimento sobre sintaxe de módulos, boas práticas e técnicas avançadas garantirá que suas habilidades de desenvolvimento permaneçam afiadas e seus projetos se mantenham à frente.

Prática

Prática
Quais são os benefícios de usar módulos JavaScript?
Quais são os benefícios de usar módulos JavaScript?
Was this page helpful?