Articles

Tree Shaking

Tree shaking este un termen utilizat în mod obișnuit în contextul JavaScript pentru eliminarea codului mort. Se bazează pe structura statică a sintaxei modulului ES2015, adică import și export. Numele și conceptul au fost popularizate de pachetul de module ES2015.

Versiunea webpack 2 a venit cu suport încorporat pentru modulele ES2015 (module alias harmony), precum și detectarea exportului modulului neutilizat. Noua versiune webpack 4 extinde această capacitate cu o modalitate de a oferi sugestii compilatorului prin intermediul "sideEffects"package.json proprietate pentru a indica care fișiere din proiectul dvs. sunt „pure” și, prin urmare, sigure de tăiat dacă sunt neutilizate.

adăugați un utilitar

să adăugăm un nou fișier utilitar la proiectul nostru,src/math.js, care exportă două funcții:

proiect

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;}

setați opțiunea de configuraremode la dezvoltare pentru a vă asigura că pachetul nu este minimizat:

webpack.config.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,+ },};

cu asta, să actualizăm scriptul nostru de intrare pentru a utiliza una dintre aceste noi metode și să eliminămlodash pentru simplitate:

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());

rețineți că nu amimport metodasquare din modululsrc/math.js. Această funcție este ceea ce este cunoscut sub numele de „cod mort”, adică un neutilizat export care ar trebui abandonat. Acum să rulăm scriptul nostru npm, npm run build și să inspectăm pachetul de ieșire:

dist/bundle.js (în jurul liniilor 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; }});

notați unused harmony export square comentariul de mai sus. Dacă vă uitați la codul de mai jos, veți observa că square nu este importat, cu toate acestea, este încă inclus în pachet. Vom rezolva asta în secțiunea următoare.

marcați fișierul ca fără efecte secundare

într-o lume a modulului ESM 100%, identificarea efectelor secundare este simplă. Cu toate acestea, nu suntem încă acolo, așa că, între timp, este necesar să oferiți sugestii compilatorului webpack despre „puritatea” codului dvs.

modul în care acest lucru este realizat este"sideEffects" pachet.proprietatea json.

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

tot codul menționat mai sus nu conține efecte secundare, deci putem marca pur și simplu proprietatea cafalse pentru a informa webpack că poate tăia în siguranță exporturile neutilizate.

dacă codul dvs. a avut unele efecte secundare, totuși, poate fi furnizată o matrice:

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

matricea acceptă modele simple de glob la fișierele relevante. Folosește glob-to-regexp sub capotă (acceptă: ***{a,b}). Modele precum *.css, care nu includ un /, vor fi tratate ca **/*.css.

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

În cele din urmă, "sideEffects" poate fi setat și din opțiunea de configurare module.rules.

clarificarea copac agitare și sideEffects

sideEffects șiusedExports (mai cunoscut sub numele de copac agitare) optimizările sunt două lucruri diferite.

sideEffects este mult mai eficient, deoarece permite să săriți module întregi / fișiere și subarborele complet.

usedExports se bazează pe terser pentru a detecta efectele secundare în declarații. Este o sarcină dificilă în JavaScript și nu la fel de eficient ca simplu sideEffects Pavilion. De asemenea, nu poate sări peste subarborele/dependențele, deoarece spec spune că efectele secundare trebuie evaluate. În timp ce funcția de export funcționează bine, componentele de ordin superior ale React (HOC) sunt problematice în această privință.

să facem un exemplu:

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

Versiunea pre-pachet arată astfel:

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,};

când Button este neutilizat puteți elimina în mod eficient export { Button$1 }; care lasă tot codul rămas. Deci, întrebarea este „acest cod are efecte secundare sau poate fi eliminat în siguranță?”. Greu de spus, mai ales din cauza acestei linii withAppProvider()(Button)withAppProvider este apelat și valoarea returnată este de asemenea apelată. Există efecte secundare la apelarea merge sau hoistStatics? Există efecte secundare la atribuirea WithProvider.contextTypes (Setter?) sau când citiți WrappedComponent.contextTypes (Getter?).

Terser încearcă de fapt să-și dea seama, dar nu știe sigur în multe cazuri. Acest lucru nu înseamnă că terser nu își face treaba bine, deoarece nu-și poate da seama. Este prea dificil să o determinați în mod fiabil într-un limbaj dinamic precum JavaScript.

dar putem ajuta terser folosind/*#__PURE__*/ adnotare. Acesta steaguri o declarație ca efect secundar gratuit. Deci, o simplă schimbare ar face posibilă tremurarea codului:

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

Acest lucru ar permite eliminarea acestei bucăți de cod. Dar există încă întrebări cu privire la importurile care trebuie incluse/evaluate, deoarece acestea ar putea conține efecte secundare.

pentru a aborda acest lucru, folosim"sideEffects" proprietate înpackage.json.

este similar cu/*#__PURE__*/ dar la nivel de modul în loc de nivel de declarație. Se spune ("sideEffects" proprietate): „dacă nu este utilizat nici un export direct dintr-un modul marcat cu NO-sideEffects, bundler poate sări peste evaluarea modulului pentru efecte secundare.”.

în exemplul Polaris al Shopify, modulele originale arată astfel:

index.js

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

componente / index.js

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

pachet.json

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

pentru import { Button } from "@shopify/polaris"; aceasta are următoarele implicații:

  • includeți-l: includeți modulul, evaluați-l și continuați să analizați dependențele
  • săriți peste: nu-l includeți, nu-l evaluați, dar continuați să analizați dependențele
  • excludeți-l: nu-l includeți, nu o evaluați și nu analizați dependențele

în mod specific pe resurse potrivite:

  • index.js: Nu este utilizat nici un export direct, dar marcat cu sideEffects -> include-L
  • configure.js: nu este utilizat nici un export, dar marcat cu sideEffects- > include-L
  • types/index.js: nu se utilizează export, nu este marcat cu efecte secundare – > excludeți-l
  • components/index.js: nu se utilizează export direct, nu este marcat cu efecte secundare, dar se utilizează exporturi reexportate – > săriți peste
  • components/Breadcrumbs.js: Nu se utilizează niciun export, nu este marcat cu efecte secundare – > excludeți-l. Acest lucru a exclus, de asemenea, toate dependențele precum components/Breadcrumbs.css chiar dacă sunt marcate cu efecte secundare.
  • components/Button.js: exportul Direct este utilizat, nu este marcat cu efecte secundare ->includeți-l
  • components/Button.css: nu este utilizat niciun export, ci marcat cu efecte secundare – >includeți-l

în acest caz, doar 4 module sunt incluse în pachet:

  • index.js: destul de mult gol
  • configure.js
  • components/Button.js
  • components/Button.css

după această optimizare, alte optimizări se pot aplica în continuare. De exemplu:buttonFrom șibuttonsFrom exporturile de laButton.js Sunt de asemenea neutilizate. usedExports optimizarea se va ridica și terser poate fi capabil să renunțe la unele declarații din modulul.

se aplică și concatenarea modulului. Astfel încât aceste 4 module plus modulul de intrare (și probabil mai multe dependențe) pot fi concatenate. index.js nu are nici un cod generat în cele din urmă.

marcați un apel de funcții ca fără efecte secundare

este posibil să spuneți webpack că un apel de funcții este fără efecte secundare (pur) utilizând adnotarea/*#__PURE__*/. Poate fi pus în fața apelurilor funcționale pentru a le marca ca fiind fără efecte secundare. Argumentele transmise funcției nu sunt marcate de adnotare și poate fi necesar să fie marcate individual. Atunci când valoarea inițială într-o declarație variabilă a unei variabile neutilizate este considerat ca efect secundar-free (pur), acesta este obtinerea marcat ca cod mort, nu executat și a scăzut de minimizer. Acest comportament este activat atunci cândoptimization.innerGraph este setat latrue.

fișier.js

/*#__PURE__*/ double(55);

Minify ieșire

deci, ne-am cued până nostru „cod mort” pentru a fi scăzut cu ajutorulimport șiexport sintaxa, dar încă mai trebuie să renunțe la pachet. Pentru a face acest lucru, setați opțiunea de configurare mode la production.

webpack.config.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',};

cu asta pătrat departe, putem rula un altnpm run build și a vedea dacă ceva sa schimbat.

observați ceva diferit despre dist/bundle.js? În mod clar, întregul pachet este acum minificat și mutilat, dar, dacă priviți cu atenție, nu veți vedea funcția square inclusă, dar veți vedea o versiune mutilată a funcției cubefunction r(e){return e*e*e}n.a=r). Cu minificarea și tremurarea copacilor, pachetul nostru este acum cu câțiva octeți mai mic! Deși acest lucru poate să nu pară prea mult în acest exemplu inventat, scuturarea copacilor poate produce o scădere semnificativă a dimensiunii pachetului atunci când lucrați la aplicații mai mari cu arbori de dependență complexi.

concluzie

deci, ceea ce am învățat este că, în scopul de a profita de copac agitare, trebuie să…

  • utilizați sintaxa modulului ES2015 (adicăimport șiexport).
  • asigurați-vă că niciun compilator nu transformă sintaxa modulului ES2015 în module CommonJS (acesta este comportamentul implicit al popularului Babel preset @babel / preset – env-consultați documentația pentru mai multe detalii).
  • adăugați o"sideEffects" proprietate la proiectul dumneavoastrăpackage.json fișier.
  • utilizați opțiunea de configurareproductionmode pentru a activa diverse optimizări, inclusiv minimizarea și agitarea copacilor.

vă puteți imagina aplicația ca un copac. Codul sursă și bibliotecile pe care le utilizați reprezintă frunzele verzi și vii ale copacului. Codul mort reprezintă frunzele maro, moarte ale copacului care sunt consumate până în toamnă. Pentru a scăpa de frunzele moarte, trebuie să scuturați copacul, făcându-i să cadă.

Dacă sunteți interesat de mai multe moduri de a vă optimiza producția, vă rugăm să treceți la următorul ghid pentru detalii despre construirea pentru producție.