
Módulos ES6, Node.js e a solução Michael Jackson
O JavaScript nunca teve uma maneira padrão de importar e exportar funcionalidades de um arquivo de origem para outro. Bem, tem: variáveis globais. Por exemplo:
<script src="https://code.jquery.com/jquery-1.12.0.min.js"></script><script>
// `$` variable available here
</script>Isso está longe de ser ideal por algumas razões:
- Você pode ter conflitos com outras bibliotecas usando os mesmos nomes de variáveis. É por isso que muitas bibliotecas possuem um método noConflict () .
- Não é possível fazer referências cíclicas corretamente . Se um módulo A depende de um módulo B e vice-versa, em qual ordem colocamos as
<script>tags? - Mesmo que não haja referências cíclicas, a ordem na qual você coloca as
<script>tags é importante e difícil de manter .
CommonJS para o resgate
Quando Node.js e outras soluções de JavaScript do lado do servidor começaram a aparecer, eles concordaram em uma maneira de corrigir esse problema. Eles criaram uma especificação mais ampla chamada CommonJS . Em relação ao problema de importação / exportação, esta especificação define uma require()função que é injetada pelo tempo de execução e uma exportsvariável para exportar a funcionalidade.
Nota: CommonJS não é a única especificação. Existem outros como UMD que, de fato, podem ser usados em ambos: frontend e backend.
À medida que passava o tempo, havia uma explosão de ferramentas, especialmente para criar aplicativos de uma única página. Com bases de código maiores no frontend e a necessidade de compartilhar o código entre frontend e backend, muitas ferramentas como o browser e o webpackcomeçaram a implementar e entender a especificação CJS como uma maneira de ignorar as limitações da plataforma: a falta de um bom sistema de módulos em A plataforma subjacente (JavaScript e os navegadores).
Isso é claramente um hack porque o navegador não implementa require()ou o exportsque essas ferramentas fazem é implementar esta funcionalidade, empacotando todo o código em conjunto. Leia mais sobrecomo os pacotes de JavaScript funcionam .
Como os módulos ES6 funcionam e por que o Node.js ainda não o implementou
O JavaScript está evoluindo muito, especialmente com ES6, e esse problema teve que ser resolvido. É por isso que os módulos ES nasceram. Eles se parecem muito com o CJS de forma sintaxe.
Vamos compará-los. É assim que importamos em ambos os sistemas:
const { helloWorld } = require('./b.js') // CommonJS
import { helloWorld } from './b.js' // ES modulesÉ assim que exportamos a funcionalidade:
// CommonJS
exports.helloWorld = () => {
console.log('hello world')
}// ES modules
export function helloWorld () {
console.log('hello world')
}
Muito parecido, certo?
Faz muito tempo que o Node.js implementou 99% do ECMAScript 2015 (também conhecido como ES6), mas precisamos esperar até o final de 2017 para suporte para módulos ES6 . E estará disponível apenas por trás de uma bandeira de tempo de execução! Por que demora tanto para implementar módulos ES6 no Node.js se eles são tão parecidos com o CJS?
Bem, o diabo está nos detalhes. A sintaxe é bastante semelhante entre ambos os sistemas, mas a semântica é bastante diferente . Existem também casos de bordas sutis que exigem um esforço especial para serem 100% compatíveis com as especificações.
Mesmo que os módulos ES não sejam implementados em Node.js, eles já estão implementados em alguns navegadores. Por exemplo, podemos testá-los no Safari 10.1. Vamos ver alguns exemplos e veremos por que a semântica é tão importante. Criei estes três arquivos:
// index.html
<script type="module" src="./a.js"></script>// a.js
console.log('executing a.js')
import { helloWorld } from './b.js'
helloWorld()// b.js
console.log('executing b.js')
export function helloWorld () {
console.log('hello world')
}
O que vemos no console quando isso é executado? Este é o resultado:
executing b.js
executing a.js
hello worldNo entanto, o mesmo código usando CJS e executando-o em Node.js:
// a.js
console.log('executing a.js')
import { helloWorld } from './b.js'
helloWorld()// b.js
console.log('executing b.js')
export function helloWorld () {
console.log('hello world')
}
Nos dará:
executing a.js
executing b.js
hello worldEntão … executou o código em uma ordem diferente! Isso ocorre porque os módulos ES6 são primeiro analisados (sem serem executados), então o tempo de execução procura importações, carrega-os e, finalmente, executa o código. Isso é chamado de carregamento assíncrono .
Por outro lado, o Node.js carrega as dependências (requer) sob demanda durante a execução do código . O que é muito diferente. Em muitos casos, isso pode não fazer diferença, mas em outros casos é um comportamento completamente diferente.
Node.js e navegadores da web precisam implementar esta nova maneira de carregar o código mantendo o anterior. Como eles sabem quando usar um sistema e quando o outro? Os navegadores sabem disso porque você o especifica no <script>nível, como vimos no exemplo com a typepropriedade:
<script type="module" src="./a.js"></script>No entanto, como o Node.js conhece? Houve muita discussão sobre isso e tem havido muitas propostas (verificando primeiro a sintaxe e depois decidir se deve ou não ser tratado como um módulo, definindo-o no arquivo package.json, …) . Finalmente, a proposta aprovada foi: a solução Michael Jackson. Basicamente, se você deseja que um arquivo seja carregado como um módulo ES6, você usará uma extensão diferente: .mjs em vez de .js.
O nome da extensão (.mjs) é a razão pela qual isso às vezes é apelidado de Solução Michael Jackson.
No início, pareceu-me uma decisão muito ruim, mas agora acho que é a melhor solução, porque é fácil e qualquer ferramenta (editor de texto, IDE, pré-processador) saberá a maneira mais fácil possível se um arquivo precisar ser tratado como um ES6 ou não. E acrescenta apenas as despesas gerais mínimas possíveis ao processo de carregamento.
Se você quiser saber mais sobre o status de implementação dos módulos ES6 no Node.js, você deve ler esta atualização .
Uma nota sobre Babel
Babel implementa módulos ES6, mas … incorretamente. Não implementa as especificações completas. Portanto, cuidado com isso se você estiver usando o Babel ao mudar para uma implementação de módulos ES6 nativos, você pode ter efeitos colaterais.
Por que os módulos ES6 são bons e como obter o melhor dos dois mundos
Os módulos ES6 são excelentes por dois motivos principais:
- Eles são um padrão de plataforma cruzada. Eles trabalharão em Node.js e navegadores da web.
- As importações e exportações são estáticas. Tem que ser assim por causa do funcionamento do processo de carregamento. Lembre-se de que dissemos que o tempo de execução primeiro carrega o arquivo, analisa e, antes de executá-lo, ele carrega as dependências? Isso só é possível se as importações e as exportações forem estáticas. Você não pode fazer
import 'engine-' + browserVersionIsso é bom por um motivo: as ferramentas podem fazer análise estática do código, descobrir qual código está realmente sendo usado e a árvore o agita . Isso é especialmente útil ao usar bibliotecas de terceiros: você nunca usa todas as funcionalidades que elas fornecem, para que você possa remover muitos bytes de código que o usuário nunca executará.
Mas, isso significa que eu não posso mais importar funcionalidade dinamicamente? Para mim, isso é muito útil. Muitas vezes faço coisas como:
Const provider = process.env.EMAIL_PROVIDER
const emailClient = require (`./email-providers / $ {provider}`)Desta forma, recebo uma implementação diferente com a mesma interface apenas com uma mudança de configuração, sem ter que carregar o código de todas as implementações.
Então, o que acontece com os módulos ES6? Bem, não se preocupe, há uma proposta de stage 3 (o que significa que provavelmente será aprovado em breve) que adiciona uma import()function . Esta função aceita um caminho e retorna a funcionalidade exportada como uma promessa.
Então, com os módulos ES6 e import(), obteremos o melhor dos dois mundos. 🚀
Os módulos ES6 são ótimos, mas levará algum tempo para adotá-los. Espero que toda essa informação ajude você a estar preparado para quando chegar esse momento!
Créditos
ES6 modules, Node.js and the Michael Jackson Solution, escrito originalmente por Alberto Gimeno
