Dynamic Import no Node.js usa a função import() para carregar módulos de forma assíncrona durante a execução. Diferente do import estático, ele pode aparecer dentro de funções, condições e fluxos que dependem de configuração ou entrada já validada.
O recurso é útil para lazy loading, plugins controlados, comandos opcionais, adaptadores de infraestrutura e interoperabilidade entre CommonJS e ESM. Porém, caminhos construídos com dados externos podem introduzir carregamento inesperado de código.
Neste guia, você aprenderá sintaxe, cache, tratamento de erros, carregamento condicional, plugins com allowlist, testes, desempenho e boas práticas para produção.
Sintaxe básica
const modulo = await import('./relatorio.js');
await modulo.gerarRelatorio();import() retorna uma Promise resolvida com o namespace do módulo. Exports nomeados aparecem como propriedades e o export default aparece em default.
Import estático versus dinâmico
import { gerarRelatorio } from './relatorio.js';O import estático é analisado antes da execução e deve ser preferido para dependências obrigatórias. O dinâmico é adequado quando a dependência é opcional ou só deve ser carregada em um fluxo específico.
Carregamento sob demanda
async function exportar(formato) {
if (formato === 'csv') {
const { gerarCsv } = await import('./exporters/csv.js');
return gerarCsv();
}
if (formato === 'pdf') {
const { gerarPdf } = await import('./exporters/pdf.js');
return gerarPdf();
}
throw new Error('Formato não suportado');
}O código do exportador só é avaliado quando o fluxo correspondente é executado.
Allowlist para caminhos
Nunca concatene diretamente entrada do usuário:
// perigoso
const plugin = await import(`./plugins/${req.query.name}.js`);Use mapeamento fechado:
const plugins = {
csv: './plugins/csv.js',
pdf: './plugins/pdf.js'
};
const caminho = plugins[tipo];
if (!caminho) throw new Error('Plugin inválido');
const plugin = await import(caminho);Usando URL relativa ao módulo
const caminho = new URL('./plugins/pdf.js', import.meta.url);
const plugin = await import(caminho);Isso evita dependência do diretório atual do processo.
Export default
const { default: criarAdapter } = await import('./adapter.js');
const adapter = criarAdapter(config);Quando possível, prefira exports nomeados para reduzir ambiguidade.
Import dinâmico em CommonJS
async function executar() {
const { processar } = await import('./processador.mjs');
return processar();
}
module.exports = { executar };Essa é a principal ponte para consumir ESM a partir de um módulo CommonJS.
Cache de módulos
Importações repetidas do mesmo identificador normalmente reutilizam o módulo já carregado. O código de inicialização não deve ser tratado como uma função executada a cada chamada.
const a = await import('./config.js');
const b = await import('./config.js');
console.log(a === b);Evite armazenar estado mutável global no módulo. Exporte uma fábrica para criar instâncias independentes.
Concorrência no primeiro carregamento
Duas chamadas simultâneas para o mesmo módulo compartilham o processo de carregamento. Ainda assim, a inicialização interna pode chamar recursos externos e falhar.
Separe import de inicialização:
const { criarCliente } = await import('./client.js');
const cliente = await criarCliente(config);Tratamento de erros
try {
const modulo = await import(caminho);
await modulo.executar();
} catch (error) {
logger.error({ err: error, plugin: tipo }, 'Falha ao carregar plugin');
throw new Error('Plugin indisponível', { cause: error });
}Não exponha caminhos internos ou stack traces ao cliente.
Timeout de inicialização
O import em si não aceita um AbortSignal para interromper avaliação síncrona do módulo. Evite módulos que executam rede no topo. Aplique timeout na função de inicialização exportada.
const { criarAdapter } = await import(caminho);
const adapter = await Promise.race([
criarAdapter(config),
timeout(5000)
]);Lazy loading em CLIs
const comandos = {
migrate: './commands/migrate.js',
seed: './commands/seed.js',
report: './commands/report.js'
};Uma CLI pode carregar apenas o comando escolhido, reduzindo tempo de inicialização quando os módulos opcionais são pesados.
Adaptadores por ambiente
const adapters = {
memory: './storage/memory.js',
postgres: './storage/postgres.js'
};
const tipo = process.env.STORAGE;
const caminho = adapters[tipo];
if (!caminho) throw new Error('STORAGE inválido');Valide variáveis conforme o guia Variáveis de Ambiente no Node.js.
Plugins de terceiros
Carregar pacote definido pelo usuário é execução de código. Em sistemas extensíveis, mantenha registro administrativo, assinatura, versões permitidas e isolamento operacional.
Não considere import dinâmico uma sandbox.
Desempenho
Lazy loading pode reduzir startup, mas desloca latência para a primeira requisição. Para funcionalidades críticas, pré-carregue após o servidor estar pronto ou aqueça o módulo antes de receber tráfego.
Meça com perf_hooks no Node.js.
Pré-carregamento controlado
const cache = new Map();
async function obterPlugin(nome) {
if (!cache.has(nome)) {
const caminho = plugins[nome];
if (!caminho) throw new Error('Plugin inválido');
cache.set(nome, import(caminho));
}
return cache.get(nome);
}Armazenar a Promise evita disparar carregamentos duplicados.
Falha em Promise armazenada
Se o carregamento falhar, decida se o erro deve permanecer em cache ou permitir nova tentativa:
try {
return await cache.get(nome);
} catch (error) {
cache.delete(nome);
throw error;
}Retries devem ter limite e backoff. Veja Retry com Backoff no Node.js.
Testando módulos dinâmicos
Coloque a seleção de caminho em função pura:
export function resolverExporter(formato) {
const caminho = exporters[formato];
if (!caminho) throw new Error('Formato inválido');
return caminho;
}Teste allowlist, formato desconhecido, erro de carregamento, export ausente e inicialização.
Contrato do plugin
function validarPlugin(modulo) {
if (typeof modulo.executar !== 'function') {
throw new TypeError('Plugin sem executar()');
}
}Para contratos complexos, valide metadados com JSON Schema no Node.js.
Import dinâmico e ESM
Para configuração completa de módulos modernos, consulte ESM no Node.js.
Exports condicionais
Quando um pacote oferece entradas diferentes, o import dinâmico normalmente segue a condição import. Veja Conditional Exports no Node.js.
Erros comuns
- construir caminho com entrada não validada;
- usar import dinâmico para dependência obrigatória;
- executar rede durante avaliação do módulo;
- esperar recarregamento a cada chamada;
- não validar exports;
- ignorar latência da primeira execução;
- tratar plugin como sandbox;
- guardar Promise rejeitada para sempre.
Checklist para produção
- use allowlist;
- resolva caminhos relativos ao módulo;
- valide contrato exportado;
- separe carregamento e inicialização;
- aplique timeout à inicialização;
- meça startup e primeira chamada;
- registre falhas sem segredos;
- limite retries;
- teste todos os módulos permitidos.
Conclusão
Dynamic Import no Node.js permite carregar módulos somente quando necessários e cria uma ponte prática entre CommonJS e ESM. O recurso funciona melhor quando a seleção é controlada, os módulos não possuem efeitos colaterais e a inicialização é explícita.
Use imports estáticos para dependências essenciais e reserve import() para capacidades opcionais. Com allowlist, contratos e métricas, o lazy loading pode reduzir startup sem comprometer previsibilidade.
Consulte a documentação oficial de import expressions do Node.js e a referência de import dinâmico da MDN.



