Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

ES Modules no Node.js: Guia Prático

Atualizado em: 3 de agosto de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

O sistema de módulos define como arquivos compartilham funções, classes e dados. Os ES Modules no Node.js usam a sintaxe padronizada import e export, compatível com navegadores e ferramentas modernas. Eles coexistem com CommonJS, mas possuem regras diferentes para resolução, extensões, caminhos e carregamento.

Migrar apenas trocando require() por import pode quebrar o projeto. Em ESM, caminhos relativos exigem extensão, __dirname não existe diretamente e alguns pacotes exportam formatos diferentes. É importante entender como o Node.js identifica cada módulo.

Neste guia, você aprenderá a habilitar ESM, criar exports, usar import dinâmico, trabalhar com URLs, importar JSON, integrar CommonJS e organizar pacotes.

Habilitando ES Modules

A forma mais comum é definir type no package.json:

{
  "type": "module"
}

Arquivos .js passam a ser tratados como ESM. Outra opção é usar a extensão .mjs. Para forçar CommonJS dentro de um pacote ESM, use .cjs.

A documentação oficial de ES Modules detalha resolução e interoperabilidade. A organização de pacotes está na documentação de Packages.

Para revisar JavaScript e Node.js, consulte o que é JavaScript e o que é Node.js.

Export nomeado

export function sum(a, b) {
  return a + b;
}

export const version = '1.0.0';

No consumidor:

import { sum, version } from './math.js';

O nome importado precisa corresponder ao exportado, salvo quando é usado as.

Export default

export default class UserService {
  async findById(id) {
    return database.users.findById(id);
  }
}
import UserService from './user-service.js';

Um módulo possui no máximo um export default. Exports nomeados costumam facilitar refatoração e autocompletar quando há várias funcionalidades.

Extensões em caminhos relativos

import { sum } from './math.js';

Em ESM, o Node.js normalmente exige a extensão completa em caminhos relativos. Isso segue o modelo de URLs e evita tentativas implícitas de vários arquivos.

Imports de módulos nativos

import fs from 'node:fs';
import { readFile } from 'node:fs/promises';

O prefixo node: deixa claro que o módulo é nativo e evita conflito com pacotes de mesmo nome.

__dirname e __filename

ES Modules usam import.meta.url:

import { fileURLToPath } from 'node:url';
import path from 'node:path';

const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);

const configFile = path.join(dirname, 'config.json');

Em versões atuais, propriedades auxiliares podem existir, mas confirme a versão mínima antes de usá-las.

import.meta.url

O valor é uma URL completa do módulo. Ele permite localizar arquivos relativos sem depender do diretório atual do processo:

const templateUrl = new URL('./template.html', import.meta.url);
const template = await readFile(templateUrl, 'utf8');

APIs de arquivo do Node.js aceitam URLs file: em muitos métodos. Veja File System no Node.js.

Import dinâmico

async function loadAdapter(name) {
  const module = await import(`./adapters/${name}.js`);
  return module.default;
}

import() retorna uma Promise e pode ser usado condicionalmente. Não monte caminhos com entrada não confiável; use uma lista de adaptadores permitidos.

Top-level await

const config = await loadConfig();
export const application = createApplication(config);

ESM permite await no nível superior. Use com cuidado: um módulo lento bloqueia a inicialização de todos os módulos que dependem dele. Prefira uma função explícita de bootstrap para operações complexas.

Importando JSON

A sintaxe e os atributos suportados dependem da versão do Node.js:

import config from './config.json' with {
  type: 'json'
};

Defina a versão mínima e teste o ambiente de produção. Para configurações mutáveis, ler com fs pode oferecer tratamento de erro mais claro.

Interoperabilidade com CommonJS

import legacyPackage from 'legacy-package';

Ao importar CommonJS, o objeto module.exports costuma aparecer como default. Exports nomeados podem ser detectados por análise estática, mas não dependa disso sem testar.

Usando createRequire()

Quando um recurso só funciona com require():

import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const legacyConfig = require('./legacy-config.cjs');

Use como ponte de migração, não como padrão para todo o código.

Export map no package.json

{
  "name": "my-library",
  "type": "module",
  "exports": {
    ".": "./src/index.js",
    "./client": "./src/client.js"
  }
}

exports define a API pública e impede imports profundos não declarados. Isso permite reorganizar arquivos internos sem quebrar consumidores.

Exports condicionais

{
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  }
}

Bibliotecas podem fornecer ESM e CommonJS. A ordem e as condições precisam ser testadas em ferramentas diferentes para evitar duas instâncias do mesmo estado.

O problema de pacote duplo

Quando uma biblioteca é carregada por import e require em formatos diferentes, caches separados podem criar duas cópias. Singletons e comparação de classes podem falhar. Centralize o estado em um módulo comum ou publique um formato quando possível.

Resolução de pacotes

Imports sem caminho relativo são resolvidos via pacotes e seus campos exports. Não dependa de arquivos internos não expostos:

import feature from 'package/internal/file.js';

Esse caminho pode deixar de funcionar quando o pacote adiciona um export map.

Ciclos de dependência

ESM possui bindings vivos, mas ciclos ainda podem produzir valores não inicializados. Separe contratos, mova constantes compartilhadas ou injete dependências. Não resolva ciclos apenas mudando a ordem dos imports.

Tree shaking

Bundlers conseguem analisar imports estáticos e remover código não usado. O Node.js executando diretamente não faz tree shaking, mas a sintaxe estática beneficia ferramentas de build.

Testes

import test from 'node:test';
import assert from 'node:assert/strict';
import { sum } from './math.js';

test('sum', () => {
  assert.equal(sum(2, 3), 5);
});

O guia de Node Test Runner apresenta mocks e cobertura.

Migrando de CommonJS

Migre por etapas:

  • defina a versão mínima do Node.js;
  • identifique dependências CommonJS e ESM-only;
  • adicione extensões a imports relativos;
  • substitua __dirname e __filename;
  • revise carregamento de JSON;
  • converta testes e scripts;
  • valide ferramentas de build e lint;
  • publique uma versão principal quando a mudança afetar consumidores.

Desempenho e cache

Módulos são avaliados uma vez por URL e reutilizados. URLs diferentes que apontam ao mesmo arquivo podem criar instâncias distintas em situações especiais. Evite parâmetros e fragmentos sem necessidade.

Segurança

Import dinâmico baseado em entrada pode carregar código inesperado. Use mapas explícitos:

const adapters = {
  postgres: () => import('./adapters/postgres.js'),
  memory: () => import('./adapters/memory.js')
};

Erros comuns

  • Esquecer extensão em import relativo.
  • Usar __dirname diretamente.
  • Assumir que CommonJS possui exports nomeados confiáveis.
  • Usar top-level await em inicialização lenta.
  • Carregar caminho dinâmico do usuário.
  • Publicar pacote sem export map estável.
  • Misturar duas cópias de pacote dual.
  • Migrar sem testar scripts e ferramentas.

Boas práticas

  • Use node: para módulos nativos.
  • Inclua extensões em imports relativos.
  • Prefira exports nomeados para APIs amplas.
  • Use import.meta.url para recursos relativos.
  • Limite import dinâmico a opções conhecidas.
  • Defina exports em bibliotecas.
  • Documente versão mínima do Node.js.
  • Evite ciclos de dependência.
  • Teste interoperabilidade CommonJS.
  • Migre em etapas.

Conclusão

Os ES Modules no Node.js trazem a sintaxe padronizada de módulos para servidores e ferramentas. Eles oferecem imports estáticos, URLs, top-level await e integração com export maps.

A migração exige atenção a extensões, caminhos, JSON e CommonJS. Com uma API pública clara e versão mínima definida, ESM melhora a consistência entre Node.js, navegador e ferramentas modernas.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita