Articles

Træskakning

træskakning er et udtryk, der ofte bruges i JavaScript-sammenhæng til eliminering af død kode. Det er afhængig af den statiske struktur af ES2015 modul syntaks, dvs. import og export. Navnet og konceptet er blevet populariseret af ES2015 modul bundler rollup.2-udgivelsen kom med indbygget understøttelse af ES2015-moduler (alias harmony-moduler) samt ubrugt moduleksportdetektering. 4-udgivelsen udvider denne funktion med en måde at give tip til kompilatoren via "sideEffects"package.json egenskaben for at angive, hvilke filer i dit projekt der er “rene” og derfor sikre at beskære, hvis de ikke er brugt.

Tilføj et værktøj

lad os tilføje en ny hjælpefil til vores projekt,src/math.js, der eksporterer to funktioner:

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

Indstilmode konfigurationsmulighed til udvikling for at sikre, at bundtet ikke er minificeret:

netpakke.konfigurationsfil.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,+ },};

med det på plads, lad os opdatere vores indgangsscript for at bruge en af disse nye metoder og fjernelodash for enkelhed:

src / indeks.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());

Bemærk, at vi ikkeimportsquare metode frasrc/math.js modul. Denne funktion er det, der er kendt som” død kode”, hvilket betyder en ubrugt export, der skal droppes. Lad os nu køre vores npm-script, npm run build, og inspicere outputbundtet:

dist/bundle.js (omkring linjer 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; }});

Bemærk unused harmony export square kommentar ovenfor. Hvis du ser på koden under den, vil du bemærke, at square ikke importeres, men det er stadig inkluderet i bundtet. Vi løser det i næste afsnit.

Marker filen som bivirkningsfri

i en 100% ESM-modulverden er det ligetil at identificere bivirkninger. Vi er dog ikke der endnu, så i mellemtiden er det nødvendigt at give tip til kompilatoren på “renheden” af din kode.

den måde, dette opnås på, er"sideEffects" pakken.JSON ejendom.

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

al koden nævnt ovenfor indeholder ikke bivirkninger, så vi kan blot markere ejendommen somfalse for at informere pakken om, at den sikkert kan beskære ubrugt eksport.

Hvis din kode dog havde nogle bivirkninger, kan der i stedet leveres et array:

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

arrayet accepterer enkle glob-mønstre til de relevante filer. Det bruger glob-til-regeks under hætten (understøtter: ***{a,b}). Mønstre som *.css, som ikke inkluderer en /, vil blive behandlet som **/*.css.

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

endelig "sideEffects" kan også indstilles fra module.rules konfigurationsindstilling.

afklaring af træskakning og sideeffekter

sideEffects ogusedExports (mere kendt som træskakning) optimeringer er to forskellige ting.

sideEffects er meget mere effektiv, da det giver mulighed for at springe over hele moduler / filer og det komplette undertræ.

usedExports er afhængig af terer for at opdage bivirkninger i udsagn. Det er en vanskelig opgave i JavaScript og ikke så effektiv som ligetil sideEffects flag. Det kan heller ikke springe over subtree/afhængigheder, da spec siger, at bivirkninger skal evalueres. Mens eksportfunktionen fungerer fint, er reacts komponenter med højere ordre (HOC) problematiske i denne henseende.

lad os lave et eksempel:

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

den præ-bundtede version ser sådan ud:

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

Når Button er ubrugt, kan du effektivt fjerne export { Button$1 }; som efterlader al den resterende kode. Så spørgsmålet er ” har denne kode nogen bivirkninger, eller kan den fjernes sikkert?”. Svært at sige, især på grund af denne linje withAppProvider()(Button)withAppProvider kaldes, og returværdien kaldes også. Er der nogen bivirkninger, når du ringer mergeeller hoistStatics? Er der bivirkninger ved tildeling af WithProvider.contextTypes (Setter?) eller når du læser WrappedComponent.contextTypes (Getter?).

terer forsøger faktisk at finde ud af det, men det ved det ikke sikkert i mange tilfælde. Dette betyder ikke, at terser ikke gør sit arbejde godt, fordi det ikke kan finde ud af det. Det er bare for svært at bestemme det pålideligt på et dynamisk sprog som JavaScript.

men vi kan hjælpe terer ved at bruge/*#__PURE__*/ annotation. Det markerer en erklæring som bivirkning gratis. Så en simpel ændring ville gøre det muligt at træ-ryste koden:

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

dette ville gøre det muligt at fjerne dette stykke kode. Men der er stadig spørgsmål med importen, som skal medtages/evalueres, fordi de kan indeholde bivirkninger.

for at tackle dette bruger vi "sideEffects" ejendom i package.json.

det ligner /*#__PURE__*/ men på et modulniveau i stedet for et udsagnsniveau. Der står ("sideEffects" property): “hvis der ikke anvendes nogen direkte eksport fra et modul markeret med ingen sideeffekter, kan bundleren springe over evalueringen af modulet for bivirkninger.”.

i Shopify ‘ s Polaris eksempel ser originale moduler sådan ud:

indeks.js

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

komponenter/indeks.js

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

pakke.json

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

forimport { Button } from "@shopify/polaris"; dette har følgende implikationer:

  • Inkluder det: Inkluder modulet, Evaluer det og fortsæt med at analysere afhængigheder
  • spring over: Medtag det ikke, Evaluer det ikke, men fortsæt med at analysere afhængigheder
  • ekskluder det: Medtag det ikke, evaluer det ikke og analyser ikke afhængigheder

specifikt pr. matchende ressource(er):

  • index.js: Ingen direkte eksport bruges, men markeret med sideeffekter -> Inkluder det
  • configure.js: ingen eksport bruges, men markeret med sideeffekter -> Inkluder det
  • types/index.js: ingen eksport bruges, ikke markeret med sideeffekter -> ekskluder det
  • components/index.js: ingen direkte eksport bruges, ikke markeret med sideeffekter, men geneksporteret eksport bruges -> spring over
  • components/Breadcrumbs.js: Ingen eksport bruges, ikke markeret med sideeffekter – > ekskluder det. Dette udelukkede også alle afhængigheder som components/Breadcrumbs.css selvom de er markeret med sideeffekter.
  • components/Button.js: direkte eksport bruges, ikke markeret med sideeffekter ->Inkluder det
  • components/Button.css: ingen eksport bruges, men markeret med sideeffekter ->inkluder det

i dette tilfælde er kun 4 moduler inkluderet i bundtet:

  • index.js: temmelig meget Tom
  • configure.js
  • components/Button.js
  • components/Button.css

efter denne optimering kan andre optimeringer stadig anvendes. For eksempel:buttonFrom ogbuttonsFrom eksport fraButton.js er også ubrugte. usedExports optimering vil afhente det, og terer kan muligvis slippe nogle udsagn fra modulet.

Modulsammenkædning gælder også. Så disse 4 moduler plus indgangsmodulet (og sandsynligvis flere afhængigheder) kan sammenkædes. index.js har ingen kode genereret i slutningen.

Marker et funktionsopkald som bivirkningsfrit

det er muligt at fortælle, at et funktionsopkald er bivirkningsfrit (rent) ved at bruge/*#__PURE__*/ annotation. Det kan sættes foran funktionsopkald for at markere dem som bivirkningsfri. Argumenter, der overføres til funktionen, markeres ikke med annotationen og skal muligvis markeres individuelt. Når den oprindelige værdi i en variabel erklæring om en ubrugt variabel betragtes som bivirkningsfri (ren), bliver den markeret som død kode, ikke udført og droppet af minimisatoren. Denne adfærd er aktiveret, når optimization.innerGrapher indstillet til true.

fil.js

/*#__PURE__*/ double(55);

Minify Output

så vi har cued op vores “dead code”, der skal droppes ved hjælp afimport ogexport syntaks, men vi skal stadig slippe det fra bundtet. For at gøre det skal du indstille mode konfigurationsindstilling til production.

hjemmeside.konfigurationsfil.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',};

med det kvadreret væk kan vi køre en andennpm run build og se om noget er ændret.

Bemærk noget andet om dist/bundle.js? Det er klart, at hele bundtet nu er minificeret og manglet, men hvis du ser nøje, vil du ikke se square – funktionen inkluderet, men vil se en manglet version af cube – funktionen (function r(e){return e*e*e}n.a=r). Med minificering og træ ryster, vores bundt er nu et par bytes mindre! Selvom det måske ikke ser ud som meget i dette konstruerede eksempel, træskakning kan give et markant fald i bundtstørrelse, når du arbejder på større applikationer med komplekse afhængighedstræer.

konklusion

så, hvad vi har lært er, at for at drage fordel af træ ryster, skal du…

  • brug ES2015 modul syntaks (dvs.import ogexport).
  • sørg for, at ingen kompilatorer omdanner din ES2015-modulsyntaks til CommonJS-moduler (dette er standardadfærden for den populære Babel preset @babel/preset – env-se dokumentationen for flere detaljer).
  • Tilføj en"sideEffects" ejendom til dit projektspackage.json fil.
  • brug productionmode konfiguration mulighed for at aktivere forskellige optimeringer, herunder minificering og træ ryster.

Du kan forestille dig din ansøgning som et træ. Kildekoden og bibliotekerne, du faktisk bruger, repræsenterer træets grønne, levende blade. Dead code repræsenterer de Brune, døde blade af træet, der forbruges af efteråret. For at slippe af med de døde blade skal du ryste træet og få dem til at falde.

Hvis du er interesseret i flere måder at optimere dit output på, skal du gå til næste vejledning for detaljer om bygning til produktion.