Tree Shaking — mechanizm usuwania nieużywanego kodu (dead code elimination) na etapie budowania aplikacji. Tree Shaking analizuje statyczną strukturę modułów ES i wyklucza eksportowane funkcje, klasy i zmienne, które nie są nigdzie importowane. Według danych Webpack Documentation, prawidłowa konfiguracja Tree Shaking może zmniejszyć rozmiar pakietu o 30–60% bez zmiany funkcjonalności aplikacji.
Najważniejsze
Tree Shaking (wytrząsanie drzewa) — technika optymalizacji kodu, w której z finalnego pakietu wykluczane są moduły i funkcje nieużywane w aplikacji. Termin został wprowadzony przez zespół Rollup w 2015 roku i metaforycznie opisuje proces: drzewo zależności jest wytrząsane, a nieużywane gałęzie odpadają. W przeciwieństwie do ręcznej optymalizacji, Tree Shaking jest wykonywany automatycznie na etapie budowania.
Tree Shaking działa tylko z modułami ES (ECMAScript Modules), gdzie zależności są określane statycznie przez import i export. CommonJS (require/module.exports) nie obsługuje Tree Shaking, ponieważ require jest wykonywany dynamicznie — narzędzie budujące nie może z góry określić, które funkcje są faktycznie używane. Nowoczesne biblioteki (Lodash, Moment.js, RxJS) wydają wersje ES do obsługi Tree Shaking.
Rollup — pierwsze narzędzie budujące, które wdrożyło Tree Shaking w 2015 roku. W przeciwieństwie do Webpack, Rollup od początku był projektowany dla modułów ES i wykonuje bardziej agresywne usuwanie martwego kodu. Rollup analizuje nie tylko poszczególne eksporty, ale także całe moduły: jeśli moduł nie ma efektów ubocznych i żaden eksport nie jest używany, Rollup wyklucza cały moduł z pakietu.
Rollup jest szczególnie efektywny dla bibliotek i SDK, gdzie każdy kilobajt ma znaczenie. Framework Vue.js używa Rollup do budowania wersji produkcyjnej. React przeszedł na Rollup w 2020 roku. Do aplikacji częściej używa się Webpack ze względu na bogatszą ekosystem wtyczek (Hot Module Replacement, code splitting, CSS modules), ale dla maksymalnego Tree Shaking przy budowaniu bibliotek Rollup pozostaje standardem branżowym.
Oszczędność z Tree Shaking silnie zależy od architektury projektu. W aplikacji na React z biblioteką Ant Design Tree Shaking może usunąć do 70% kodu komponentów UI. W projekcie, gdzie wszystkie importy są specyficzne i punktowe, oszczędność wyniesie 5–15%. Średnia oszczędność według badań Webpack wynosi 30–40% rozmiaru pakietu.
| Typ martwego kodu | Przykład | Wykrywanie przez Tree Shaking |
|---|---|---|
| Nieużywany eksport | export function unusedHelper() | Tak |
| Nieużywany import | import { unused } from "lib" | Tak |
| Martwa gałąź warunku | if (false) { ... } | Nie (usuwane przez minifikator) |
| Niewywołana funkcja po DCE | function a(){} a() gdzie a nie jest wywołane | Częściowo |
Mechanizm Tree Shaking opiera się na grafie zależności (dependency graph), który narzędzie budujące tworzy ze wszystkich import/export w projekcie. W pierwszym etapie narzędzie budujące przegląda wszystkie pliki od punktu wejścia (entry point) i zbiera drzewo modułów. W drugim etapie analizowane jest, które eksporty z każdego modułu są faktycznie importowane w innych modułach.
Dla każdego modułu Webpack lub Rollup oznacza eksporty jako używane lub nieużywane. Nieużywane eksporty są wykluczane z pakietu. Jednak sam moduł pozostaje w pakiecie, jeśli przynajmniej jeden jego eksport jest używany. Całkowicie wykluczyć moduł można tylko przez flagę sideEffects lub jeśli moduł nie zawiera żadnych efektów ubocznych.
// utils.js — moduł z funkcjami
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 — punkt wejścia
import { formatDate } from "./utils";
const today = formatDate(new Date());
console.log(today);// Po Tree Shaking — w pakiecie tylko formatDate
function formatDate(date) {
return date.toISOString().slice(0, 10);
}
const today = formatDate(new Date());
console.log(today);Tree Shaking wykluczył formatCurrency i slugify z finalnego pakietu, ponieważ nie są importowane w app.js. Rozmiar modułu utils.js zmniejszył się z 3 funkcji do 1. Jeśli utils.js zawiera side effects (np. globalną inicjalizację), Tree Shaking nie będzie mógł usunąć nawet nieużywanych eksportów.
Webpack zawiera wbudowaną obsługę Tree Shaking przez wtyczkę TerserPlugin w trybie produkcyjnym. Do włączenia Tree Shaking wystarczą dwa warunki: mode ustawiony na production (mode: "production") i moduły używające składni ES (import/export). Webpack automatycznie oznacza nieużywane eksporty i przekazuje je Terser do usunięcia.
Dodatkowa konfiguracja usedExports: true w optimization.webpack.config.js włącza szczegółową analizę użycia eksportów wewnątrz modułu. Ta opcja określa, które eksporty są faktycznie używane (used), a które są tylko eksportowane (provided). Kombinacja usedExports i Terser daje maksymalną efektywność usuwania martwego kodu.
// webpack.config.js — konfiguracja 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 }],
],
},
},
},
],
},
};Kluczowy parametr — modules: false w @babel/preset-env. Babel domyślnie przekształca moduły ES w CommonJS, co zabija Tree Shaking. modules: false zabrania Babel przekształcania import/export, zachowując składnię ES dla Webpack. concatenateModules dodatkowo łączy moduły we wspólnym zakresie widoczności, zmniejszając liczbę IIFE i rozmiar pakietu.
Side effects (efekty uboczne) — działania modułu przy jego imporcie, niezwiązane z eksportowanymi wartościami: globalne style (import "./styles.css"), polifile (import "core-js/stable"), inicjalizacja globalnych zmiennych lub rejestracja Service Worker. Jeśli moduł zawiera side effects, narzędzie budujące nie może bezpiecznie usunąć go z pakietu, nawet jeśli żaden eksport nie jest używany.
Flaga sideEffects w package.json informuje narzędzie budujące, które moduły w pakiecie nie mają efektów ubocznych. Dla pakietu, gdzie wszystkie moduły są czyste (tylko eksport funkcji), należy ustawić "sideEffects": false. Dla pakietów z CSS lub polifilami — tablicę ścieżek do plików z efektami ubocznymi: "sideEffects": ["*.css"]. Bez tej flagi Tree Shaking nie usunie nawet nieużywanych funkcji.
Aby sprawdzić, czy w module są efekty uboczne, zadaj pytanie: czy ten import wykona jakieś działania niezwiązane z eksportem wartości? import "./styles.css" dodaje CSS do DOM — to side effect. import { throttle } from "lodash-es" nie ma side effects — tylko udostępnia funkcję throttle. Polifile (import "core-js/stable") mają side effects — modyfikują globalne prototypy.
Dla własnych modułów zaleca się: wyodrębniać style i polifile do osobnych punktów wejścia, oddzielać czyste narzędzia (funkcje bez side effects) od modułów z efektami ubocznymi (inicjalizacja, logowanie, rejestracja Service Worker). W package.json projektu głównego ustawiać "sideEffects": false tylko jeśli wszystkie moduły są czyste. Jeśli są style — ustawiać "sideEffects": ["*.css"] dokładnie.
{
"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"] oznacza: wszystkie pliki CSS mają efekty uboczne (nie można ich usuwać) i polyfills.js też. Wszystkie pozostałe pliki JS w pakiecie są czyste — można je bezpiecznie shake-ować. Pole module wskazuje ścieżkę do wersji ES pakietu, której narzędzie budujące powinno używać zamiast wersji CommonJS (main) dla Tree Shaking.
React Native z Metro Bundler obsługuje ograniczoną wersję Tree Shaking. Metro nie wykonuje pełnej statycznej analizy używanych eksportów (usedExports) jak Webpack. Zamiast tego Metro polega na Terser do usuwania nieużywanej części modułów na etapie minifikacji. Efektywność takiego podejścia jest niższa niż pełny Tree Shaking w Webpack.
Dla maksymalnej optymalizacji React Native zaleca się: używać bibliotek z modułami ES (pole module w package.json), podłączać wtyczki babel-plugin-transform-remove-console do usuwania kodu debugowania i konfigurować Metro transformer.minifierConfig dla Terser. Dodatkowo Ram Bundle (podział pakietu na moduły) zmniejsza ładowanie nieużywanych ekranów.
Często zadawane pytania
CommonJS (require/module.exports) nie obsługuje statycznej analizy — require może być wywoływany dynamicznie wewnątrz warunków i funkcji. Narzędzie budujące nie może określić, które części modułu są faktycznie używane. Tylko moduły ES ze statycznymi import/export umożliwiają Tree Shaking.
TypeScript jest w pełni kompatybilny z Tree Shaking pod warunkiem, że tsconfig.json jest skonfigurowany na moduły ES: "module": "esnext". Kompilator TypeScript musi zachowywać import/export bez przekształcania na CommonJS. Babel z @babel/preset-typescript i modules: false również poprawnie przekazuje moduły ES do Webpack.
Webpack Bundle Analyzer — wtyczka wizualizująca skład pakietu w formie interaktywnego diagramu. Jeśli biblioteka jest obecna w pakiecie, ale jej funkcje nie są używane, Tree Shaking nie zadziałał. Można również przeanalizować plik wyjściowy: znaleźć nieużywany eksport w tekście pakietu przez grep.
Lodash v4 jest dystrybuowany jako pakiet CommonJS. Do Tree Shaking należy użyć lodash-es — wersji ES biblioteki. Zamień import throttle from "lodash/throttle" na import { throttle } from "lodash-es" i skonfiguruj resolve.alias w Webpack do podmiany lodash na lodash-es.
Tree Shaking nieznacznie wydłuża czas budowania (o 5–15%), ponieważ dodaje etap analizy grafu zależności i oznaczania używanych eksportów. W trybie development Tree Shaking jest zwykle wyłączony dla szybkości. W produkcji dodatkowy czas jest uzasadniony znacznym zmniejszeniem rozmiaru pakietu.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również