O sistema de módulos do Node.js resolve arquivos, pacotes, módulos nativos e formatos como CommonJS e ES Modules. Além de require() e import, o runtime oferece a Module API no Node.js por meio de node:module, com funções para criar require em ES Modules, identificar módulos nativos, sincronizar exports, trabalhar com source maps, registrar hooks e controlar recursos recentes do carregador.
Essas APIs são úteis para CLIs, bundlers, frameworks, ferramentas de teste, loaders, sistemas de plugins e migrações entre CommonJS e ESM. Algumas funções são estáveis e antigas; outras são experimentais ou mudam entre versões. Por isso, fixe a versão do Node.js e consulte a documentação antes de depender de recursos avançados.
Neste guia, você aprenderá a usar createRequire(), builtinModules, isBuiltin(), syncBuiltinESMExports(), source maps, caminhos de resolução, hooks, cache de compilação e estratégias para criar ferramentas compatíveis e seguras.
O que é node:module?
node:module expõe utilitários usados pelo sistema de carregamento. A documentação oficial da Module API lista funções, classes e níveis de estabilidade. A documentação oficial de packages explica type, exports, imports e resolução.
Para os modelos básicos, consulte CommonJS no Node.js e ES Modules no Node.js.
Importando a API
const Module = require('node:module');Em ES Modules:
import * as Module from 'node:module';Também é possível importar funções específicas:
import {
createRequire,
builtinModules,
isBuiltin
} from 'node:module';createRequire()
Um ES Module não possui require global. createRequire() cria uma função de resolução baseada em uma URL ou caminho:
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const packageJson = require('./package.json');Essa técnica ajuda a carregar JSON, addons ou pacotes CommonJS durante uma migração.
Base de resolução
O require criado resolve caminhos como se estivesse localizado no arquivo informado. Não use uma base arbitrária fornecida pelo usuário.
const requireFromProject = createRequire(
new URL('./package.json', import.meta.url)
);Carregando CommonJS em ESM
const legacyLibrary = require('legacy-library');Antes de usar createRequire, teste se import direto já oferece interoperabilidade suficiente.
require.resolve()
const resolved = require.resolve('some-package');O resultado mostra o arquivo selecionado pela resolução CommonJS. Não trate o caminho como estável entre versões do pacote.
Plugins
Um sistema de plugins pode resolver módulos por nome:
function loadPlugin(name) {
if (!allowedPlugins.has(name)) {
throw new Error('Plugin não permitido');
}
const filename = require.resolve(name);
return require(filename);
}Nunca aceite qualquer nome de módulo vindo do cliente. Isso pode permitir carregar código instalado no servidor.
builtinModules
import { builtinModules } from 'node:module';
console.log(builtinModules);O array lista módulos nativos conhecidos pela versão atual. Ele é útil para ferramentas que analisam imports e não devem empacotar APIs do runtime.
Prefixo node:
Módulos nativos podem ser importados com prefixo:
import fs from 'node:fs';O prefixo deixa explícito que o módulo vem do Node.js e evita colisão visual com pacotes externos.
isBuiltin()
import { isBuiltin } from 'node:module';
console.log(isBuiltin('fs'));
console.log(isBuiltin('node:fs'));
console.log(isBuiltin('express'));Essa função é melhor que comparar manualmente com uma lista, pois entende formatos reconhecidos pela versão.
Bundlers
Uma ferramenta de bundle pode manter módulos nativos como externos:
if (isBuiltin(specifier)) {
return { external: true };
}Teste módulos com e sem prefixo node:.
syncBuiltinESMExports()
Módulos nativos podem ter exports nomeados ESM sincronizados a partir da versão CommonJS:
import fs, { readFileSync } from 'node:fs';
import { syncBuiltinESMExports } from 'node:module';
fs.readFileSync = () => Buffer.from('mock');
syncBuiltinESMExports();Depois da sincronização, bindings nomeados compatíveis podem refletir a alteração.
Riscos de monkey patch
Alterar módulos nativos globalmente afeta toda a aplicação e bibliotecas. Prefira injeção de dependência em testes.
syncBuiltinESMExports() é útil em ferramentas específicas, mas não deve virar padrão para mocks.
Module.builtinModules
Em CommonJS, a propriedade também pode ser acessada pelo objeto Module. Prefira imports nomeados em código moderno.
Resolução de packages
O Node.js considera:
- campo
type; - extensões;
- campo
exports; - campo
imports; - condições;
- diretórios
node_modules; - módulos nativos.
Não replique o algoritmo manualmente sem necessidade.
Package exports
{
"name": "my-library",
"exports": {
".": "./dist/index.js",
"./testing": "./dist/testing.js"
}
}Exports define a interface pública. Arquivos não exportados podem ficar inacessíveis mesmo existindo no pacote.
Conditional exports
{
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}Bibliotecas dual package precisam garantir comportamento consistente entre os formatos.
Dual package hazard
Quando o mesmo pacote é carregado como CommonJS e ESM, podem existir duas instâncias de estado. Evite singletons compartilhados implicitamente.
Package imports
{
"imports": {
"#config": "./src/config.js"
}
}Aliases iniciados com # ajudam a organizar imports internos sem depender de caminhos longos.
import.meta.resolve()
Em versões modernas, ES Modules podem resolver specifiers com import.meta.resolve(). Compare com require.resolve() conforme o formato.
const url = import.meta.resolve('some-package');URLs e caminhos
ESM usa URLs. Converta corretamente:
import { fileURLToPath } from 'node:url';
const filename = fileURLToPath(import.meta.url);Veja URL API no Node.js e Módulo Path no Node.js.
Source maps
A Module API inclui recursos relacionados a source maps em versões compatíveis. Eles ajudam a mapear código compilado para TypeScript ou fonte original.
findSourceMap()
import { findSourceMap } from 'node:module';
const sourceMap = findSourceMap(filename);O mapa precisa ter sido carregado e o suporte habilitado conforme a versão.
SourceMap
A classe SourceMap permite consultar entradas e encontrar posições originais. Ferramentas de cobertura e stack traces podem usar essas informações.
Sourcemaps e segurança
Mapas podem conter paths e código fonte. Não publique em ambientes públicos sem avaliar exposição.
Hooks de carregamento
O Node.js oferece mecanismos para customizar resolução e carregamento. APIs antigas usavam loaders por flags; versões recentes também podem oferecer registro programático.
Esses recursos são avançados e podem ser experimentais.
register()
Em versões compatíveis, register() registra hooks ESM:
import { register } from 'node:module';
register('./hooks.mjs', import.meta.url);Consulte assinatura e estabilidade na versão usada.
Hook resolve
export async function resolve(specifier, context, nextResolve) {
if (specifier.startsWith('virtual:')) {
return {
url: `data:text/javascript,export default 42`,
shortCircuit: true
};
}
return nextResolve(specifier, context);
}Um hook pode redirecionar módulos, mas precisa evitar loops e validar specifiers.
Hook load
export async function load(url, context, nextLoad) {
const result = await nextLoad(url, context);
return result;
}Transformações precisam preservar formato, sourcemap e erros.
Transpilação
Loaders podem transpilar TypeScript ou outras sintaxes em tempo de execução. Isso aumenta startup e complexidade. Para produção, build antecipado costuma ser mais previsível.
Strip de TypeScript
Versões recentes podem oferecer APIs para remover sintaxe TypeScript. O suporte e as limitações evoluem. Não assuma equivalência com um compilador completo.
Veja o que é TypeScript.
Cache de compilação
APIs recentes podem permitir habilitar cache de compilação para reduzir trabalho em execuções futuras. A localização, permissões e compatibilidade precisam ser controladas.
Cache e versão
Cache de bytecode depende da versão do Node.js, V8, arquitetura e conteúdo. Não compartilhe indiscriminadamente entre ambientes diferentes.
Cache em containers
Um filesystem efêmero perde o cache a cada reinício. Um volume persistente melhora reaproveitamento, mas precisa de limpeza e permissões.
Module cache CommonJS
const first = require('./service');
const second = require('./service');
console.log(first === second);CommonJS armazena módulos em require.cache.
Invalidando cache
delete require.cache[require.resolve('./service')];Recarregar módulos pode deixar referências antigas e duplicar estado. Use apenas em ferramentas de desenvolvimento.
ESM cache
ES Modules possuem cache próprio baseado em URL. Adicionar query string pode criar outra instância, mas esse padrão deve ser usado com cuidado.
Plugins seguros
Um sistema de plugins deve:
- usar allowlist;
- validar versão;
- isolar falhas;
- limitar permissões;
- evitar código remoto arbitrário;
- registrar carregamento;
- definir contrato de exports.
O Permission Model no Node.js pode reduzir capacidades, mas não torna plugins não confiáveis completamente seguros.
Single Executable
Bundling e resolução dinâmica precisam ser planejados em uma Single Executable no Node.js. Módulos que dependem do filesystem podem não estar incorporados.
Testes
Cubra:
- require em ESM;
- imports condicionais;
- módulos nativos;
- package exports;
- plugin não permitido;
- source maps;
- hook com erro;
- cache;
- Windows e POSIX;
- versões suportadas.
Observabilidade
Registre carregamentos especiais, não cada import normal. Para plugins, inclua nome, versão, caminho sanitizado, duração e resultado.
Performance
Hooks e transpilação em runtime adicionam custo à inicialização. Faça benchmark e use cache quando seguro.
Segurança
Carregar um módulo executa código. Nunca transforme input HTTP em specifier:
await import(req.query.module);Isso pode permitir acesso a pacotes ou arquivos internos.
Path traversal
Mesmo com paths, valide que plugins estão dentro de uma raiz permitida. Consulte o guia de Path para contenção e symlinks.
Dependências
Use lockfile, verificação de integridade e atualização regular. A Module API não garante que um pacote instalado seja confiável.
Erros comuns
- Usar createRequire com base errada: pacotes diferentes são resolvidos.
- Carregar specifier externo: código arbitrário pode executar.
- Depender de arquivo interno do pacote: exports pode bloquear.
- Invalidar cache em produção: estado duplica.
- Usar loader sem sourcemap: stacks ficam incorretas.
- Confiar em API experimental: atualização quebra o projeto.
- Empacotar módulo nativo: o bundle falha no runtime.
Boas práticas
- Prefira
node:para módulos nativos. - Use createRequire apenas quando necessário.
- Respeite package exports.
- Evite imports dinâmicos não validados.
- Fixe a versão do Node.js.
- Teste hooks e sourcemaps.
- Use build antecipado quando possível.
- Controle cache.
- Restrinja plugins.
- Documente formatos CommonJS e ESM.
Conclusão
A Module API no Node.js oferece ferramentas para interoperabilidade, resolução, módulos nativos, source maps, hooks e cache de compilação. Ela é especialmente útil em frameworks, CLIs, bundlers e sistemas de plugins.
O poder exige cuidado: carregar módulos executa código, APIs avançadas podem ser experimentais e caches podem duplicar estado. Com specifiers controlados, versões fixas, package exports bem definidos e testes entre CommonJS e ESM, a Module API permite construir ferramentas flexíveis sem tornar o carregamento imprevisível ou inseguro.




