Tree Shaking è un meccanismo per rimuovere il codice non utilizzato (dead code elimination) nella fase di build dell’applicazione. Tree Shaking analizza la struttura statica dei moduli ES ed esclude funzioni, classi e variabili esportate che non vengono importate da nessuna parte. Secondo la Documentazione Webpack, una corretta configurazione di Tree Shaking può ridurre la dimensione del bundle del 30–60% senza modificare la funzionalità dell’applicazione.
Punti chiave
Tree Shaking è una tecnica di ottimizzazione del codice che esclude moduli e funzioni inutilizzati dal bundle finale. Il termine è stato introdotto dal team di Rollup nel 2015 e descrive metaforicamente il processo: l’albero delle dipendenze viene scosso e i rami inutilizzati cadono. A differenza dell’ottimizzazione manuale, Tree Shaking viene eseguito automaticamente nella fase di build.
Tree Shaking funziona solo con i moduli ES (ECMAScript Modules), dove le dipendenze sono determinate staticamente tramite import ed export. CommonJS (require/module.exports) non supporta Tree Shaking perché require viene eseguito dinamicamente — il bundler non può determinare in anticipo quali funzioni sono effettivamente utilizzate. Le librerie moderne (Lodash, Moment.js, RxJS) pubblicano versioni ES per supportare Tree Shaking.
Rollup è stato il primo bundler a implementare Tree Shaking nel 2015. A differenza di Webpack, Rollup è stato progettato fin dall’inizio per i moduli ES ed esegue una rimozione del codice morto più aggressiva. Rollup analizza non solo le singole esportazioni ma interi moduli: se un modulo non ha effetti collaterali e nessuna esportazione viene utilizzata, Rollup esclude l’intero modulo dal bundle.
Rollup è particolarmente efficace per librerie e SDK dove ogni kilobyte conta. Il framework Vue.js utilizza Rollup per compilare la sua versione di produzione. React è passato a Rollup nel 2020. Per le applicazioni, Webpack è più comunemente utilizzato grazie al suo ecosistema di plugin più ricco (Hot Module Replacement, code splitting, CSS modules), ma per il massimo Tree Shaking durante la creazione di librerie, Rollup rimane lo standard del settore.
Il risparmio di Tree Shaking dipende fortemente dall’architettura del progetto. In un’applicazione React con la libreria Ant Design, Tree Shaking può rimuovere fino al 70% del codice dei componenti UI. In un progetto dove tutte le importazioni sono specifiche e mirate, il risparmio sarà del 5–15%. Secondo le ricerche di Webpack, il risparmio medio è del 30–40% della dimensione del bundle.
| Tipo di codice morto | Esempio | Rilevamento Tree Shaking |
|---|---|---|
| Esportazione inutilizzata | export function unusedHelper() | Sì |
| Importazione inutilizzata | import { unused } from "lib" | Sì |
| Ramo di condizione morto | if (false) { ... } | No (rimosso dal minifier) |
| Funzione non chiamata dopo DCE | function a(){} a() dove a non è chiamata | Parzialmente |
Il meccanismo di Tree Shaking si basa su un grafo delle dipendenze che il bundler costruisce da tutte le dichiarazioni import/export nel progetto. Nella prima fase, il bundler attraversa tutti i file dal punto di ingresso (entry point) e raccoglie l’albero dei moduli. Nella seconda fase, analizza quali esportazioni da ciascun modulo vengono effettivamente importate in altri moduli.
Per ogni modulo, Webpack o Rollup contrassegna le esportazioni come utilizzate o inutilizzate. Le esportazioni inutilizzate vengono escluse dal bundle. Tuttavia, il modulo stesso rimane nel bundle se almeno una delle sue esportazioni viene utilizzata. Un modulo può essere completamente escluso solo tramite il flag sideEffects o se il modulo non contiene effetti collaterali.
// utils.js — modulo con funzioni
export function formatDate(date) {
return date.toISOString().slice(0, 10);
}
export function formatCurrency(amount) {
return "$" + amount.toFixed(2);
}
export function slugify(text) {
return text.toLowerCase().replace(/\s+/g, "-");
}// app.js — punto di ingresso
import { formatDate } from "./utils";
const today = formatDate(new Date());
console.log(today);// Dopo Tree Shaking — nel bundle solo formatDate
function formatDate(date) {
return date.toISOString().slice(0, 10);
}
const today = formatDate(new Date());
console.log(today);Tree Shaking ha escluso formatCurrency e slugify dal bundle finale perché non sono importati in app.js. La dimensione del modulo utils.js è passata da 3 funzioni a 1. Se utils.js contiene effetti collaterali (ad esempio, inizializzazione globale), Tree Shaking non può rimuovere nemmeno le esportazioni inutilizzate.
Webpack include il supporto integrato di Tree Shaking tramite TerserPlugin in modalità production. Per abilitare Tree Shaking, due condizioni sono sufficienti: mode è impostato su production (mode: "production") e i moduli utilizzano la sintassi ES (import/export). Webpack contrassegna automaticamente le esportazioni inutilizzate e le passa a Terser per la rimozione.
La configurazione aggiuntiva di usedExports: true in optimization.webpack.config.js consente un’analisi dettagliata dell’utilizzo delle esportazioni all’interno di un modulo. Questa opzione determina quali esportazioni sono effettivamente utilizzate e quali sono solo esportate (provided). La combinazione di usedExports e Terser offre la massima efficienza di rimozione del codice morto.
// webpack.config.js — configurazione Tree Shaking
module.exports = {
mode: "production",
entry: "./src/app.js",
output: {
filename: "bundle.js",
},
optimization: {
usedExports: true,
minimize: true,
concatenateModules: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules\/(?!(my-lib)\/).*/,
use: {
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", { modules: false }],
],
},
},
},
],
},
};Il parametro chiave è modules: false in @babel/preset-env. Per impostazione predefinita, Babel trasforma i moduli ES in CommonJS, il che uccide Tree Shaking. modules: false impedisce a Babel di trasformare import/export, preservando la sintassi ES per Webpack. concatenateModules inoltre unisce i moduli in un ambito condiviso, riducendo il numero di IIFE e diminuendo la dimensione del bundle.
I side effects (effetti collaterali) sono azioni che un modulo esegue quando viene importato e che non sono correlate ai valori esportati: stili globali (import "./styles.css"), polyfill (import "core-js/stable"), inizializzazione di variabili globali o registrazione di Service Worker. Se un modulo contiene effetti collaterali, il bundler non può rimuoverlo in modo sicuro dal bundle, anche se nessuna delle sue esportazioni viene utilizzata.
Il flag sideEffects in package.json dice al bundler quali moduli nel pacchetto non hanno effetti collaterali. Per un pacchetto in cui tutti i moduli sono puri (solo esportazioni di funzioni), è necessario specificare "sideEffects": false. Per i pacchetti con CSS o polyfill — un array di percorsi verso file con effetti collaterali: "sideEffects": ["*.css"]. Senza questo flag, Tree Shaking non rimuoverà nemmeno le funzioni inutilizzate.
Per verificare se un modulo ha effetti collaterali, chiediti: questa importazione eseguirà azioni non correlate all’esportazione di valori? import "./styles.css" aggiunge CSS al DOM — questo è un effetto collaterale. import { throttle } from "lodash-es" non ha effetti collaterali — rende semplicemente disponibile la funzione throttle. I polyfill (import "core-js/stable") hanno effetti collaterali — modificano i prototipi globali.
Per i propri moduli, si raccomanda di: estrarre stili e polyfill in punti di ingresso separati, separare le utilità pure (funzioni senza effetti collaterali) dai moduli con effetti collaterali (inizializzazione, registrazione, registrazione di Service Worker). Nel package.json del progetto di livello superiore, specificare "sideEffects": false solo se tutti i moduli sono puri. Se ci sono stili, specificare "sideEffects": ["*.css"] con precisione.
{
"name": "my-ui-lib",
"version": "2.1.0",
"sideEffects": [
"*.css",
"polyfills.js"
],
"module": "dist/index.esm.js",
"main": "dist/index.cjs.js"
}"sideEffects": ["*.css", "polyfills.js"] significa: tutti i file CSS hanno effetti collaterali (non possono essere rimossi) e polyfills.js anche. Tutti gli altri file JS nel pacchetto sono puri — possono essere scossi in modo sicuro. Il campo module specifica il percorso della versione ES del pacchetto che il bundler deve utilizzare al posto della versione CommonJS (main) per Tree Shaking.
React Native con Metro Bundler supporta una versione limitata di Tree Shaking. Metro non esegue un’analisi statica completa delle esportazioni utilizzate (usedExports) come Webpack. Invece, Metro si affida a Terser per rimuovere le parti inutilizzate dei moduli durante la minificazione. L’efficacia di questo approccio è inferiore al Tree Shaking completo in Webpack.
Per la massima ottimizzazione dei progetti React Native, si raccomanda di: utilizzare librerie con moduli ES (campo module in package.json), aggiungere babel-plugin-transform-remove-console per rimuovere il codice di debug e configurare Metro transformer.minifierConfig per Terser. Inoltre, Ram Bundle (divisione del bundle in moduli) riduce il caricamento delle schermate inutilizzate.
Domande frequenti
CommonJS (require/module.exports) non supporta l’analisi statica — require può essere chiamato dinamicamente all’interno di condizioni e funzioni. Il bundler non può determinare quali parti del modulo vengono effettivamente utilizzate. Solo i moduli ES con import/export statici consentono Tree Shaking.
TypeScript è completamente compatibile con Tree Shaking a condizione che tsconfig.json sia configurato per i moduli ES: "module": "esnext". Il compilatore TypeScript deve preservare import/export senza convertirli in CommonJS. Babel con @babel/preset-typescript e modules: false trasmette correttamente i moduli ES a Webpack.
Webpack Bundle Analyzer è un plugin che visualizza la composizione del bundle come diagramma interattivo. Se una libreria è presente nel bundle ma le sue funzioni non vengono utilizzate, Tree Shaking non ha funzionato. Puoi anche analizzare il file di output: cerca le esportazioni inutilizzate nel testo del bundle tramite grep.
Lodash v4 viene distribuito come pacchetto CommonJS. Per Tree Shaking, è necessario usare lodash-es — la versione ES della libreria. Sostituisci import throttle from "lodash/throttle" con import { throttle } from "lodash-es" e configura resolve.alias in Webpack per sostituire lodash con lodash-es.
Tree Shaking aumenta leggermente i tempi di build (del 5–15%) perché aggiunge una fase di analisi del grafo delle dipendenze e marcatura delle esportazioni utilizzate. In modalità development, Tree Shaking è solitamente disabilitato per velocità. In produzione, il tempo aggiuntivo è giustificato da una significativa riduzione delle dimensioni del bundle.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche