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è import
eexport
. 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 import
il square
metodo 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 merge
o 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 comecomponents/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 import
e 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 cube
function 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 configurazione
production
mode
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.