Jednoduchá chyba, která zničila optimalizaci balíčku Webpack

Úvod

Práce na velkých projektech přináší mnoho obtížných výzev, jednou z nich je udržení velikosti balíku aplikací pod kontrolou. Když projekt roste, nevyhnutelně začnete oddělovat velké části funkcí do samostatných modulů nebo podaplikací a delegovat vývoj na jiné týmy nebo někdy dokonce jiné společnosti. Zanedlouho máte obrovskou aplikaci, desítky týmů vytvářejících stovky modulů, které je třeba všechny zabalit, sbalit a odeslat uživateli.

Kontrola velikosti balíčku se v tomto okamžiku stává kritickým, jeden modul, jedno špatné jablko , může prostě všechno zničit. Naštěstí webpack pod kapotou provádí spoustu optimalizací, aby bylo zajištěno, že odešlete minimální kód, jak je požadováno. Nicméně, a byl jsem toho svědkem znovu a znovu, stále existuje jedna jednoduchá chyba, kterou můžete udělat a která zabrání tomu, aby webpack fungoval jeho kouzlo. Pojďme si o tom promluvit.

TL;DR

V tuto chvíli všichni víme, že webpack „třese stromem“, aby optimalizoval velikost balíčku. Jen pro případ, "tree shaking" je termín běžně používaný v kontextu JavaScriptu pro odstranění mrtvého kódu, nebo jednoduše řečeno - exportovaný kód, který nebyl importován a spuštěn, bude detekován jako nepoužitý , takže jej lze bezpečně odstranit a zmenšit tak velikost balíčku.

Co možná nevíte, není to webový balíček, který sám o sobě odstraňuje mrtvý kód. Samozřejmě, že dělá většinu "přípravné" práce, ale je to stručný balíček, který ve skutečnosti *odřízne * nepoužitý kód. Terser je JavaScript parser, Mangler a sada nástrojů pro ES6+.

Pojďme si to vyjasnit – webpack vezme vaše moduly, zřetězí je do kousků a převede je do tersera pro minifikaci (všechno se samozřejmě stane, pouze pokud je povolena optimalizace).

Je čas zdůraznit několik klíčových bodů:

  • Ve výchozím nastavení se webpack vždy pokusí zřetězit váš kód z různých modulů (souborů) do jednoho oboru a později z něj vytvořit blok. Např. moduleA.js importuje několik metod z moduleB.js skončí jako chunk-[hash].js obsahující kód z obou výše uvedených souborů v rámci jednoho rozsahu, jako by byl napsán v jednom souboru (v podstatě odstranění konceptu "modulu" ). Když to však nelze zřetězit, webpack tyto soubory zaregistruje jako moduly, takže k nim bude možné přistupovat globálně prostřednictvím interního pomocníka webpack_require později.
  • Ve výchozím nastavení terser neořezává globální odkazy ve vašem kódu (příznak topLevel je false). Např. vytvoříte nějakou knihovnu s globálním rozsahem API, nechcete, aby byla během minifikace odstraněna. V podstatě bude odstraněn pouze poněkud "zjevně" mrtvý (nedosažitelný) kód nebo nepoužitý v blízkém rozsahu kódu.

Pravděpodobně jste to viděli, jak se to blíží - terser umí odstraňte své nepoužívané exporty pouze v případě, že je webpack upravoval tak, aby bylo možné nepoužité deklarace snadno detekovat.

Pro optimalizaci webpack silně spoléhá na statickou strukturu syntaxe modulu ES2015, tj. import a export klíčových slov, a stejně jako nyní nefunguje pro jiné typy modulů. Můžeme to vidět sami od zdroje.

Jak vidíte, zaneřádění rozhraní modulů brání ModuleConcatenationPlugin (plugin pro optimalizaci) dělat svou práci.

Všichni milujeme a používáme babel k transpilaci moderní syntaxe ES v našich modulech, ale v této situaci se babel-preset-env stává naším špatným přítelem - ve výchozím nastavení jsou moduly transpilovány do standardu "commonjs" a to je přesně to, co my neděláme. chcete při spojování více balíků do jedné aplikace! Musíme se ujistit, že jsme nastavili moduly:false v přednastavené konfiguraci. Webpack dokáže většinu svých optimalizací provádět pouze pro moduly Harmony!

No, technicky to samozřejmě není tak jednoduché. Webpack na své straně provádí spoustu zpracování, aby vytvořil zřetězený kód, na své straně také sleduje poskytnuté a použité exporty, ještě předtím, než zavolá terser, takže "kombinovaný" kód se všemi moduly je pro terser stále platný . Ale ještě jednou - nebude to fungovat pro nic jiného než statickou syntaxi modulu ES.

Pod kapotou

Pod kapotou probíhá poměrně složitý proces, počínaje předáním webpack.config.js kompilátoru a před vygenerováním balíčku. Mírně se dotkneme částí, které jsou pro naši diskusi zajímavé.

Fáze kompilace je místo, kde se odehrává veškerá zábava, níže vidíte její hlavní kroky.

Nakonec během kompilace webpack vytvoří graf závislosti pro vstupní bod zadaný ve vašem webpack.config.js (nebo několik z nich, pokud konfigurace určuje více vstupních bodů).

(0) Začátek vstupního modulu (Compilation.js#1033)
(1) Sestavení modulu (Compilation.js#1111)
(2) Po sestavení závislostí modulu procesu (Compilation.js#1095)
(3) Přidejte do modulu závislosti (Compilation.js#843)

Chcete-li sestavit modul znamená generovat AST při extrahování všech potřebných informací (export-y, import-y atd.). Webpack se při vytváření a zpracování AST spoléhá na acorn.Parser (z žaludu).

Následuje fáze optimalizace.

FlagDependencyUsagePlugin se zahákne do fáze kompilace a identifikuje použité exporty. V podstatě jde o to najít, co „moduleA“ importuje z „moduluB“, nastavit jeho použitý export. Tento proces vyžaduje hodně rekurzivního procházení a „počítání referencí“.

Jak víte, webpack má řadu pluginů pracujících na událostech, pokud se chcete dozvědět více, podívejte se na můj další příspěvek Tapable library jako jádro architektury webpacku.

FlagDependencyUsagePlugin.js se řídí tím, co HarmonyImportDependencyParserPlugin.js zjistil o používání závislostí.

(1) Jednou importSpecifier je detekována, proměnná bude označena jako „importovaná var“ pro další sledování
(2) Poslouchejte hovory (prvek AST method call), tj. webpack je chytrý, importovaná metoda nemusí znamenat, že je použita, ale musí se ujistit, že je také volána
(3) Zjištěna volaná importovaná metoda a uložena jako závislost (později bude uvnitř usedExports pro importovaný modul)

Ještě jednou, aby to fungovalo, import-s/export-s by měly zůstat v balíčku (netranspilovány).

Zajímavé nálezy

Ve zdrojovém kódu webpacku je příliš mnoho zajímavých věcí, které by měly být zmíněny. Pravděpodobně to chce samostatný příspěvek.

Vyzdvihnu jen některé z nich.

Pamatujete si tuto chybu při prvním spuštění webpacku, ale zapomněli jste nainstalovat balíček webpack-cli? Nejsou to peerDependencies, takže webpack poskytuje uživatelům docela užitečný návod, jak to vyřešit.

Další poměrně velké překvapení, kolik nezávislých balíčků-závislostí webpack má. Doslova za všechno:

1) Tapable balíček pro architekturu řízenou událostmi
2) terser pro minifikaci
3) žalud pro zpracování AST
4) watchpack pro sledování změn souborů

To je samozřejmě velmi pěkné, a proto je lze všechny znovu použít pro různé účely v jiných nástrojích!