Articles

Tree shaking

Tree shaking is een term die vaak wordt gebruikt in de JavaScript-context voor het verwijderen van dode code. Het is gebaseerd op de statische structuur van de ES2015 module syntaxis, d.w.z. import en export. De naam en het concept zijn gepopulariseerd door de ES2015 module bundler rollup.

De webpack 2-release werd geleverd met ingebouwde ondersteuning voor ES2015-modules (alias harmony-modules) en ongebruikte module-exportdetectie. De nieuwe webpack 4-release breidt deze mogelijkheid uit met een manier om hints te geven aan de compiler via de eigenschap "sideEffects"package.json om aan te geven welke bestanden in uw project “zuiver” zijn en daarom veilig te snoeien als ze niet worden gebruikt.

voeg een hulpprogramma toe

laten we een nieuw hulpprogramma aan ons project toevoegen, src/math.js, dat twee functies exporteert:

project

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

Stel de mode configuratieoptie in om ervoor te zorgen dat de bundel niet wordt verkleind:

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

met dat op zijn plaats, laten we ons invoerscript updaten om een van deze nieuwe methoden te gebruiken en verwijderen lodash voor de eenvoud:

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

merk op dat we niet import de square methode uit de src/math.js module. Die functie is wat bekend staat als” dode code”, wat betekent dat een ongebruikte export die moet worden verwijderd. Laten we nu ons NPM script draaien, npm run build, en de uitvoerbundel inspecteren:

dist/bundle.js (rond de regels 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; }});

merk op dat unused harmony export square opmerking hierboven. Als je naar de code hieronder kijkt, zul je merken dat square niet wordt geïmporteerd, maar het is nog steeds opgenomen in de bundel. Dat regelen we in de volgende sectie.

markeer het bestand als neveneffectvrij

In een 100% ESM-modulewereld is het identificeren van bijwerkingen eenvoudig. We zijn er echter nog niet, dus in de tussentijd is het nodig om tips te geven aan webpack ’s compiler over de “puurheid” van je code.

de manier waarop dit wordt bereikt is het "sideEffects" pakket.JSON eigendom.

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

alle bovenstaande code bevat geen bijwerkingen, dus we kunnen gewoon de eigenschap markeren als false om webpack te informeren dat het veilig ongebruikte exports kan snoeien.

als uw code echter enige bijwerkingen had, kan in plaats daarvan een array worden opgegeven:

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

de array accepteert eenvoudige glob patronen voor de relevante bestanden. Het gebruikt glob-naar-regexp onder de motorkap (ondersteunt: ***{a,b}). Patronen als *.css, die geen / bevatten, zullen worden behandeld als **/*.css.

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

ten slotte kan "sideEffects" ook worden ingesteld met de module.rules configuratie optie.

Clarifying tree shaking and sideEffects

DesideEffects enusedExports (beter bekend als tree shaking) optimalisaties zijn twee verschillende dingen.

sideEffects is veel effectiever omdat het toestaat om hele modules/bestanden en de volledige subboom over te slaan.

usedExports vertrouwt op terser om bijwerkingen in verklaringen te detecteren. Het is een moeilijke taak in JavaScript en niet zo effectief als eenvoudige sideEffects flag. Het kan ook subtree/afhankelijkheden niet overslaan omdat de specificatie zegt dat bijwerkingen moeten worden geëvalueerd. Hoewel de exportfunctie prima werkt, zijn de hogere Ordecomponenten (HOC) van React in dit opzicht problematisch.

laten we een voorbeeld geven:

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

De vooraf gebundelde versie ziet er als volgt uit:

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

wanneer Button ongebruikt is, kunt u effectief de export { Button$1 }; verwijderen die laat alle resterende code. Dus de vraag is ” heeft deze code bijwerkingen of kan het veilig worden verwijderd?”. Moeilijk te zeggen, vooral vanwege deze regel withAppProvider()(Button)withAppProvider wordt aangeroepen en de retourwaarde wordt ook aangeroepen. Zijn er bijwerkingen bij het aanroepen van merge of hoistStatics? Zijn er bijwerkingen bij het toewijzen van WithProvider.contextTypes (Setter?) of bij het lezen van WrappedComponent.contextTypes (Getter?).

Terser probeert het eigenlijk uit te zoeken, maar het weet het in veel gevallen niet zeker. Dit betekent niet dat terser zijn werk niet goed doet omdat het niet kan achterhalen. Het is gewoon te moeilijk om het betrouwbaar te bepalen in een dynamische taal als JavaScript.

maar we kunnen terser helpen met de/*#__PURE__*/ annotatie. Het markeert een verklaring als neveneffect gratis. Dus een eenvoudige verandering zou het mogelijk maken om de code te tree-shake:

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

Dit zou het mogelijk maken om dit stukje code te verwijderen. Maar er zijn nog steeds vragen met betrekking tot de invoer die moeten worden opgenomen/geëvalueerd, omdat ze bijwerkingen kunnen bevatten.

om dit aan te pakken, gebruiken we de eigenschap "sideEffects" In package.json.

Het is vergelijkbaar met /*#__PURE__*/ maar op een module niveau in plaats van een statement niveau. Er staat ("sideEffects" property): “als er geen directe export wordt gebruikt van een module die gemarkeerd is zonder neveneffecten, kan de bundler de evaluatie van de module overslaan voor bijwerkingen.”.

in het voorbeeld van Shopify ‘ s Polaris zien originele modules er als volgt uit:

index.js

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

componenten/index.js

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

pakket.json

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

Voor de import { Button } from "@shopify/polaris"; dit heeft de volgende gevolgen:

  • opnemen: neem de module, evalueren en verder gaan met de analyse van afhankelijkheden
  • overslaan: niet opnemen, niet te evalueren, maar blijven de analyse van afhankelijkheden
  • uitsluiten: niet opnemen, niet te evalueren, en niet het analyseren van afhankelijkheden

Specifiek per bijpassende resource(s):

  • index.js: Geen directe export is gebruikt, maar zijn gemarkeerd met bijwerkingen -> opnemen
  • configure.js: Geen uitvoer is gebruikt, maar zijn gemarkeerd met bijwerkingen -> opnemen
  • types/index.js: Geen uitvoer wordt gebruikt, niet gemarkeerd met bijwerkingen -> sluiten
  • components/index.js: er is Geen directe export is gebruikt, niet is gemarkeerd met bijwerkingen, maar reexported uitvoer worden gebruikt -> overslaan
  • components/Breadcrumbs.js: Er wordt geen export gebruikt, niet gemarkeerd met sideEffects -> uitsluiten. Dit sloot ook alle afhankelijkheden uit zoals components/Breadcrumbs.css, zelfs als ze gemarkeerd zijn met sideEffects.
  • components/Button.js: Directe export is gebruikt, niet is gemarkeerd met bijwerkingen -> opnemen
  • components/Button.css: Geen uitvoer wordt gebruikt, maar zijn gemarkeerd met bijwerkingen -> opnemen

In dit geval alleen de 4 modules zijn opgenomen in de bundel:

  • index.js: vrijwel leeg
  • configure.js
  • components/Button.js
  • components/Button.css

na deze optimalisatie kunnen nog andere optimalisaties worden toegepast. Bijvoorbeeld: buttonFrom en buttonsFrom exports van Button.js zijn ook niet gebruikt. usedExports optimalisatie zal het ophalen en terser kan mogelijk enkele statements uit de module laten vallen.

Modulecombinatie is ook van toepassing. Zodat deze 4 modules plus de entry module (en waarschijnlijk meer afhankelijkheden) kunnen worden samengevoegd. index.js heeft aan het eind geen code gegenereerd.

Markeer een functieaanroep als neveneffectvrij

Het is mogelijk om webpack te vertellen dat een functieaanroep neveneffectvrij is (pure) door gebruik te maken van de /*#__PURE__*/ annotatie. Het kan voor functieoproepen worden gezet om ze als neveneffectvrij te markeren. Argumenten die aan de functie worden doorgegeven worden niet gemarkeerd door de annotatie en moeten mogelijk individueel worden gemarkeerd. Wanneer de initiële waarde in een variabele declaratie van een ongebruikte variabele wordt beschouwd als neveneffect-vrij (pure), wordt het gemarkeerd als dode code, niet uitgevoerd en gedaald door de minimizer. Dit gedrag wordt ingeschakeld als optimization.innerGraph is ingesteld op true.

bestand.js

/*#__PURE__*/ double(55);

Minifyeer de uitvoer

zodat we onze” dode code”hebben gecreëerd om te laten vallen met behulp van de import en export syntaxis, maar we moeten het nog steeds uit de bundel laten vallen. Om dat te doen, stelt u de mode configuratie optie in op production.

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

met dat kwadraat weg, kunnen we een andere npm run build draaien en kijken of er iets is veranderd.

Merk iets anders op aan dist/bundle.js? Het is duidelijk dat de hele bundel nu geminifieerd en gemangled is, maar als je goed kijkt, zie je de square functie niet inbegrepen, maar een gemangelde versie van de cube functie (function r(e){return e*e*e}n.a=r). Met minificatie en boom schudden, onze bundel is nu een paar bytes kleiner! Hoewel dat misschien niet veel lijkt in dit gekunstelde voorbeeld, kan het schudden van bomen een significante afname in bundelgrootte opleveren bij het werken aan grotere toepassingen met complexe afhankelijkheidsbomen.

conclusie

dus, wat we hebben geleerd is dat om te profiteren van boom schudden, je moet…

  • gebruik ES2015 modulesyntaxis (d.w.z. import en export).
  • zorg ervoor dat compilers uw ES2015 module syntaxis niet omzetten in CommonJS modules (Dit is het standaard gedrag van de populaire Babel preset @babel/preset-env – zie de documentatie voor meer details).
  • voeg een "sideEffects" eigenschap toe aan het package.json bestand van uw project.
  • gebruik deproductionmode configuratie optie om verschillende optimalisaties mogelijk te maken, waaronder minificatie en tree shaking.

u kunt uw toepassing voorstellen als een boomstructuur. De broncode en bibliotheken die je gebruikt vertegenwoordigen de groene, levende bladeren van de boom. Dode code staat voor de bruine, dode bladeren van de boom die worden verbruikt door de herfst. Om van de dode bladeren af te komen, moet je de boom schudden, waardoor ze vallen.

Als u geïnteresseerd bent in meer manieren om uw uitvoer te optimaliseren, ga dan naar de volgende gids voor details over bouwen voor productie.