Rozdělení kódu v Angular nebo jak sdílet komponenty mezi línými moduly

Tento článek vám umožní lépe porozumět tomu, jak Angular rozdělil váš kód na kousky.

Pokud se bojíte výstupu Angular CLI uvedeného výše nebo pokud jste zvědaví, jak se toto rozdělení kódu skutečně děje, tento příspěvek je pro vás.

Rozdělení kódu vám umožní rozdělit váš kód do různých balíčků, které pak lze na požádání načíst. Při správném použití může mít velký vliv na dobu načítání.

Obsah

  1. Proč bych se měl starat?
  2. Úhlové dělení kódu CLI pod kapotou
  3. Jednoduchá úhlová aplikace s línými moduly
  4. Jak sdílet komponenty mezi línými moduly
  5. Závěr

Proč bych se měl starat?

Představme si, že jste zahájili zcela nový projekt Angular. Čtete mnoho zdrojů o tom, jak architektovat aplikaci Angular, jaká je vhodná struktura složek a co je nejdůležitější, jak udržet skvělý výkon při spuštění.

Vybrali jste Angular CLI a vytvořili jste modulární aplikaci se spoustou líných načtených funkčních modulů. A samozřejmě jste vytvořili sdílený modul, do kterého jste vložili běžně používané direktivy, roury a komponenty.

Po chvíli jste se chytili a mysleli si, že jakmile váš nový funkční modul vyžaduje nějakou funkčnost od jiných funkčních modulů, máte tendenci tuto funkci přesouvat do jednoho sdíleného modulu.

Aplikace se vyvíjí a brzy jste si všimli, že její doba spuštění nesplňuje vaše očekávání (a co je nejdůležitější, vašeho klienta).

Nyní máte pochybnosti ...

  • Pokud umístím všechny své kanály, direktivy a společné komponenty do jednoho velkého sdíleného modulu a poté je importuji do lenivých modulů (kde používám pouze jednu nebo dvě importované funkce), může to pravděpodobně ve výstupních souborech způsobit duplikáty nevyužitého kódu. .
  • Na druhé straně, pokud sdílím sdílené funkce mezi několik sdílených modulů a importuji pouze ty z nich, které jsou potřebné v každém konkrétním modulu, sníží se tím velikost mé aplikace? Nebo Angular provádí všechny takové optimalizace ve výchozím nastavení?

Pojďme demystifikovat!

Úhlové dělení kódu CLI pod kapotou

Jak všichni víme, současná verze Angular CLI používá k provádění balíků webpack. Ale i přes to je webpack také zodpovědný za rozdělení kódu.

Pojďme se tedy podívat, jak to webpack dělá.

Webpack 4 představil SplitChunksPlugin, který nám umožňuje definovat nějakou heuristiku pro rozdělení modulů na kousky. Mnoho lidí si stěžuje, že tato konfigurace vypadá tajemně. A zároveň je to nejzajímavější část rozdělení kódu.

Před použitím optimalizace SplitChunksPlugin však webpack vytvoří nový kus:

  • pro každý vstupní bod

Úhlové CLI konfiguruje následující vstupní body

hlavní styly polyfillů

což bude mít za následek kousky se stejnými jmény.

  • pro každý dynamicky načtený modul (pomocí syntaxe import (), která vyhovuje návrhu ECMAScript pro dynamický import)

Pamatujete si zatížení syntaxe dětí? Toto je signál, aby webpack vytvořil kus.

Nyní přejdeme na SplitChunksPlugin. Lze ji povolit v optimalizačním bloku webpack.config.js

Pojďme se podívat na zdrojový kód Angular CLI a najít tuto sekci konfigurace:

Konfigurace SplitChunksPlugin v úhlovém CLI 8

Zaměříme se zde na možnosti cacheGroups, protože toto je „recept“ pro webpack, jak vytvořit oddělené kousky na základě určitých podmínek.

cacheGroups je prostý objekt, kde klíčem je název skupiny. V zásadě můžeme považovat skupinu mezipaměti za potenciální příležitost k vytvoření nového kusu.

Každá skupina má mnoho konfigurací a může zdědit konfiguraci z úrovně splitChunks.

Pojďme se opravdu rychle podívat na možnosti, které jsme viděli v Angular CLI konfiguraci výše:

  • Hodnotu chunks lze použít k filtrování modulů mezi synchronizačními a asynchronními bloky. Jeho hodnota může být počáteční, asynchronní nebo všechna. počáteční znamená pouze přidat soubory do bloku, pokud jsou importovány uvnitř synchronizačních bloků. async znamená přidat soubory do bloku, pokud jsou importovány uvnitř asynchronních bloků (ve výchozím nastavení async)
  • minChunks říká webpacku, aby injektoval moduly pouze v chunku, pokud jsou sdíleny alespoň 2 chunky (ve výchozím nastavení 1)
  • name říká webpacku, aby použil toto jméno pro nově vytvořený kus. Zadáním řetězce nebo funkce, která vždy vrací stejný řetězec, sloučí všechny běžné moduly do jednoho kusu.
  • Hodnota priority se používá k identifikaci nejlépe odpovídajících bloků, když modul spadá do mnoha skupin kusů.
  • vynutit říká webpacku, aby ignoroval možnosti minSize, minChunks, maxAsyncRequests a maxInitialRequests a vždy vytvářel bloky pro tuto skupinu mezipaměti. Je tu jedna malá gotcha: pokud jsou některé z těchto ignorovaných možností k dispozici na úrovni cacheGroup, bude tato volba stále použita.
  • test kontroluje, které moduly jsou vybrány touto skupinou mezipaměti. Jak jsme si mohli všimnout, Angular CLI používá tuto možnost k přesunutí všech závislostí node_modules na kus dodavatele.
  • minSize se používá k identifikaci minimální velikosti v bajtech pro generování bloku. To se neobjevilo v Angular CLI config, ale je to velmi důležitá možnost, o které bychom měli vědět. (Jak uvádí zdrojový kód, ve výrobě je ve výchozím nastavení 30 kB av prostředí dev) 10 kB)
Tip: Přestože dokumentace k webpacku definuje výchozí hodnoty, pro nalezení přesných hodnot bych se odkazoval na zdrojový kód webpacku

Pojďme to shrnout zde: Angular CLI přesune modul do:

  • díl dodavatele, pokud tento modul pochází z adresáře node_modules.
  • výchozí blok, pokud je tento modul importován uvnitř asynchronního modulu a sdílen mezi alespoň dvěma moduly. Všimněte si, že zde existuje mnoho výchozích bloků. Vysvětlím, jak webpack generuje jména pro tyto kousky později.
  • společný blok, pokud je tento modul importován uvnitř asynchronního modulu a sdílen mezi nejméně dvěma moduly a nespadal pod výchozí blok (priorita hello) a také bez ohledu na to, jaká je velikost (díky možnosti theenforce)

Dostatek teorie, pojďme cvičit.

Jednoduchá úhlová aplikace s línými moduly

Abychom vysvětlili proces SplitChunksPlugin, začneme zjednodušenou verzí aplikace Angular:

aplikace ├── a (líný) └── └── a.component.ts │ └── a.module.ts │ ├── ab │ └── ab.component.ts │ └── ab.module.ts │ ├── b (líný) │ └── b.component.ts │ └── b.module.ts │ └── c (líný) │ └── c.component.ts │ └── c.module. ts │ └── cd │ └── cd.component.ts │ └── cd.module.ts │ └── d (líný) │ └── d.component.ts │ └── d.module.ts │ └── sdílený │ └── shared.module.ts │ └── app.component.ts └── app.module.ts

Zde a, b, cad jsou líné moduly, což znamená, že jsou importovány pomocí syntaxe import ().

Komponenty aab používají komponentu ab ve svých šablonách. Komponenty c a d používají komponentu cd.

Závislosti mezi úhlovými moduly

Rozdíl mezi ab.module a cd.module je ten, že ab.module je importován do a.module a b.module, zatímco cd.module je importován do shared.module.

Tato struktura přesně popisuje pochybnosti, které jsme chtěli demystifikovat. Pojďme zjistit, kde budou ab a cd moduly v konečném výstupu.

Algoritmus

1) Algoritmus SplitChunksPlugin začíná tím, že každému dříve vytvořenému kousku vytvoří index.

kusy podle indexu

2) Poté se smyčky přes všechny moduly v kompilaci vyplní chunkSetsInGraph Map. Tento slovník ukazuje, které bloky sdílejí stejný kód.

chunkSetsInGraph

Např. 1,2 hlavní řada polyfill znamená, že existuje alespoň jeden modul, který se zobrazuje ve dvou blocích: main a polyfill.

a a b moduly sdílejí stejný kód fromab-modulu, takže si můžeme také všimnout výše uvedené kombinace (4,5).

3) Projděte všechny moduly a zjistěte, zda je možné vytvořit nový blok pro konkrétní cacheGroup.

3a) Nejprve webpack určí, zda lze modul přidat do konkrétní cacheGroup kontrolou vlastnosti thecacheGroup.test.

ab.modulové testy

default test undefined => ok
společný test nedefinovaný => ok
testovací funkce dodavatele => false

výchozí a běžná skupina mezipaměti nedefinovala nejzávažnější vlastnost, takže by ji měla předat. Skupina mezipaměti dodavatele definuje funkci, kde existuje filtr, který zahrnuje pouze moduly z cesty thenode_modules.

Testy cd.module jsou stejné.

3b) Nyní je čas projít všechny kombinace kusů.

Každý modul chápe, ve kterých částech se objeví (díky vlastnosti module.chunksIterable).

ab.module je importován do dvou líných bloků. Její kombinace jsou (4), (5) a (4,5).

Na druhé straně je cd.module importován pouze v thshared modulu, což znamená, že je importováno pouze v themain chunk. Její kombinace jsou pouze (1).

Poté plugin filtruje kombinace podle velikosti minChunk:

if (chunkCombination.size 

Protože ab.module má kombinaci (4,5), měla by tuto kontrolu projít. Toto nemůžeme říci o cd.module. V tomto okamžiku tento modul zbývá žít uvnitř hlavního kusu.

3c) Existuje ještě jedna kontrola pomocí cacheGroup.chunkds (počáteční, asynchronní nebo všechny)

ab.module je importován do asynchronních (líných načtených) bloků. To je přesně to, co vyžadují výchozí a běžné skupiny mezipaměti. Tímto způsobem bude ab.module přidán do dvou nových možných bloků (výchozí a běžné).

Slíbil jsem to dříve, takže tady jdeme.

Jak webpack generuje jméno pro kus vytvořený SplitChunksPlugin?

Zjednodušená verze toho může být reprezentována jako

kde:

  • groupName je název skupiny (výchozí v našem případě)
  • ~ je defaultAutomaticNameDelimiter
  • chunkNames odkazuje na seznam všech názvů chunků, které jsou součástí této skupiny. Toto jméno je jako cesta fullPath, ale místo lomítka používá -.

Např. Dd-modul znamená, že máme ve složce d soubor d.module.

Když jsme tedy použiliimport ('./ a / a.module') a import ('./ b / b.module'), dostaneme

Struktura výchozího názvu bloku

Ještě jedna věc, která stojí za zmínku, je, že když délka kusu jména dosáhne 109 znaků, webpack jej ořízne a přidá na konec nějaký hash.

Struktura názvu velkého kusu, který sdílí kód napříč více línými moduly

Jsme připraveni vyplnit chunksInfoMap, který ví vše o všech možných nových blocích a také ví, které moduly by se měly skládat a související bloky, na kterých se tyto moduly momentálně nacházejí.

chunksInfoMap

Je čas filtrovat možné kousky

SplitChunksPlugin opakuje položky chunksInfoMap, aby našel ten nejlepší záznam. Co to znamená?

Výchozí skupina mezipaměti má prioritu 10, která převažuje častěji (která má pouze 5). To znamená, že výchozí hodnota je nejvhodnější položka a měla by být zpracována jako první.

Jakmile jsou splněny všechny ostatní požadavky, odstraní webpack všechny moduly chunku z dalších možných kousků ve slovníku chunksInfoMap. Pokud nezůstane žádný modul, bude modul odstraněn

Tímto způsobem má výchozí ~ aa-modul ~ bb-modul přednost před běžným blokem. Ten je odstraněn, protože obsahuje stejný seznam modulů.

V neposlední řadě je třeba provést několik optimalizací (například odstranit duplicitu) a zajistit, aby byly splněny všechny požadavky, jako je maxSize.

Celý zdrojový kód SplitChunksPlugin najdete zde

Zjistili jsme, že webpack vytváří kousky třemi různými způsoby:

  • pro každý záznam
  • pro dynamicky načtené moduly
  • pro sdílený kód pomocí SplitChunksPlugin
Úhlový výstup CLI podle typu bloků

Nyní se vraťme k našim pochybnostem o tom, jaký je nejlepší způsob, jak zachovat sdílený kód.

Jak sdílet komponenty mezi línými moduly

Jak jsme viděli v naší jednoduché Angular aplikaci, webpack vytvořil oddělený kus pro ab.module, ale zahrnul cd.module do hlavního kusu.

Shrňme klíčové cesty z tohoto příspěvku:

  • Pokud umístíme všechny sdílené kanály, direktivy a společné komponenty do jednoho velkého sdíleného modulu a poté jej importujeme všude (uvnitř synchronizačních a asynchronních bloků), bude tento kód v našem počátečním hlavním bloku. Pokud tedy chcete dosáhnout špatného výkonu při počátečním načtení, je to způsob, jak jít.
  • Na druhou stranu, pokud rozdělíme běžně používaný kód na líné načtené moduly, vytvoří se nový sdílený blok a načte se pouze v případě, že se načte některý z těchto líných modulů. To by mělo zlepšit počáteční zatížení aplikace. Ale udělejte to moudře, protože někdy je lepší dát malý kód do jednoho kusu, který má zvláštní požadavek potřebný pro samostatné načtení kusu.

Závěr

Doufám, že byste nyní měli jasně porozumět výstupu Angular CLI a rozlišit mezi vstupem, dynamickým a rozděleným pomocí bloků SplitChunksPlugin.

Šťastný kódování!