Instruções imports são úteis para linkar módulos (arquivos). Bundlers (ex: Browserify / Webpack) são normalmente acionados para criar um arquivo contendo tudo que foi importado a partir de um arquivo entry point e a ramificação de suas dependências. Loaders são capazes de carregar módulos dinamicamente (ex: O SystemJS é capaz de carregar módulos exportados em qualquer formato: CommonJS, AMD, ES6, variáveis globais e até mesmo módulos paraguaios vindos pela Ponte da Amizade ;)
Obs: Há um projeto chamado SystemJS builder que é um bundler e talvez possamos considerar o Webpack Dev Server um loader que roda dentro um serviço HTTP próprio. Ambos também suportam módulos não ES6.
Apesar se ser possível carregar módulos dinamicamente, a especificação ES6 modules determina que elas são instruções declarativas, sendo obrigatoriamente realizadas antes do script iniciar. Isso traz diversas vantagens, a mais notada delas é a Treek Shaking.
Ao utilizar chaves, como em:
import { Message } from './message.model';
Estamos importando alguma constante / variável / função / classe que foi exportada sem a instrução default.
Ex: export class Message { }
Ao contrário de módulos CommonJS, ES6 imports importam os chamados immutable bindings, ou seja referências imutáveis ao que foi exportado no módulo em questão. Assim, todas as alterações realizadas posteriormente (como através de timeouts ou eventos por exemplo) são automaticamente refletidas no módulo que importou e vice-versa.
Ao utilizar imports sem chaves, como em:
import Message from './message.model';
Estamos importando o único elemento exportado através da instrução export default. Neste caso, qualquer valor pode ser exportado. Por exemplo (três exemplos distintos, não podem estar presentes no mesmo arquivo):
export default { esse: 'objeto', foi: 'exportado' };
export default 'essa string também';
export default variavelDeclaradaAnteriormente;
Por ser possível exportar de modo default qualquer valor literal, só é possível have um único export default por arquivo. Do contrário não haveria como saber o que importar na outra ponta.
É possível ainda (re)definir o nome do que foi importado como em:
import { Message as MessageModel } from './message.model';
import { default as MessageModel } from './message.model';
import * as TudoQueVemDeLa from './la/do/outro/lado';
Acima, a primeira linha importou algo chamado Message
em message.model.js
mas resolveu chamar de MessageModel
.
Já a segunda linha importou o export default de message.model.js
não importa qual nome foi utilizado para referenciar o valor a ser exportado, nem mesmo se houve algum. Essa linha é equivalente a (perceba a ausência das chaves nesse caso):
import MessageModel from './message.model';
Já na terceira linha do bloco anterior, TudoQueVemDeLa
colecionará todos os exports do módulo em questão, além do atributo default
referenciando o valor exportado através de export default, caso haja. O comando import *
exige o complemento as algumaCoisa
.
Ao utilizar imports sem informar como queremos tratar o que será importado, como em:
import './criar-listeners';
Não estamos interessados em nada que possa ter sido exportado na outra ponta, mas sim interessados em apenas executar o arquivo em questão. Não é a melhor prática, mas pode servir para importar código legado. Declarações top level do arquivo importado ficarão disponíveis no arquivo corrente.
Desta forma, podemos importar outros formatos como:
import './styles/header.css';
obs: Loaders/bundlers como o Webpack (através do Styles Loader) entendem esse import como instrução para injetar uma tag style no documento de entrada (entry point) e carregar o estilo do arquivo importado.
Após a cláusula from definimos o caminho para um arquivo ou módulo instalado no diretório node_modules. Caso o valor não inicie com algo que denuncie um caminho (como ., .. ou /), em geral bundlers entendem como referência a node_modules.
Ainda é possível importar e exportar ao mesmo tempo:
export { Message as MessageModel } from './message.model';
Perceba a semelhança desse export com relação a um comando import. Desde que você não precise localmente do que está referenciando, é possível repassá-lo para outro módulo.
É possível importar diversas coisas na mesma linha:
import { Message, ImageMessage } from './messages-modules'
import VouChamarODefaultDeMessage, { OutraCoisaNaoDefault as UmNomeQualquer } from './qualquer-lugar';
Essa prática vem se tornando cada vez mais frequente e é bem mais recomendável do que imports com asteriscos porque permitem a seleção específica do que se quer importar. A técnica conhecida como Tree Shaking, introduzida pelo Rollup mas também disponível no Webpack 2 garantem bundles contendo unicamente o que se quer do módulo que foi importado, dispensando todo o resto do conteúdo do arquivo. Isso proporciona um bundle final bem mais enxuto.
A especificação para ES6 modules trata ainda da capacidade de realizar importações dinâmicas. Em geral, elas são menos recomendáveis, uma vez que dificultam a análise estática das dependências (imagine que possam vir dentro de uma função ou desvio condicional, o que acabaria forçando o bundler / loader a executar os cenários possíveis pra decidir se carrega o módulo importado ou não).
De qualquer forma, é possível utilizar a instrução System.import('./caminho/pro/meu/modulo') que retorna uma promise que uma vez o módulo sendo carregado, resolve (then) e recupera os valores exportados.