W3docs

JavaScript Dynamic import()

Aprenda o dynamic import() do JavaScript — carregue módulos sob demanda com code splitting e lazy loading, usando await e .then(), com tratamento de erros e exemplos executáveis.

As importações dinâmicas em JavaScript são um recurso introduzido no ECMAScript 2020 (ES2020) que permite carregar um módulo em tempo de execução, sob demanda, em vez de carregar tudo de uma vez quando o script é interpretado pela primeira vez. Ao contrário da instrução estática import, a forma import() é uma expressão semelhante a uma função que retorna uma promise, portanto você pode decidir quando e se carregar um módulo com base em condições, ações do usuário ou roteamento. Este guia cobre a sintaxe, os casos de uso mais comuns (code splitting, lazy loading, carregamento condicional), tratamento de erros e um exemplo executável de ponta a ponta.

Este capítulo pressupõe que você esteja familiarizado com módulos ES e async/await.

import estático vs. import() dinâmico

Uma instrução import estática deve aparecer no nível superior de um módulo e é totalmente resolvida antes de qualquer código do módulo ser executado. Isso a torna previsível e amigável para ferramentas, mas também significa que todo módulo importado estaticamente é buscado antecipadamente — mesmo código que o usuário talvez nunca alcance.

import() é diferente em três aspectos importantes:

  • É uma expressão, não uma instrução, portanto pode aparecer em qualquer lugar — dentro de um if, uma função ou um manipulador de eventos.
  • Aceita um especificador dinâmico: o caminho do módulo pode ser uma variável ou uma string computada, não apenas um literal de string.
  • Retorna uma promise que resolve para o objeto namespace do módulo (um objeto cujas propriedades são as exportações nomeadas do módulo, mais default).
// Static import — runs at parse time, must be top-level
import { formatDate } from './utils.js';

// Dynamic import — runs when this line executes, can be anywhere
const utils = await import('./utils.js');
utils.formatDate(new Date());

Como import() retorna uma promise, você trata o resultado com await (dentro de uma função async) ou com .then()/.catch():

// With await
const mod = await import('./utils.js');

// With .then() / .catch()
import('./utils.js')
  .then(mod => mod.formatDate(new Date()))
  .catch(err => console.error('Failed to load module:', err));

Lendo exportações do objeto do módulo

O valor resolvido é o objeto namespace do módulo. Exportações nomeadas são propriedades; a exportação padrão fica sob a chave default. A desestruturação torna isso mais limpo:

// math.js exports: export function add(a,b){...}, export default function greet(){...}
const { add, default: greet } = await import('./math.js');

console.log(add(2, 3)); // 5
console.log(greet());   // "hello"
Informação

await import(...) só funciona dentro de uma função async ou no nível superior de um módulo ES (top-level await). Em um <script> comum ou em uma função não assíncrona, use a forma .then() em vez disso.

Casos de uso comuns

As importações dinâmicas se destacam quando parte da sua aplicação é usada condicionalmente ou não é necessária imediatamente. Abaixo estão os padrões mais comuns.

Code splitting

O caso de uso mais comum para importações dinâmicas é o code splitting: dividir seu bundle em partes menores que carregam apenas quando necessário — tipicamente quando uma rota é visitada ou um recurso é usado. Abaixo, um script pesado é buscado somente após o usuário clicar, em vez de inflar o carregamento inicial da página.

button.addEventListener('click', function () {
    import('./heavyScript.js').then(mod => {
        mod.runHeavyTask();
    });
});

Como o manipulador de clique é síncrono, a forma .then() é usada aqui em vez de await. O navegador (ou bundler) solicita heavyScript.js apenas no primeiro clique; cliques subsequentes reutilizam o módulo em cache.

Aviso

Meça antes de dividir. Adicionar muitos chunks dinâmicos pequenos pode prejudicar o desempenho — cada um é uma viagem de ida e volta na rede separada. Reserve as importações dinâmicas para código que seja genuinamente grande ou raramente usado.

Lazy loading de componentes

Frameworks como React, Angular e Vue usam importações dinâmicas internamente para carregar componentes de forma lazy — um componente só é buscado quando é renderizado pela primeira vez.

// Lazy loading a component in React
const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() {
    return (
        <React.Suspense fallback={<div>Loading...</div>}>
            <LazyComponent />
        </React.Suspense>
    );
}

React.lazy encapsula a importação dinâmica e React.Suspense exibe o fallback até que o chunk chegue. O usuário vê Loading... apenas pelo breve momento em que o componente está sendo buscado.

Uso avançado de importações dinâmicas

Carregamento condicional

Como import() é uma expressão, você pode protegê-la com qualquer condição — uma flag de recurso, uma configuração do usuário, o ambiente ou até mesmo a localidade do navegador.

if (user.prefersAdvancedMode) {
    const advanced = await import('./advancedEditor.js');
    advanced.init();
}

Usuários que nunca ativam o modo avançado nunca baixam advancedEditor.js. Você pode ir além com um especificador dinâmico — carregar um módulo diferente por localidade, por exemplo:

const locale = navigator.language.startsWith('fr') ? 'fr' : 'en';
const messages = await import(`./locales/${locale}.js`);
console.log(messages.default.greeting);
Aviso

Bundlers como Webpack e Vite precisam saber quais arquivos podem ser carregados. Um especificador totalmente arbitrário (por exemplo, um caminho construído a partir de entrada do usuário) não pode ser bundled. Mantenha a parte variável do caminho em um diretório e extensão conhecidos, como no exemplo de localidade acima.

Suporte em ferramentas de build e Node.js

Quando você escreve import('./module.js'), bundlers como Webpack, Rollup e Vite emitem automaticamente um chunk separado e o carregam sob demanda — normalmente nenhuma configuração extra é necessária. No navegador, o import() nativo é suportado em todos os navegadores modernos.

import() também funciona em Node.js (v12+), incluindo dentro de arquivos CommonJS, que é a maneira padrão de carregar um módulo ES a partir de código CommonJS:

// Loading an ESM module from a CommonJS file
async function run() {
    const { default: chalk } = await import('chalk');
    console.log(chalk.green('Loaded an ESM package from CommonJS'));
}
run();

Metadados do módulo com import.meta

Dentro de um módulo você pode ler import.meta para obter informações contextuais. O campo mais amplamente suportado é import.meta.url, que contém a URL do módulo atual — útil para resolver recursos adjacentes:

// Resolve a JSON file relative to the current module
const dataUrl = new URL('./data.json', import.meta.url);
const data = await import(dataUrl, { with: { type: 'json' } });

Um exemplo completo: widget de clima dinâmico

O widget de clima carregará dinamicamente o módulo para buscar dados de clima somente quando o usuário solicitar. Este é um cenário ideal para importações dinâmicas, pois adia o carregamento de código de interação com API potencialmente pesado até que seja realmente necessário.

O exemplo usa três arquivos: uma página HTML, um script de entrada e o módulo carregado de forma lazy.

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dynamic Weather Widget</title>
</head>
<body>
    <h1>Weather Widget</h1>
    <button id="loadWeather">Load Weather</button>
    <div id="weatherOutput">Click the button to load the weather.</div>

    <script src="index.js"></script>
</body>
</html>

index.js:

document.getElementById('loadWeather').addEventListener('click', async () => {
    const output = document.getElementById('weatherOutput');
    try {
        const weatherModule = await import('./weatherModule.js');
        const data = await weatherModule.loadWeather();
        output.textContent = `Weather: ${data.weather}`;
    } catch (err) {
        output.textContent = 'Failed to load weather data.';
    }
});

Este código dispara uma importação dinâmica na interação do usuário:

  1. Event listener: Anexa um manipulador de clique ao botão.
  2. Importação dinâmica: Usa await import() para buscar o módulo somente quando clicado, mantendo o bundle inicial pequeno.
  3. Tratamento de erros: O try...catch envolve tanto o import() quanto a chamada de dados, portanto um download com falha ou uma requisição rejeitada exibe a mensagem de fallback.

Essa abordagem ajuda a tornar as páginas web eficientes e responsivas carregando recursos apenas quando necessário e fornecendo feedback imediato às interações do usuário.

weatherModule.js:

export async function loadWeather() {
    // Simulated API call
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ weather: 'Sunny, 76°F' });  // Simulating weather data
        }, 1000);
    });
}

A função simula a busca de dados de uma fonte remota sem precisar de uma API real: ela resolve após um atraso de um segundo para que você possa ver o carregamento adiado em ação.

Explicação do exemplo

  • Configuração HTML: Fornece um botão e um contêiner para a saída.
  • Importação dinâmica em ação: Clicar no botão faz com que index.js carregue weatherModule.js sob demanda.
  • Módulo de clima: Simula um atraso de API, mostrando como as importações dinâmicas adiam lógica pesada ou condicional até que seja realmente necessária.

Armadilhas comuns

  • await fora de um módulo ou função async. O await import() de nível superior só funciona em módulos ES; em scripts comuns ou callbacks não assíncronos, use .then().
  • Esquecer .default. A exportação padrão de um módulo é acessada via a propriedade default do objeto resolvido, não o próprio objeto.
  • Caminhos totalmente dinâmicos. Bundlers não podem dividir um caminho que não conseguem analisar. Mantenha a parte literal do especificador (diretório e extensão) estática.
  • Divisão excessiva. Cada chunk dinâmico é uma requisição separada. Divida código grande ou raramente usado, não cada pequeno helper.

Conclusão

O import() dinâmico permite carregar módulos sob demanda, retornando uma promise que resolve para o objeto namespace do módulo. Ele potencializa o code splitting, componentes carregados de forma lazy, carregamento condicional e importações com suporte a localidade — melhorando o desempenho de inicialização quando usado de forma deliberada. Combine-o com async/await e um sólido tratamento de erros, e apoie-se no seu bundler para transformar cada import() em um chunk otimizado.

Para se aprofundar, revise módulos ES: export e import, a introdução a módulos e promises.

Prática

Prática
Quais afirmações sobre o dynamic import() do JavaScript estão corretas?
Quais afirmações sobre o dynamic import() do JavaScript estão corretas?
Was this page helpful?