Melhore o desempenho do SPA dividindo suas bibliotecas Angular em várias partes

Olá Habr! Apresento a você a tradução do artigo “Melhore o desempenho do SPA dividindo suas bibliotecas Angular em vários pedaços” de Kevin Kreuzer .


Angular é uma ótima estrutura. Todos nós o amamos <3.


Uma das coisas que torna a Angular bem-sucedida e bonita ao mesmo tempo é a ampla comunidade e o valor que ela carrega. Existem muitas reuniões, blogs, conferências e, é claro, bibliotecas Angular.


Graças à CLI Angular, as bibliotecas são fáceis de criar hoje. Eles são ótimos para compartilhar código entre vários aplicativos.


Como eles podem ser usados ​​em muitos lugares, o desempenho é um aspecto crítico. Uma biblioteca com desempenho ruim pode diminuir a velocidade de vários aplicativos!


O frontend possui diferentes tipos de desempenho. tempo de execução - desempenho e carregamento inicial. Neste artigo, focaremos no carregamento inicial.


Ao fornecer e dar suporte a várias bibliotecas e estruturas de interface do usuário para uma grande empresa, deparei-me com algumas armadilhas não tão óbvias e maneiras de corrigi-las. Eu acho que vale a pena compartilhar alguns deles.


"Esta é uma biblioteca tão simples. Não pode afetar o desempenho, certo?"


Vamos começar com uma biblioteca simples que criaremos usando a CLI Angular. Se você nunca criou uma biblioteca Angular, pode ser útil ler o seguinte artigo:


imagem


Assim que usarmos a CLI para configurar grande parte do espaço de trabalho do projeto, podemos começar a adicionar código.


A biblioteca se chama howdy e seu único objetivo é cumprimentá-lo com seu nome ou informar a hora local. Ele contém dois módulos com cada componente. Um modelo dá boas-vindas, o outro fala o tempo.


imagem


Apenas um módulo Angular e Componente comum que pega uma propriedade de nome na parte superior das ligações de Entrada e a exibe.


imagem


O HowdyTimeComponent é responsável por exibir o tempo usando uma biblioteca de momentos de terceiros.


Ótimo! Nossa biblioteca está pronta para publicação! É uma biblioteca tão simples; ela não será capaz de afetar o desempenho, certo?


Howdy consumo de biblioteca


Agora nós temos uma biblioteca do Olá ! Seria uma pena não tirar proveito disso. Para usar a biblioteca howdy , estamos criando um novo SPA com a CLI Angular.


ng new greeting-app 

Como estamos interessados ​​em desempenho, vamos instalar também a dependência do desenvolvedor chamada webpack-bundle-analyzer .


 npm i -D webpack-bundle-analyzer 

O Webpack-bundle-analyzer permite visualizar o tamanho dos arquivos de saída do webpack usando um mapa em árvore escalável e interativo.


A melhor maneira de analisar nosso pacote é adicionar o seguinte script de análise ao nosso package.json .


 "analyze": "ng build --prod --stats-json && webpack-bundle-analyzer ./dist/greeting-app/stats-es2015.json" 

Se executarmos este comando, o Angular fará a construção da produção e também produzirá o stats-es2015.json , que será então selecionado e renderizado pelo webpack-bundle-anlyzer .


imagem


Como ainda não escrevemos nenhum código, nosso pacote principal consiste principalmente em Angular. Também podemos ver que o zone.js está incluído no pacote polyfill .


Em geral, o tamanho do nosso aplicativo agora é 207 KB .


Mas ainda não incluímos nossa biblioteca Howdy! Vamos em frente e fazê-lo.


 npm i howdy 

Instalamos a biblioteca howdy porque queremos hospedar uma saudação com um nome. Não estamos interessados ​​na demonstração do tempo. Portanto, usaremos apenas o módulo HowdyNameModule e não incluiremos HowdyTimeModule .


imagem


É importante observar aqui que importamos apenas HowdyNameModule . Vamos executar o script de análise de análise novamente.


imagem


Uau! Muito legal! Trocamos de 207 KB para 511.15 KB. O tamanho mais que dobrou. Que ...!


Basta um olhar para encontrar o culpado. momento é enorme! Ele traz seu principal código de implementação e todas as configurações regionais.


Obviamente, o momento pode ser substituído por outros pacotes, como date-fns ou moment-mini . Mas a questão é diferente; Por que ele está lá? Lembre-se de que importamos apenas HowdyNameModule , não HodwyTimeModule . Eu pensei que quando ocorre a agitação da árvore , apenas os módulos não utilizados sacudem? O que está havendo?


Tremer árvores pode não remover tudo


Para que a agitação da árvore aconteça, a compilação Angular lança várias otimizações avançadas. Mas ainda assim, o momento está presente no kit, embora o HowdyTimeModule não esteja .
O problema é como o momento é empacotado. Vamos dar uma olhada rápida no arquivo moment.js em nossa pasta node_modules .


imagem


Como o momento pode ser usado em muitos lugares, como back-end do Node JS, aplicativos Angular ou JavaScript comum, ele é empacotado no UMD e não como um módulo ES .


As bibliotecas UMD vinculadas são agrupadas em uma função IFFE, o que significa que ModuleConcatenation não pode ser usado.As ferramentas de otimização de montagem não conseguem descobrir se esse código será usado ou se tem efeitos colaterais.


Em poucas palavras, esse tipo de módulo impede a Angular de lançar um kit de otimização mais avançado.


Infelizmente, não podemos controlar como o momento está completo. Isso significa que temos que aturar o tamanho da embalagem?


Pontos de entrada secundários para a vitória


Não podemos controlar como o momento é criado. Mas podemos gerenciar nossa biblioteca. De fato, existe uma maneira de evitar tais cenários. Pontos de entrada secundários!


Atualmente, quase todas as bibliotecas Angular são empacotadas usando o ng-packagr . O ng-packagr permite que você use o ng-package.json em combinação com a public-api , que eventualmente se tornará o ponto de entrada para o seu aplicativo.


Como o nome indica, pontos de entrada adicionais permitem especificar vários pontos de entrada para o seu aplicativo.


Isso parece bom! Como ativar pontos de entrada secundários?


Pontos de entrada secundários são detectados dinamicamente usando ng-packagr . ng-packagr procura por arquivos package.json nos subdiretórios da pasta principal do arquivo package.json


Legal! Vamos tirar proveito dos pontos de entrada secundários em nossa biblioteca Howdy , adicionando os seguintes arquivos.


imagem


Para cada módulo, adicionamos index.ts , package.json e public_api.ts .


  • index.ts está lá, apenas para apontar para public_api , que é útil durante a importação.
  • public_api exporta todos os módulos e componentes do nosso módulo.
  • O package.json contém configurações ng-packagr específicas. No nosso caso, isso é suficiente para especificar entryFile .

O Package.json também pode conter outras propriedades, como cssUrl , etc. Observe que o escopo dessas propriedades é apenas o subitem atual.

Se executarmos a montagem agora, obteremos três blocos. howdy.js , howdy-src-lib-name.js e howdy-src-lib-time.js .


O Howdy-src-lib-name.js agora contém apenas o código relacionado ao HowdyNameModule e o howdy-src-lib-time.js agora contém apenas o código específico do HowdyTimeModule .


Mas vamos dar uma olhada em howdy.js .


imagem


A parte howdy.js ainda contém HowdyNameComponent e HowdyTimeComponent . Isso significa que ainda temos o momento, mesmo se importarmos apenas o HowdyNameModule .


Se queremos nos livrar do HowdyTimeModule com essa abordagem, precisamos usar a importação profunda. Portanto, estamos importando não do howdy.js , mas diretamente do howdy-src-lib-time.js
O que não é recomendado! Importações profundas são perigosas e sempre devem ser evitadas!

Como podemos resolver esses problemas? Como podemos garantir que o HowdyTimeModule também será removido, mesmo se usarmos importações padrão? Bem, precisamos configurar uma maneira de criar um pedaço do howdy.js .


Use "placa de sinalização"


A idéia é remover o código do bloco howdy.js e permitir que ele atue como uma espécie de “ponteiro” de “orientação” que aponta para outros blocos.


Então, vamos dar uma olhada em src / public_api.ts .


 /* * Public API Surface of howdy */ export * from './lib/name/howdy-name.component'; export * from './lib/name/howdy-name.module'; export * from './lib/time/howdy-time.component'; export * from './lib/time/howdy-time.module'; 

Essas linhas são responsáveis ​​por incluir tudo, desde nome e hora no bloco howdy.js . Precisamos remover o código do howdydy.js e apontar para outros fragmentos que contêm a implementação. Vamos mudar o seu conteúdo.


 / * Public API Surface of howdy */ export * from 'howdy/src/lib/name'; export * from 'howdy/src/lib/time'; 

Em vez de exportar a implementação real, indicamos o caminho relativo para as várias partes. Com essa alteração, o howdy.js aponta apenas para outros pacotes e não contém nenhum código "real".
Vamos executar a compilação e analisar nossa pasta dist .


imagem


O Howdy.js agora atua como um "ponteiro" de orientação que aponta para fragmentos que contêm a implementação. O bloco howdy-src-lib-name.js contém apenas o código da pasta name e o arquivo howdy-src-lib-time.js contém apenas o código da pasta time .


Complete o pacote com substitutos


Vamos atualizar o pacote howdy em nosso aplicativo de boas-vindas e executar novamente o script de análise.


imagem


Legal. O tamanho do pacote agora é 170,94 KB . Um pouco mais alto do que originalmente. Vamos ver como o módulo Howdy se parece no pacote final.


imagem


Ótimo! Esse ajuste nos permite manter o tamanho da embalagem consumindo SPA pequeno. SPA recebe apenas o que eles precisam!


Os pontos de entrada secundários são muito bons quando você os utiliza em conjunto com o carregamento lento. Se usássemos o HowdyTimeModule em um módulo carregado preguiçosamente, o momento terminaria em uma peça carregada preguiçosamente, e não basicamente.

Experiência real


O exemplo acima é muito simples.


No entanto, após a introdução de pontos de entrada secundários no projeto corporativo existente, tudo será diferente. Você precisa lidar com uma complexidade muito maior, enquanto as mensagens de erro do ng-packagr nem sempre são úteis.


Provavelmente, você precisará configurar alguns caminhos de importação ou especificar alguns no seu arquivo tsconfig.json . E você também encontrará módulos de um bloco que usam módulos de outro bloco.


Mas acredite, depois de gerenciar esse fardo, vale a pena.


Em um amplo ambiente corporativo, descobrimos que o tamanho do pacote do recém-criado SPA explodiu depois que incluímos algumas das bibliotecas que fornecemos.


Em algum momento, chegou a 5 MB . Cada SPA recebeu um momento , @swimlane / datatable e outras coisas que ele nem usou. Começamos a nos concentrar na otimização desse tamanho de pacote.


Removemos o momento do date-fns e começamos a usar pontos de entrada secundários. Atualmente, recebemos uma unidade principal de 662 KB para o recém-criado SPA, que inclui várias bibliotecas. Isso ainda é muito, mas ainda não terminamos. A otimização ainda não está completa - podemos reduzir ainda mais o tamanho do pacote.


É muito legal ver de onde estamos e de onde viemos.


No entanto, apesar do exemplo acima ser bastante fácil de usar pontos de entrada adicionais, pode ser bastante difícil imaginá-los em um projeto mais significativo.


Conclusão


Angular faz um trabalho maravilhoso quando se trata de otimizar o tamanho do pacote. Embora as etapas de montagem para otimização sejam muito complexas, elas não podem agitar a árvore.


Módulos compactados em formatos diferentes de ESModules não podem ser agitados em árvore.


Portanto, como criadores de bibliotecas, devemos monitorar cuidadosamente o impacto de nossa biblioteca no tamanho do pacote ao incluir bibliotecas de terceiros.
Não podemos controlar o empacotamento de bibliotecas de terceiros. Mas temos um controle muito bom sobre o empacotamento da nossa biblioteca.


As subpartes nos oferecem uma ótima maneira de entregar nossa biblioteca em várias partes. Essas peças podem ser abaladas (abaladas em árvore) durante a otimização da montagem dos angulares. Com essa abordagem, mesmo as bibliotecas de terceiros empacotadas incorretamente são incluídas no pacote final somente se forem usadas.

Source: https://habr.com/ru/post/pt482646/


All Articles