Há dois anos, escrevi sobre uma
técnica que agora é comumente chamada de padrão módulo / nomódulo. Seu aplicativo permite escrever código JavaScript usando os recursos do ES2015 + e, em seguida, usar bundlers e transpilers para criar duas versões da base de código. Um deles contém sintaxe moderna (é carregado usando uma estrutura como
<script type="module">
e o segundo é a sintaxe ES5 (carregado com
<script nomodule>
). O padrão module / nomodule permite enviar para navegadores que suportam módulos, muito menos código do que os navegadores que não suportam esse recurso, agora esse padrão é suportado pela maioria das estruturas da Web e ferramentas de linha de comando.

Anteriormente, mesmo considerando a capacidade de enviar código JavaScript moderno para produção, e mesmo que a maioria dos navegadores suportasse módulos, eu recomendei a coleta de código em pacotes configuráveis.
Porque Principalmente porque tive a sensação de que o carregamento dos módulos no navegador era lento. Embora protocolos recentes, como o HTTP / 2, teoricamente suportassem o carregamento eficiente de vários arquivos, todos os estudos de desempenho da época concluíram que o uso de
bundlers ainda é mais eficiente do que o uso de módulos.
Mas deve-se admitir que esses estudos estavam incompletos. Os casos de teste usando os módulos estudados consistiram em arquivos de código-fonte não otimizados e não minimizados que foram implementados na produção. Não houve comparações do pacote otimizado com os módulos com o script clássico otimizado.
No entanto, para ser sincero, não havia uma maneira ideal de implantar os módulos naquele momento. Mas agora, graças a algumas melhorias modernas nas tecnologias de empacotador, é possível implantar o código de produção na forma de módulos ES2015 usando comandos de importação estáticos e dinâmicos e, ao mesmo tempo, obter um desempenho superior ao obtido com as opções disponíveis, nas quais módulos não são usados.
Note-se que no
site em que o material original é publicado, a primeira parte da tradução que publicamos hoje, os módulos são utilizados na produção há vários meses.
Equívocos sobre módulos
Muitas pessoas com quem conversei rejeitam completamente os módulos, nem mesmo os consideram uma das opções para aplicativos de produção em larga escala. Muitos deles citam o próprio
estudo que eu já mencionei. Nomeadamente, a parte dele, que afirma que os módulos não devem ser usados na produção, a menos que seja uma questão de “pequenas aplicações web, que incluem menos de 100 módulos que diferem em uma árvore de dependência relativamente“ pequena ”(ou seja, - aquele cuja profundidade não exceda 5 níveis). ”
Se você já examinou o diretório
node_modules
de qualquer um de seus projetos, provavelmente sabe que mesmo um aplicativo pequeno pode facilmente ter mais de 100 módulos de dependência. Quero oferecer uma olhada em quantos módulos estão disponíveis em alguns dos pacotes npm mais populares.
É aqui que o principal equívoco referente aos módulos está enraizado. Os programadores acreditam que, quando se trata de usar módulos na produção, eles têm apenas duas opções. A primeira é implantar todo o código-fonte em seu formato existente (incluindo o diretório
node_modules
). O segundo é não usar módulos.
No entanto, se você examinar atentamente as recomendações do estudo citado acima, descobrirá que não há nada a dizer que o carregamento de módulos é mais lento do que o carregamento de scripts regulares. Não diz que os módulos não devem ser utilizados. Ele apenas fala sobre o fato de que se alguém implantar centenas de arquivos de módulo não infectados na produção, o Chrome não poderá carregá-los tão rapidamente quanto um único pacote compactado. Como resultado, o estudo recomenda continuar usando bundlers, compiladores e minificadores.
Mas você sabe o que? O fato é que você pode usar tudo isso e usar módulos na produção.
De fato, os módulos são um formato no qual devemos nos esforçar para converter, já que os navegadores já sabem como carregar módulos (e os navegadores que não podem fazer isso podem carregar uma versão sobressalente do código usando o mecanismo de nomodule). Se você observar o código que os empacotadores mais populares geram, encontrará muitos fragmentos de modelo cujo objetivo é apenas carregar dinamicamente outro código e gerenciar dependências. Mas tudo isso não será necessário se apenas usarmos os módulos e expressões
import
e
export
.
Felizmente, pelo menos um dos empacotadores modernos populares (
Rollup ) suporta módulos na forma de
dados de
saída . Isso significa que você pode processar o código com um bundler e implantar módulos na produção (sem usar fragmentos de modelo para carregar o código). E, como o Rollup possui uma excelente implementação do algoritmo de agitação de árvore (o melhor que já vi nos empacotadores), a criação de programas na forma de módulos usando o Rollup permite obter um código menor que o tamanho do mesmo código obtido ao aplicar outros mecanismos disponíveis hoje.
Note-se que eles
planejam adicionar suporte para módulos na próxima versão do Parcel. O Webpack ainda não suporta módulos como um formato de saída, mas
aqui está - discussões que focam nessa questão.
Outro equívoco em relação aos módulos é que algumas pessoas acreditam que os módulos só podem ser usados se 100% das dependências do projeto usarem módulos. Infelizmente (acho que é um grande arrependimento), a maioria dos pacotes npm ainda está sendo preparada para publicação usando o formato CommonJS (alguns módulos, mesmo aqueles escritos usando os recursos do ES2015, são traduzidos para o formato CommonJS antes de serem publicados no npm)!
Aqui, novamente, quero observar que o Rollup possui um plug-in (
rollup-plugin-commonjs ) que pega o código-fonte de entrada gravado usando o CommonJS e o converte no código ES2015. Definitivamente, seria
melhor se o formato de dependência usado desde o início usasse o formato de módulo ES2015. Porém, se algumas dependências não forem assim, isso não impedirá que você implante projetos usando módulos em produção.
Nas partes seguintes deste artigo, mostrarei como coleciono projetos em pacotes configuráveis que usam módulos (incluindo o uso de importações dinâmicas e separação de código), discutirei por que essas soluções geralmente são mais produtivas que os scripts clássicos e mostramos como elas funcionam. com navegadores que não suportam módulos.
Estratégia de criação de código ideal
O código de construção da produção é sempre uma tentativa de equilibrar os prós e os contras de várias soluções. Por um lado, o desenvolvedor deseja que seu código seja carregado e executado o mais rápido possível. Por outro lado, ele não deseja baixar o código que não será usado pelos usuários do projeto.
Além disso, os desenvolvedores precisam ter certeza de que seu código é mais adequado para armazenamento em cache. O grande problema do pacote de códigos é que qualquer alteração no código, mesmo uma linha alterada, leva à invalidação do cache de todo o pacote. Se você implantar um aplicativo que consiste em milhares de pequenos módulos (apresentados exatamente na forma em que estão presentes no código-fonte), poderá fazer pequenas alterações no código com segurança e ao mesmo tempo saber que a maior parte do código do aplicativo será armazenada em cache . Mas, como eu já disse, essa abordagem ao desenvolvimento provavelmente pode significar que o carregamento do código na primeira visita ao recurso pode demorar mais do que quando se usa abordagens mais tradicionais.
Como resultado, enfrentamos uma tarefa difícil, que é encontrar a abordagem correta para dividir os pacotes em partes. Precisamos encontrar o equilíbrio certo entre a velocidade de carregamento dos materiais e o armazenamento em cache a longo prazo.
A maioria dos empacotadores, por padrão, usa técnicas de divisão de código com base em comandos de importação dinâmica. Mas eu diria que dividir o código apenas com foco na importação dinâmica não permite dividi-lo em fragmentos suficientemente pequenos. Isso é especialmente verdadeiro para sites com muitos usuários recorrentes (ou seja, em situações em que o cache é importante).
Eu acredito que o código deve ser dividido em fragmentos tão pequenos quanto possível. Vale a pena reduzir o tamanho dos fragmentos até o número deles aumentar tanto que isso afetará a velocidade de download do projeto. E embora eu recomendo definitivamente que todos realizem sua própria análise da situação, se você acredita nos cálculos aproximados feitos no estudo que mencionei, ao carregar menos de 100 módulos, não há uma desaceleração perceptível no carregamento. Um estudo separado sobre o
desempenho do HTTP / 2 não revelou uma desaceleração perceptível no projeto ao baixar menos de 50 arquivos. No entanto, testamos apenas as opções em que o número de arquivos era 1, 6, 50 e 1000. Como resultado, provavelmente 100 arquivos são o valor que você pode navegar facilmente sem medo de perder a velocidade de download.
Então, qual é a melhor maneira de dividir agressivamente, mas não muito agressivamente, o código em partes? Além da separação de código com base nos comandos de importação dinâmica, aconselho que você dê uma olhada mais de perto na separação de código pelos pacotes npm. Com essa abordagem, o que é importado para o projeto da pasta
node_modules
cai em um fragmento separado do código finalizado com base no nome do pacote.
Separação de Pacotes
Eu disse acima que alguns dos recursos modernos dos empacotadores tornam possível organizar um esquema de alto desempenho para a implantação de projetos baseados em módulos. O que eu estava falando é representado por dois novos recursos de Rollup. O primeiro é
a separação automática de código através dos comandos
import()
dinâmicos (adicionados
na v1.0.0 ). A segunda opção é
a separação manual de código realizada pelo programa com base na opção
manualChunks
(adicionada na
v1.11.0 ).
Graças a esses dois recursos, agora é muito fácil configurar o processo de compilação, no qual o código é dividido no nível do pacote.
Aqui está um exemplo de configuração que usa a opção
manualChunks
, graças à qual cada módulo importado de
node_modules
cai em um fragmento de código separado cujo nome corresponde ao nome do pacote (tecnicamente, o nome do diretório do pacote na pasta
node_modules
):
export default { input: { main: 'src/main.mjs', }, output: { dir: 'build', format: 'esm', entryFileNames: '[name].[hash].mjs', }, manualChunks(id) { if (id.includes('node_modules')) {
A opção
manualChunk
aceita uma função que aceita, como argumento único, o caminho para o arquivo do módulo. Esta função pode retornar um nome de string. O que ele retornará apontará para um fragmento da montagem ao qual o módulo atual deve ser adicionado. Se a função não retornar nada, o módulo será adicionado ao fragmento padrão.
Considere um aplicativo que importe os
cloneDeep()
,
debounce()
e
find()
do pacote
lodash-es
. Se você aplicar a configuração acima ao criar este aplicativo, cada um desses módulos (assim como cada módulo
lodash
importado por esses módulos) será colocado em um único arquivo de saída com um nome como
npm.lodash-es.XXXX.mjs
(aqui
XXXX
é exclusivo hash do arquivo de módulo no fragmento
lodash-es
).
No final do arquivo, você verá uma expressão de exportação como a seguinte. Observe que esta expressão contém apenas comandos de exportação para os módulos adicionados ao fragmento, e nem todos os módulos
lodash
.
export {cloneDeep, debounce, find};
Então, se o código em qualquer um dos outros fragmentos usar esses módulos
lodash
(talvez apenas o método
debounce()
), nesses fragmentos, em sua parte superior, haverá uma expressão de importação com a seguinte aparência:
import {debounce} from './npm.lodash.XXXX.mjs';
Esperamos que este exemplo tenha esclarecido a questão de como a separação manual de códigos funciona no pacote cumulativo. Além disso, acho que os resultados da separação de código usando as expressões de
import
e
export
são muito mais fáceis de ler e entender do que o código de fragmentos, cuja formação utilizou mecanismos não padrão que são usados apenas em um determinado empacotador.
Por exemplo, é muito difícil descobrir o que está acontecendo no próximo arquivo. Esta é a saída de um dos meus projetos antigos que usavam o webpack para dividir o código. Quase tudo neste código não é necessário em navegadores que suportam módulos.
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["import1"],{ "tLzr": (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); __webpack_require__.d(__webpack_exports__, "import1", function() { return import1; }); var _dep_1__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( "6xPP"); const import1 = "imported: " + _dep_1__WEBPACK_IMPORTED_MODULE_0__["dep1"]; }) }]);
E se houver centenas de dependências npm?
Como eu já disse, acredito que a separação no nível do código no nível do pacote geralmente permite ao desenvolvedor entrar em uma posição favorável quando a separação do código é realizada de forma agressiva, mas não muito agressiva.
Obviamente, se o seu aplicativo importar módulos de centenas de pacotes npm diferentes, você ainda poderá estar em uma situação em que o navegador não poderá carregá-los com eficiência.
No entanto, se você realmente possui muitas dependências npm, não deve abandonar completamente essa estratégia por enquanto. Lembre-se de que você provavelmente não baixará todas as dependências npm em todas as páginas. Portanto, é importante descobrir quantas dependências realmente carregam.
No entanto, estou certo de que existem alguns aplicativos reais que possuem tantas dependências npm que essas dependências simplesmente não podem ser representadas como fragmentos separados. Se o seu projeto é exatamente isso - eu recomendaria que você procure uma maneira de agrupar pacotes onde o código no qual com alta probabilidade possa mudar ao mesmo tempo (como
react
e
react-dom
), pois a
react-dom
cache de fragmentos com esses pacotes também será executada ao mesmo tempo Posteriormente, mostrarei um exemplo no qual todas as dependências do React estão agrupadas no mesmo
fragmento .
Para continuar ...
Caros leitores! Como você aborda o problema da separação de código em seus projetos?
