Dividir el codi en Angular o com compartir components entre mòduls mandrosos

Aquest article us proporcionarà una millor comprensió de com Angular divideix el vostre codi en fragments.

Si teniu por de la sortida de la CLI angular que es mostra més amunt o si teniu curiositat de com es produeix aquesta divisió de codis, aleshores aquesta publicació és per a vosaltres.

La divisió de codi us permet dividir el vostre codi en diversos paquets que després es poden carregar sota demanda. Si s’utilitza correctament, pot tenir un impacte important en el temps de càrrega.

Continguts

  1. Per què m’hauria d’importar?
  2. Divisió de codi CLI angular sota el capó
  3. Aplicació angular senzilla amb mòduls mandrosos
  4. Com compartir components entre mòduls mandrosos
  5. Conclusió

Per què m’hauria d’importar?

Imaginem que vau començar un nou projecte Angular. Llegiu molts recursos sobre com arquitectear una aplicació angular, quina és l’estructura de carpetes adequada i, el que és més important, com mantenir un gran rendiment d’inici.

Heu escollit Angular CLI i heu creat una aplicació modular amb molts mòduls de funcions carregats de mandra. Per descomptat, heu creat un mòdul compartit on poseu directrius, canonades i components d'ús comú.

Al cap d'una estona, us ha semblat pensar que una vegada que el vostre nou mòdul de funcions requereixi alguna funcionalitat d'altres mòduls de funcions, acostumeu a moure aquesta funcionalitat a aquest mòdul compartit.

L’aplicació evoluciona i aviat us heu adonat que el seu temps d’inici no compleix les vostres expectatives (i el més important, el vostre client).

Ara, tens dubtes ...

  • Si poso totes les meves canonades, directives i components comuns en un mòdul compartit gran i després l’importo en mòduls carregats de mandra (on només faig servir una o dues de les funcions importades) probablement pugui provocar duplicats de codi no utilitzats als fitxers de sortida .
  • D'altra banda, si dividiu les funcions compartides entre diversos mòduls compartits i n'importo només les necessàries en tots els mòduls, reduirà la mida de la meva aplicació? O Angular fa de manera predeterminada totes aquestes optimitzacions?

Desmitifiquem!

Divisió de codi CLI angular sota el capó

Com tots sabem, la versió angular CLI actual utilitza webpack per realitzar agrupacions. Però malgrat això, webpack també és responsable de la divisió de codis.

Donem un cop d'ull a com ho fa el webpack.

Webpack 4 va introduir SplitChunksPlugin que ens permet definir algunes heurístiques per dividir mòduls en trossos. Molta gent es queixa que aquesta configuració sembla misteriosa. A la vegada, aquesta és la part més interessant de la divisió de codis.

Però abans d’aplicar-se l’optimització de SplitChunksPlugin, webpack crea un nou bloc:

  • per a cada punt d’entrada

Angular CLI configura els punts d’entrada següents

principals estils de polífills

que tindrà com a resultat els trossos amb els mateixos noms.

  • per a cada mòdul carregat dinàmicament (mitjançant la sintaxi d’importació) que s’ajusta a la proposta ECMAScript per a importacions dinàmiques)

Recordeu la sintaxi de loadChildren? Aquest és el senyal per a webpack per crear un tros.

Passem ara a SplitChunksPlugin. Es pot activar dins del bloc d’optimització de webpack.config.js

Analitzem el codi font CLI angular i trobem aquesta secció de configuració:

Configuració SplitChunksPlugin en CLI angular 8

Ens centrarem aquí en les opcions de cacheGroups, ja que aquesta és la “recepta” de webpack sobre com crear trossos separats basats en algunes condicions.

cacheGroups és un objecte normal on la clau és un nom de grup. Bàsicament, podem pensar en un grup de memòria cau com una oportunitat potencial per a la creació d'un nou fragment.

Cada grup té moltes configuracions i pot heretar la configuració des del nivell splitChunks.

Anem molt de pressa per sobre de les opcions que varem veure a la configuració de CLI Angular anterior:

  • El valor de trossos es pot utilitzar per filtrar mòduls entre fragments de sincronització i async. El seu valor pot ser inicial, asínc o tot. significa que només s’afegeixen fitxers al tros si s’importen dins dels fragments de sincronització. async significa només afegir fitxers al troc si s’importen dins dels fragments d’async (async per defecte)
  • minChunks diu a la webpack que només injecti mòduls en un fragment si es comparteixen entre almenys 2 trossos (1 per defecte)
  • name diu al webpack que el faci servir per utilitzar un fragment recentment creat. Si especifiqueu una cadena o una funció que sempre retorna la mateixa cadena, es fusionaran tots els mòduls comuns en un sol tros.
  • el valor de prioritat s'utilitza per identificar els trossos més adequats quan un mòdul cau en molts grups.
  • enforce diu a webpack que ignori les opcions de minSize, minChunks, maxAsyncRequests i maxInitialRequests i que sempre crei trossos per a aquest grup de memòria cau. Hi ha una petita gotcha aquí: si es proporciona alguna d'aquestes opcions ignorades al nivell del cacheGroup, aquesta opció encara s'utilitzarà.
  • prova de control quins mòduls seleccionen aquest grup de memòria cau. Com podríem notar, Angular CLI utilitza aquesta opció per traslladar totes les dependències del node_modules al troc del venedor.
  • minSize s'utilitza per identificar la mida mínima, en bytes, perquè es generi un fragment. No apareixia a la configuració AngI CLI, però és una opció molt important que hauríem de tenir present. (Com indica el codi font, és de 30kb per defecte en producció i 10kb en un entorn dev)
Consell: malgrat que la documentació de webpack defineix els valors per defecte, em referiria al codi font de la webpack per trobar els valors exactes

Tornem a passar aquí: Angular CLI traslladarà un mòdul a:

  • part del venedor si aquest mòdul prové del directori node_modules.
  • fragment predeterminat si aquest mòdul s'importa dins d'un mòdul async i es comparteix entre almenys dos mòduls. Tingueu en compte que hi ha molts trossos predeterminats. Més endavant explicaré com webpack genera noms per a aquests fragments.
  • tros comú si aquest mòdul s'importa dins d'un mòdul async i es comparteix entre almenys dos mòduls i no entra dins del troc predeterminat (hola prioritat) i també no importa de quina mida sigui (gràcies a l'opció theenforce)

Prou teoria, anem a practicar.

Aplicació angular senzilla amb mòduls mandrosos

Per explicar el procés de SplitChunksPlugin, començarem amb una versió simplificada d’aplicació angular:

app ├── a (mandrosa) │ └── a.component.ts │ └── a.module.ts │ ─── ab │ └── ab.component.ts │ └── ab.module.ts │ ├── b (mandrós) │ └── b.component.ts │ └── b.module.ts │ ─── c (mandrós) │ ─── c.component.ts │ └── c.module. ts │ └── cd │ └── cd.component.ts │ └── cd.module.ts │ ─── d (mandrós) ││─d.component.ts │ └── d.module.ts │ └── compartit │ └── shared.module.ts │ └── app.component.ts └── app.module.ts

Aquí a, b, c i d són mòduls mandrosos, el que significa que s’importen mitjançant la sintaxi d’importació ().

els components a i b utilitzen un component ab a les seves plantilles. els components c i d utilitzen el component de cd.

Dependències entre mòduls angulars

La diferència entre ab.module i cd.module és que ab.module s'importa en a.module i b.module mentre que cd.module s'importa en shared.module.

Aquesta estructura descriu exactament els dubtes que volíem desmitificar. Esbrinem on seran els mòduls ab i cd a la sortida final.

Algoritme

1) L’algoritme de SplitChunksPlugin comença per donar a cada tros un índex creat prèviament.

trossos per índex

2) A continuació, passa a la llista de tots els mòduls de la recopilació per omplir el mapa de chunkSetsInGraph. Aquest diccionari mostra quins fragments comparteixen el mateix codi.

chunkSetsInGraph

Per exemple, 1,2 fila principal, polifillatge, significa que hi ha almenys un mòdul que apareix en dos fragments: principal i polifill.

Els mòduls a i b comparteixen el mateix codi fromab-module, de manera que també podem notar la combinació (4,5) anterior.

3) Camineu per tots els mòduls i esbrineu si és possible crear un tros nou per a un cacheGroup específic.

3a) En primer lloc, webpack determina si es pot afegir un mòdul a cacheGroup específic mitjançant la comprovació de la propietat thecacheGroup.test.

Proves ab.module

prova per defecte indefinida => bé
test comú indefinit => bé
funció de prova del venedor => fals

El grup de memòria cau predeterminat i comú no va definir la propietat test pel que hauria de passar-la. el grup de memòria cau del venedor defineix una funció on hi ha un filtre que només inclou els mòduls de la ruta llavorsode_modules.

Les proves de cd.module són les mateixes.

3b) Ara és hora de caminar per totes les combinacions.

Cada mòdul entén en quins trossos apareix (gràcies a la propietat module.chunksIterable).

ab.module s'importa en dos trossos mandrosos. De manera que les seves combinacions són (4), (5) i (4,5).

D'altra banda, el cd.module només s'importa en el mòdul compartit, és a dir, només s'importa en el tros principal. Les seves combinacions són només (1).

A continuació, el connector filtra les combinacions per mida minChunk:

if (chunkCombination.size 

Com que ab.module té la combinació (4,5) hauria de passar aquest control. Això no ho podem dir sobre cd.module. Arribats a aquest punt, aquest mòdul continua per viure dins de la part principal.

3c) Hi ha un altre control de cacheGroup.chunkds (inicial, async o tot)

ab.module s'importa dins de fragments d'async (carregats de mandra). Això és exactament el que requereixen els grups de memòria cau predeterminats i habituals. D’aquesta manera ab.module s’afegeix a dos nous possibles trossos (per defecte i comú).

Ho vaig prometre abans, així que anem.

Com genera el webpack el nom d’un fragment creat per SplitChunksPlugin?

Es pot representar la versió simplificada com

on:

  • groupName és el nom del grup (per defecte en el nostre cas)
  • ~ és un valor predeterminatAutomaticNameDelimiter
  • chunkNames fa referència a la llista de tots els noms que apareixen inclosos en aquest grup. Aquest nom és com una ruta de ruta completa, però en comptes de fer-ne servir també -.

Per exemple, el mòdul dd vol dir que tenim un fitxer d.module a la carpeta d.

Així, tenint això que utilitzem importació ('./ a / a.module') i importació ('./b / b.module') obtenim

Estructura del nom del troc predeterminat

Una altra cosa que cal destacar és que quan la longitud d’un nom tros arriba a 109 caràcters, la webpack el talla i afegeix una mica de hash al final.

Estructura del gran nom que comparteix codi en diversos mòduls mandrosos

Estem preparats per omplir els fragments de fitxers que coneix tot sobre els possibles fragments nous i també sap en quins mòduls haurien de consistir i els fragments relacionats on es troben actualment.

chunksInfoMap

És el moment de filtrar possibles trossos

SplitChunksPlugin enllaça els articles de ChunksInfoMap per trobar la millor entrada coincident. Què vol dir?

el grup de memòria cau predeterminat té una prioritat 10 que overweightscommon (que només en té 5). Això vol dir que l’entrada predeterminada és l’entrada més adequada i que s’ha de processar primer.

Una vegada complerts tots els altres requisits, el webpack elimina tots els mòduls del fragment d'altres diccions possibles del diccionari chunksInfoMap. Si no hi ha cap mòdul, es suprimeix el mòdul

D’aquesta manera, el mòdul ~ aa-module ~ bb-module té prioritat sobre els fragments habituals. Aquest darrer s'elimina ja que conté la mateixa llista de mòduls.

El darrer pas, però no per això menys important, és fer algunes optimitzacions (com eliminar duplicacions) i assegurar-se que es compleixen tots els requisits, com ara MaxSize.

Podeu trobar el codi font complet de SplitChunksPlugin aquí

Hem descobert que el webpack crea fragments de tres maneres diferents:

  • per a cada entrada
  • per a mòduls carregats dinàmicament
  • per a codi compartit amb l'ajuda de SplitChunksPlugin
Sortida de CLI angular per tipus de trossos

Ara tornem als nostres dubtes sobre quina és la millor manera de mantenir el codi compartit.

Com compartir components entre mòduls mandrosos

Com hem vist en la nostra senzilla aplicació Angular, webpack va crear un fragment separat per a ab.module, però incloïa cd.module en la part principal.

Resumim els principals desviaments d'aquesta publicació:

  • Si posem tots els tubs compartits, directives i components comuns en un mòdul compartit important i després l’importem a tot arreu (dins dels fragments de sincronització i async), aquest codi serà a la nostra part principal inicial. Per tant, si voleu obtenir un mal rendiment de càrrega inicial, cal seguir el camí.
  • D'altra banda, si dividim el codi d'ús comú entre mòduls carregats mandrosos, es crearà un nou bloc compartit i només es carregarà si es carrega algun d'aquests mòduls mandrosos. Això hauria de millorar la càrrega inicial de l'aplicació. Però fes-ho sàviament, ja que de vegades és millor posar un codi petit en un fragment que tenir la sol·licitud addicional necessària per a una càrrega independent.

Conclusió

Espero que ara entengueu clarament la sortida de l’angular CLI i distingiu entre entrada, dinàmica i dividida mitjançant els fragments SplitChunksPlugin.

Codificació feliç!