Articles

třes stromů

třes stromů je termín běžně používaný v kontextu JavaScriptu pro eliminaci mrtvého kódu. Spoléhá se na statickou strukturu syntaxe modulu ES2015, tj. import a export. Název a koncept byly popularizovány kumulativním balíčkem modulů ES2015.

vydání webpack 2 přišlo s vestavěnou podporou modulů ES2015 (alias harmony modules) a také s nepoužívanou detekcí exportu modulů. Nový webpack 4 vydání rozšiřuje tuto schopnost s způsob, jak poskytovat rady kompilátor pomocí "sideEffects"package.json majetek k označení, které soubory v projektu jsou „čisté“, a proto bezpečné prořezávat, pokud se nepoužívá.

Přidat Nástroj

Pojďme přidat nové utility soubor do našeho projektu, src/math.js, který exportuje dvě funkce:

projekt

webpack-demo|- package.json|- webpack.config.js|- /dist |- bundle.js |- index.html|- /src |- index.js+ |- math.js|- /node_modules

src/math.js

export function square(x) { return x * x;}export function cube(x) { return x * x * x;}

Nastavte mode možnost konfigurace na rozvoj ujistěte se, že svazek není minified:

webpack.konfigurace.js

const path = require('path');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), },+ mode: 'development',+ optimization: {+ usedExports: true,+ },};

S tím, že v místě, pojďme aktualizovat náš vstup skriptu využít jeden z těchto nových metod a odstranit lodash pro jednoduchost:

src/index.js

- import _ from 'lodash';+ import { cube } from './math.js'; function component() {- const element = document.createElement('div');+ const element = document.createElement('pre');- // Lodash, now imported by this script- element.innerHTML = _.join(, ' ');+ element.innerHTML = .join('\n\n'); return element; } document.body.appendChild(component());

Všimněte si, že jsme neměli importsquare metoda src/math.js modul. Tato funkce je známá jako „mrtvý kód“, což znamená nepoužitý export, který by měl být zrušen. Nyní spusťte náš skript npm, npm run build a zkontrolujte výstupní svazek:

Dist / bundle.js (around lines 90 – 100)

/* 1 *//***/ (function (module, __webpack_exports__, __webpack_require__) { 'use strict'; /* unused harmony export square */ /* harmony export (immutable) */ __webpack_exports__ = cube; function square(x) { return x * x; } function cube(x) { return x * x * x; }});

poznamenejte si unused harmony export square komentář výše. Pokud se podíváte na kód pod ním, zjistíte, že square není importován, je však stále součástí balíčku. Opravíme to v další části.

označte soubor jako bez vedlejších účinků

ve světě 100% ESM modulů je identifikace vedlejších účinků přímočará. Nicméně, ještě tam nejsme, takže mezitím je nutné poskytnout rady překladači webpacku o „čistotě“ vašeho kódu.

způsob, jak toho dosáhnout ,je"sideEffects" balíček.json property.

{ "name": "your-project", "sideEffects": false}

kód výše uvedeno, neobsahuje vedlejší účinky, takže můžeme jednoduše označit majetek jako false informovat webpack, které je možné bezpečně prořezávat nepoužité vývozu.

Pokud má váš kód nějaké vedlejší účinky, může být místo toho poskytnuto pole:

{ "name": "your-project", "sideEffects": }

pole přijímá jednoduché vzory glob do příslušných souborů. Používá glob–regexp pod kapotou (Podporuje: ***{a,b}). Vzory jako *.css, které neobsahují /, bude zacházeno jako **/*.css.

{ "name": "your-project", "sideEffects": }

konečně, "sideEffects" lze také nastavit z možnosti konfigurace module.rules.

Vyjasnění strom třese a vedlejší účinky

sideEffectsusedExports (více známý jako strom třese) optimalizace jsou dvě různé věci.

sideEffects je mnohem efektivnější, protože umožňuje přeskočit celé moduly/soubory a celý podstrom.

usedExports spoléhá na terser k detekci vedlejších účinků ve výkazech. Je to obtížný úkol v JavaScriptu a není tak účinný jako přímočarýsideEffects flag. To také nemůže přeskočit subtree / závislosti, protože spec říká, že vedlejší účinky je třeba vyhodnotit. Zatímco funkce exportu funguje dobře, komponenty vyššího řádu React (HOC) jsou v tomto ohledu problematické.

příklad:

import { Button } from '@shopify/polaris';

pre-svázaný verze vypadá takhle:

import hoistStatics from 'hoist-non-react-statics';function Button(_ref) { // ...}function merge() { var _final = {}; for ( var _len = arguments.length, objs = new Array(_len), _key = 0; _key < _len; _key++ ) { objs = arguments; } for (var _i = 0, _objs = objs; _i < _objs.length; _i++) { var obj = _objs; mergeRecursively(_final, obj); } return _final;}function withAppProvider() { return function addProvider(WrappedComponent) { var WithProvider = /*#__PURE__*/ (function (_React$Component) { // ... return WithProvider; })(Component); WithProvider.contextTypes = WrappedComponent.contextTypes ? merge(WrappedComponent.contextTypes, polarisAppProviderContextTypes) : polarisAppProviderContextTypes; var FinalComponent = hoistStatics(WithProvider, WrappedComponent); return FinalComponent; };}var Button$1 = withAppProvider()(Button);export { // ..., Button$1,};

Když Button je nepoužitý můžete účinně odstranit export { Button$1 }; což ponechává všechny zbývající kód. Otázka tedy zní: „má tento kód nějaké vedlejší účinky nebo může být bezpečně odstraněn?“. Těžko říci, zejména kvůli tomuto řádku withAppProvider()(Button)withAppProvider je volán a návratová hodnota je také volána. Existují nějaké vedlejší účinky při volání merge nebo hoistStatics? Existují vedlejší účinky při přiřazování WithProvider.contextTypes (setr?) nebo při čtení WrappedComponent.contextTypes (Getter?).

Terser se to vlastně snaží zjistit, ale v mnoha případech to neví jistě. To neznamená, že terser nedělá svou práci dobře, protože na to nemůže přijít. Je příliš obtížné ji spolehlivě určit v dynamickém jazyce, jako je JavaScript.

ale můžeme pomoci terser pomocí/*#__PURE__*/ anotace. Označuje prohlášení jako vedlejší efekt zdarma. Jednoduchá změna by tedy umožnila setřást kód stromem:

var Button$1 = /*#__PURE__*/ withAppProvider()(Button);

to by umožnilo odstranit tento kus kódu. Stále však existují otázky týkající se dovozu, které je třeba zahrnout / vyhodnotit, protože by mohly obsahovat vedlejší účinky.

k řešení tohoto problému použijeme vlastnost "sideEffects" v package.json.

je to podobné /*#__PURE__*/ ale na úrovni modulu místo úrovně příkazu. To říká, že ("sideEffects" vlastnost): „Pokud žádný přímý export z modulu označeny žádné vedlejší účinky se používá, bundler může přeskočit hodnocení modul pro nežádoucí účinky.“.

v příkladu Shopify Polaris vypadají původní moduly takto:

index.js

import './configure';export * from './types';export * from './components';

komponenty / index.js

// ...export { default as Breadcrumbs } from './Breadcrumbs';export { default as Button, buttonFrom, buttonsFrom } from './Button';export { default as ButtonGroup } from './ButtonGroup';// ...

package.json

// ..."sideEffects": ,// ...

Na import { Button } from "@shopify/polaris"; má to následující důsledky:

  • zahrnují: obsahovat modul, vyhodnotit a pokračovat v analýze závislostí
  • přeskočit: ne, patří to, nechci hodnotit, ale pokračovat v analýze závislostí
  • vyloučit to: nechci, zahrnout, nechci hodnotit a analyzovat závislosti

Konkrétně za odpovídající zdroj(s):

  • index.js: Žádný přímý export se používá, ale označeny účinky -> zahrnout
  • configure.js: Ne export se používá, ale označeny účinky -> zahrnout
  • types/index.js: Ne export se používá, nejsou označeny účinky -> vyloučit
  • components/index.js: Bez přímého vývozu se používá, není označena s účinky, ale reexported vývoz se používají -> přeskočit
  • components/Breadcrumbs.js: Nepoužívá se žádný export, není označen sideEffects – > vyloučit. To také vyloučeny všechny závislostí, jako je components/Breadcrumbs.css i když jsou označeny účinky.
  • components/Button.js: Přímý vývoz se používá, nejsou označeny účinky -> zahrnout
  • components/Button.css: Ne export se používá, ale označeny účinky -> zahrnout

V tomto případě pouze 4 moduly jsou zahrnuty do svazku:

  • index.js: skoro prázdný
  • configure.js
  • components/Button.js
  • components/Button.css

Po této optimalizaci, další optimalizace může ještě použít. Například: buttonFrom a buttonsFrom exporty z Button.js jsou také nepoužité. usedExports optimalizace ji vyzvedne a terser může být schopen vypustit některé příkazy z modulu.

platí také zřetězení modulu. Takže tyto 4 moduly plus vstupní modul (a pravděpodobně více závislostí) mohou být zřetězeny. index.js nemá na konci vygenerovaný žádný kód.

Označit volání funkce jako vedlejší účinek zdarma

je možné říct, webpack, že volání funkce není strana účinek zdarma (pure) pomocí /*#__PURE__*/ anotace. Lze jej umístit před volání funkcí a označit je jako bez vedlejších účinků. Argumenty předané funkci nejsou označeny anotací a může být nutné je označit jednotlivě. Když je počáteční hodnota v proměnné prohlášení o nepoužité proměnné je považován za vedlejší účinek zdarma (pure), je stále označen jako mrtvý kód, nebude vykonán, a klesl o minimizer. Toto chování je povoleno, pokud je optimization.innerGraph nastaveno na true.

soubor.js

/*#__PURE__*/ double(55);

Minify Výstup

Takže máme přichystané naše „mrtvý kód“, aby se snížil pomocí importexport syntaxe, ale stále je třeba, aby ji pokles ze svazku. K tomu, nastavte mode možnost konfigurace production.

webpack.konfigurace.js

const path = require('path');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), },- mode: 'development',- optimization: {- usedExports: true,- }+ mode: 'production',};

S tou druhou stranou, můžeme spustit další npm run build a uvidíme, jestli se něco změnilo.

Všimněte si něco jiného o dist/bundle.js? Jasně celý svazek je teď minified a rozbité, ale když se podíváte pozorně, neuvidíte square funkce zahrnuty, ale bude vidět zmrzačené verze cube funkce (function r(e){return e*e*e}n.a=r). Díky minifikaci a třesu stromů je náš balíček nyní o několik bajtů menší! I když se to v tomto vymyšleném příkladu nemusí zdát jako moc, třepání stromů může při práci na větších aplikacích s komplexními stromy závislostí přinést významné snížení velikosti svazku.

závěr

takže jsme se naučili, že abyste mohli využít třesu stromů, musíte…

  • použijte syntaxi modulu ES2015 (tj. import a export).
  • Zajistit, žádné kompilátory transformovat své ES2015 modul syntaxe do CommonJS modules (toto je výchozí chování populární Babel přednastavených @babel/preset-env – naleznete v dokumentaci pro další podrobnosti).
  • přidejte vlastnost"sideEffects" do souboru package.json.
  • Použití productionmode konfigurace možnost povolit různé optimalizace včetně minification a strom třese.

svou aplikaci si můžete představit jako strom. Zdrojový kód a knihovny, které skutečně používáte, představují zelené, živé listy stromu. Mrtvý kód představuje hnědé, mrtvé listy stromu, které jsou spotřebovány na podzim. Chcete-li se zbavit mrtvých listů, musíte strom potřást a způsobit, že padnou.

Máte-li zájem o více způsobů, jak optimalizovat svůj výstup, přejděte na další průvodce pro podrobnosti o stavbě pro výrobu.