Articles

Tree Shaking

Tree shaking è un termine comunemente usato nel contesto JavaScript per l’eliminazione del codice morto. Si basa sulla struttura statica della sintassi del modulo ES2015, cioè importeexport. Il nome e il concetto sono stati resi popolari dal rollup del bundler del modulo ES2015.

La versione webpack 2 è stata fornita con il supporto integrato per i moduli ES2015 (alias harmony modules) e il rilevamento delle esportazioni dei moduli non utilizzati. La nuova versione di webpack 4 espande questa capacità con un modo per fornire suggerimenti al compilatore tramite la proprietà"sideEffects"package.json per indicare quali file nel progetto sono “puri” e quindi sicuri da potare se non utilizzati.

Aggiungi un’utilità

Aggiungiamo un nuovo file di utilità al nostro progetto,src/math.js, che esporta due funzioni:

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

Imposta l’opzione di configurazionemode sullo sviluppo per assicurarti che il bundle non sia minimizzato:

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

Con quello in atto, aggiorniamo il nostro script di ingresso per utilizzare uno di questi nuovi metodi e rimuoverelodash per semplicità:

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

Si noti che non abbiamo importil squaremetodo dal src/math.js modulo. Questa funzione è ciò che è noto come” codice morto”, ovvero un export inutilizzato che dovrebbe essere eliminato. Ora eseguiamo il nostro script npm, npm run build e ispezioniamo il bundle di output:

dist/bundle.js (intorno alle righe 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 il commentounused harmony export square sopra. Se guardi il codice sottostante, noterai chesquare non viene importato, tuttavia, è ancora incluso nel bundle. Lo sistemeremo nella prossima sezione.

Contrassegna il file come privo di effetti collaterali

In un mondo di moduli ESM al 100%, identificare gli effetti collaterali è semplice. Tuttavia, non siamo ancora lì, quindi nel frattempo è necessario fornire suggerimenti al compilatore di webpack sulla “purezza” del tuo codice.

Il modo in cui questo viene realizzato è il pacchetto "sideEffects".proprietà json.

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

Tutto il codice sopra indicato non contiene effetti collaterali, quindi possiamo semplicemente contrassegnare la proprietà comefalse per informare webpack che può tranquillamente potare le esportazioni inutilizzate.

Se il tuo codice ha avuto alcuni effetti collaterali, puoi invece fornire un array:

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

L’array accetta semplici pattern glob nei file pertinenti. Utilizza glob-to-regexp sotto il cofano (Supporta: ***{a,b}). Modelli come*.css, che non includono un/, saranno trattati come**/*.css.

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

Infine, "sideEffects"può anche essere impostato dall’opzione di configurazione module.rules.

Clarifying tree shaking and sideEffects

Le ottimizzazionisideEffects eusedExports (più note come tree shaking) sono due cose diverse.

sideEffects è molto più efficace poiché consente di saltare interi moduli / file e il sottoalbero completo.

usedExports si basa su terser per rilevare gli effetti collaterali nelle dichiarazioni. È un compito difficile in JavaScript e non efficace quanto semplicesideEffects flag. Inoltre, non può saltare sottoalbero/dipendenze poiché le specifiche dicono che gli effetti collaterali devono essere valutati. Mentre la funzione di esportazione funziona correttamente, i componenti di ordine superiore (HOC) di React sono problematici a questo proposito.

facciamo un esempio:

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

pre-bundle versione simile a questa:

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

Quando Button non viene utilizzato si può efficacemente rimuovere il export { Button$1 }; che lascia tutto il resto del codice. Quindi la domanda è ” Questo codice ha effetti collaterali o può essere rimosso in modo sicuro?”. Difficile da dire, soprattutto a causa di questa linea withAppProvider()(Button)withAppProvider viene chiamato e viene chiamato anche il valore restituito. Ci sono effetti collaterali quando si chiama mergeo hoistStatics? Ci sono effetti collaterali quando si assegnaWithProvider.contextTypes (Setter?) o quando si legge WrappedComponent.contextTypes (Getter?).

Terser in realtà cerca di capirlo, ma non lo sa con certezza in molti casi. Questo non significa che terser non stia facendo bene il suo lavoro perché non riesce a capirlo. È troppo difficile determinarlo in modo affidabile in un linguaggio dinamico come JavaScript.

Ma possiamo aiutare terser usando l’annotazione/*#__PURE__*/. Segnala una dichiarazione come effetto collaterale gratuito. Quindi una semplice modifica renderebbe possibile agitare ad albero il codice:

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

Ciò consentirebbe di rimuovere questo pezzo di codice. Ma ci sono ancora domande con le importazioni che devono essere incluse/valutate perché potrebbero contenere effetti collaterali.

Per affrontare questo problema, usiamo la proprietà "sideEffects" in package.json.

È simile a /*#__PURE__*/ ma a livello di modulo anziché a livello di istruzione. Dice ("sideEffects" proprietà): “Se non viene utilizzata alcuna esportazione diretta da un modulo contrassegnato con no-sideEffects, il bundler può saltare la valutazione del modulo per gli effetti collaterali.”.

Nell’esempio Polaris di Shopify, i moduli originali hanno questo aspetto:

index.js

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

componenti / indice.js

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

pacchetto.json

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

import { Button } from "@shopify/polaris"; questo ha le seguenti implicazioni:

  • la includono: include il modulo, valutare e continuare l’analisi delle dipendenze
  • saltare: non si comprendono, non valutare, ma continua l’analisi delle dipendenze
  • escluderlo: non incluso, non valutare e non si analizzano le dipendenze

in particolare per risorsa corrispondente(s):

  • index.js: No esportazione diretta, ma contrassegnato con effetti collaterali -> includere
  • configure.js: No esporta, ma contrassegnato con effetti collaterali -> includere
  • types/index.js: Nessuna esportazione viene utilizzata, non sono contrassegnate con effetti collaterali -> escluderlo
  • components/index.js: No esportazione diretta viene utilizzato, non contrassegnati con minimi effetti collaterali, ma riesportato le esportazioni sono utilizzati -> salta
  • components/Breadcrumbs.js: Non viene utilizzata alcuna esportazione, non contrassegnata con sideEffects- > escluderla. Questo ha anche escluso tutte le dipendenze come components/Breadcrumbs.css anche se sono contrassegnate con sideEffects.
  • components/Button.js: esportazione Diretta viene utilizzato, non contrassegnati con effetti collaterali -> includere
  • components/Button.css: Nessuna esportazione viene utilizzata, ma contrassegnato con effetti collaterali -> includere

In questo caso, solo 4 moduli sono inclusi nel bundle:

  • index.js: praticamente vuota
  • configure.js
  • components/Button.js
  • components/Button.css

Dopo questa ottimizzazione, altre ottimizzazioni possono ancora applicare. Ad esempio: buttonFrom e buttonsFrom le esportazioni da Button.js sono inutilizzate. usedExports l’ottimizzazione lo raccoglierà e terser potrebbe essere in grado di rilasciare alcune istruzioni dal modulo.

Si applica anche la concatenazione del modulo. In modo che questi 4 moduli più il modulo entry (e probabilmente più dipendenze) possano essere concatenati. index.js non ha codice generato alla fine.

Contrassegna una chiamata di funzione come priva di effetti collaterali

È possibile dire a webpack che una chiamata di funzione è priva di effetti collaterali (puri) utilizzando l’annotazione/*#__PURE__*/. Può essere messo di fronte alle chiamate di funzione per contrassegnarle come prive di effetti collaterali. Gli argomenti passati alla funzione non vengono contrassegnati dall’annotazione e potrebbe essere necessario contrassegnarli individualmente. Quando il valore iniziale in una dichiarazione di variabile di una variabile non utilizzata è considerato privo di effetti collaterali (puro), viene contrassegnato come codice morto, non eseguito e rilasciato dal minimizzatore. Questo comportamento è abilitato quando optimization.innerGraph è impostato su true.file

.js

/*#__PURE__*/ double(55);

Minimizza l’output

Quindi abbiamo preparato il nostro “codice morto” da eliminare usando la sintassi importe export, ma abbiamo ancora bisogno di rilasciarlo dal bundle. Per fare ciò, impostare l’opzione di configurazione mode su 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',};

Con quello squadrato, possiamo eseguire un altro npm run build e vedere se qualcosa è cambiato.

Nota qualcosa di diverso sudist/bundle.js? Chiaramente l’intero pacchetto è ora minificato e mutilato, ma, se guardi attentamente, non vedrai la funzione square inclusa ma vedrai una versione mutilata della funzione cubefunction r(e){return e*e*e}n.a=r). Con minification e albero agitazione, il nostro pacchetto è ora un paio di byte più piccolo! Anche se questo potrebbe non sembrare molto in questo esempio artificioso, l’agitazione degli alberi può produrre una significativa diminuzione delle dimensioni del bundle quando si lavora su applicazioni più grandi con alberi di dipendenza complessi.

Conclusione

Quindi, quello che abbiamo imparato è che, al fine di sfruttare albero scuotendo, è necessario…

  • Usa la sintassi del modulo ES2015 (cioèimport eexport).
  • Assicurarsi che nessun compilatore trasformi la sintassi del modulo ES2015 in moduli CommonJS (questo è il comportamento predefinito del popolare preset babel @ babel / preset-env-vedere la documentazione per maggiori dettagli).
  • Aggiungi una proprietà"sideEffects" al filepackage.json del tuo progetto.
  • Utilizzare l’opzione di configurazioneproductionmode per abilitare varie ottimizzazioni, tra cui la minimizzazione e l’agitazione degli alberi.

Puoi immaginare la tua applicazione come un albero. Il codice sorgente e le librerie effettivamente utilizzate rappresentano le foglie verdi e vive dell’albero. Il codice morto rappresenta le foglie marroni e morte dell’albero che vengono consumate dall’autunno. Per sbarazzarsi delle foglie morte, devi scuotere l’albero, facendoli cadere.

Se sei interessato a più modi per ottimizzare il tuo output, vai alla guida successiva per i dettagli sulla costruzione per la produzione.