Pular para o conteúdo principal

Importação Dinâmica `import()`

· Leitura de 5 minutos
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Importação Dinâmica import() introduz uma nova forma semelhante a uma função de import que desbloqueia novas capacidades em comparação com a importação estática. Este artigo compara os dois e oferece uma visão geral do que há de novo.

Importação estática import (recapitulação)

O Chrome 61 foi lançado com suporte para a instrução import do ES2015 dentro de módulos.

Considere o seguinte módulo, localizado em ./utils.mjs:

// Exportação padrão
export default () => {
console.log('Olá da exportação padrão!');
};

// Exportação nomeada `doStuff`
export const doStuff = () => {
console.log('Fazendo coisas…');
};

Aqui está como importar e usar o módulo ./utils.mjs estaticamente:

<script type="module">
import * as module from './utils.mjs';
module.default();
// → registra 'Olá da exportação padrão!'
module.doStuff();
// → registra 'Fazendo coisas…'
</script>
nota

Nota: O exemplo anterior usa a extensão .mjs para sinalizar que é um módulo em vez de um script regular. Na Web, extensões de arquivo não importam muito, desde que os arquivos sejam servidos com o tipo MIME correto (por exemplo, text/javascript para arquivos JavaScript) no cabeçalho HTTP Content-Type.

A extensão .mjs é especialmente útil em outras plataformas como Node.js e d8, onde não há conceito de tipos MIME ou outros ganchos obrigatórios como type="module" para determinar se algo é um módulo ou um script comum. Estamos usando a mesma extensão aqui para consistência entre plataformas e para distinguir claramente entre módulos e scripts comuns.

Essa forma sintática para importar módulos é uma declaração estática: ela só aceita um literal de string como especificador de módulo e introduz vinculações no escopo local via um processo de "ligação" pré-runtime. A sintaxe de import estática só pode ser usada no nível superior do arquivo.

import estático permite casos de uso importantes, como análise estática, ferramentas de empacotamento e eliminação de código morto (tree-shaking).

Em alguns casos, é útil:

  • importar um módulo sob demanda (ou condicionalmente)
  • calcular o especificador do módulo em tempo de execução
  • importar um módulo dentro de um script comum (em vez de um módulo)

Nenhuma dessas opções é possível com a import estática.

import() Dinâmico 🔥

import() Dinâmico introduz uma nova forma semelhante a uma função de import que atende a esses casos de uso. import(moduleSpecifier) retorna uma promessa para o objeto de namespace do módulo solicitado, que é criado após o carregamento, a instanciação e a avaliação de todas as dependências do módulo, bem como do próprio módulo.

Aqui está como importar e usar o módulo ./utils.mjs dinamicamente:

<script type="module">
const moduleSpecifier = './utils.mjs';
import(moduleSpecifier)
.then((module) => {
module.default();
// → registra 'Olá da exportação padrão!'
module.doStuff();
// → registra 'Fazendo coisas…'
});
</script>

Como import() retorna uma promessa, é possível usar async/await em vez do estilo de callback baseado em then:

<script type="module">
(async () => {
const moduleSpecifier = './utils.mjs';
const module = await import(moduleSpecifier)
module.default();
// → registra 'Olá da exportação padrão!'
module.doStuff();
// → registra 'Fazendo coisas…'
})();
</script>
nota

Nota: Embora import() pareça uma chamada de função, ele é especificado como sintaxe que por acaso usa parênteses (semelhante ao super()). Isso significa que import não herda de Function.prototype, então você não pode usá-lo com call ou apply, e coisas como const importAlias = import não funcionam — aliás, import nem é um objeto! Porém, isso não faz diferença prática.

Aqui está um exemplo de como o import() dinâmico permite carregar módulos sob demanda ao navegar em uma pequena aplicação de página única:

<!DOCTYPE html>
<meta charset="utf-8">
<title>Minha biblioteca</title>
<nav>
<a href="books.html" data-entry-module="books">Livros</a>
<a href="movies.html" data-entry-module="movies">Filmes</a>
<a href="video-games.html" data-entry-module="video-games">Video Games</a>
</nav>
<main>Este é um espaço reservado para o conteúdo que será carregado sob demanda.</main>
<script>
const main = document.querySelector('main');
const links = document.querySelectorAll('nav > a');
for (const link of links) {
link.addEventListener('click', async (event) => {
event.preventDefault();
try {
const module = await import(`/${link.dataset.entryModule}.mjs`);
// O módulo exporta uma função chamada `loadPageInto`.
module.loadPageInto(main);
} catch (error) {
main.textContent = error.message;
}
});
}
</script>

As capacidades de carregamento dinâmico habilitadas pelo import() dinâmico podem ser bastante poderosas quando aplicadas corretamente. Para fins de demonstração, Addy modificou um exemplo de PWA do Hacker News que importava todos os seus dependentes estaticamente, incluindo comentários, na primeira carga. A versão atualizada usa o import() dinâmico para carregar os comentários de maneira preguiçosa, evitando o custo de carregamento, análise e compilação até que o usuário realmente precise deles.

nota

Nota: Se seu aplicativo importa scripts de outro domínio (seja estaticamente ou dinamicamente), os scripts precisam ser retornados com cabeçalhos CORS válidos (como Access-Control-Allow-Origin: *). Isso ocorre porque, ao contrário dos scripts regulares, os scripts de módulos (e suas importações) são buscados com CORS.

Recomendações

import estático e import() dinâmico são ambos úteis. Cada um tem seus próprios casos de uso muito distintos. Use imports estáticos para dependências de pintura inicial, especialmente para conteúdo acima da dobra. Em outros casos, considere carregar dependências sob demanda com import() dinâmico.

Suporte a import() dinâmico