Tree Shaking — механизам уклањања неискоришћеног кода (dead code elimination) у фази изградње апликације. Tree Shaking анализира статичку структуру ES-модула и искључује извезене функције, класе и променљиве које се нигде не увозе. Према Webpack Documentation, правилно подешавање Tree Shaking-а може смањити величину пакета за 30–60% без промене функционалности апликације.
Главно
Tree Shaking (трешење дрвета) — техника оптимизације кода при којој се из финалног пакета искључују модули и функције које се не користе у апликацији. Термин је увео тим Rollup-а 2015. године и метафорички описује процес: дрво зависности се протресе и неискоришћене гране отпадају. За разлику од ручне оптимизације, Tree Shaking се извршава аутоматски у фази изградње.
Tree Shaking ради само са ES-модулима (ECMAScript Modules), где се зависности дефинишу статички кроз import и export. CommonJS (require/module.exports) не подржава Tree Shaking јер се require извршава динамички — алат за изградњу не може унапред одредити које функције се стварно користе. Модерне библиотеке (Lodash, Moment.js, RxJS) издају ES верзије за подршку Tree Shaking-а.
Rollup — први алат за изградњу који је имплементирао Tree Shaking 2015. године. За разлику од Webpack-а, Rollup је од почетка дизајниран за ES-модуле и врши агресивније уклањање мртвог кода. Rollup анализира не само појединачне извозе, већ и целе модуле: ако модул нема споредних ефеката и ниједан извоз се не користи, Rollup искључује цео модул из пакета.
Rollup је посебно ефикасан за библиотеке и SDK-ове, где сваки килобајт има значај. Оквир Vue.js користи Rollup за изградњу production верзије. React је прешао на Rollup 2020. године. За апликације се чешће користи Webpack због богатијег екосистема додатака (Hot Module Replacement, code splitting, CSS modules), али за максимални Tree Shaking при изградњи библиотека, Rollup остаје индустријски стандард.
Уштеда од Tree Shaking-а снажно зависи од архитектуре пројекта. У апликацији на React-у са библиотеком Ant Design, Tree Shaking може уклонити до 70% кода UI компоненти. У пројекту где су сви увози специфични и тачкасти, уштеда ће износити 5–15%. Просечна уштеда према истраживањима Webpack-а је 30–40% величине пакета.
| Тип мртвог кода | Пример | Откривање Tree Shaking-ом |
|---|---|---|
| Неискоришћени извоз | export function unusedHelper() | Да |
| Неискоришћени увоз | import { unused } from "lib" | Да |
| Мртва грана услова | if (false) { ... } | Не (уклања минификатор) |
| Непозвана функција након DCE | function a(){} a() где a није позвана | Делимично |
Механизам Tree Shaking-а се заснива на графу зависности (dependency graph) који алат за изградњу прави од свих import/export у пројекту. У првој фази, алат за изградњу обилази све датотеке од улазне тачке (entry point) и прикупља стабло модула. У другој фази се анализира који извози из сваког модула се стварно увозе у другим модулима.
За сваки модул Webpack или Rollup означава извозе као коришћене или неискоришћене. Неискоришћени извози се искључују из пакета. Међутим, сам модул остаје у пакету ако се бар један његов извоз користи. Потпуно искључивање модула могуће је само кроз флаг sideEffects или ако модул не садржи никакве споредне ефекте.
// utils.js — модул са функцијама
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 — улазна тачка
import { formatDate } from "./utils";
const today = formatDate(new Date());
console.log(today);// Након Tree Shaking-а — у пакету само formatDate
function formatDate(date) {
return date.toISOString().slice(0, 10);
}
const today = formatDate(new Date());
console.log(today);Tree Shaking је искључио formatCurrency и slugify из финалног пакета, јер се не увозе у app.js. Величина модула utils.js смањила се са 3 функције на 1. Ако utils.js садржи side effects (нпр. глобалну иницијализацију), Tree Shaking неће моћи да уклони ни неискоришћене извозе.
Webpack укључује уграђену подршку за Tree Shaking кроз додатак TerserPlugin у production режиму. За укључивање Tree Shaking-а довољна су два услова: mode постављен на production (mode: "production") и модули који користе ES синтаксу (import/export). Webpack аутоматски означава неискоришћене извозе и прослеђује их Terser-у за уклањање.
Додатно подешавање usedExports: true у optimization.webpack.config.js укључује детаљну анализу коришћења извоза унутар модула. Ова опција одређује који извози се стварно користе (used), а који су само извезени (provided). Комбинација usedExports и Terser-а даје максималну ефикасност уклањања мртвог кода.
// webpack.config.js — подешавање 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 }],
],
},
},
},
],
},
};Кључни параметар — modules: false у @babel/preset-env. Babel подразумевано претвара ES-модуле у CommonJS, што убија Tree Shaking. modules: false забрањује Babel-у да претвара import/export, чувајући ES синтаксу за Webpack. concatenateModules додатно спаја модуле у заједнички опсег видљивости, смањујући број IIFE и величину пакета.
Side effects (споредни ефекти) — радње модула при његовом увозу које нису повезане са извезеним вредностима: глобални стилови (import "./styles.css"), полифилови (import "core-js/stable"), иницијализација глобалних променљивих или регистрација Service Worker-а. Ако модул садржи side effects, алат за изградњу не може безбедно да га уклони из пакета, чак и ако се ниједан извоз не користи.
Флаг sideEffects у package.json обавештава алат за изградњу који модули у пакету немају споредне ефекте. За пакет где су сви модули чисти (само извоз функција), треба поставити "sideEffects": false. За пакете са CSS-ом или полифилима — низ путања до датотека са споредним ефектима: "sideEffects": ["*.css"]. Без овог флага, Tree Shaking неће уклонити ни неискоришћене функције.
Да бисте проверили да ли модул има споредне ефекте, поставите питање: да ли ће овај увоз извршити било какве радње које нису повезане са извозом вредности? import "./styles.css" додаје CSS у DOM — то је side effect. import { throttle } from "lodash-es" нема side effects — само чини функцију throttle доступном. Полифилови (import "core-js/stable") имају side effects — мењају глобалне прототипове.
За сопствене модуле препоручује се: износити стилове и полифиле у засебне улазне тачке, раздвајати чисте алате (функције без side effects) од модула са споредним ефектима (иницијализација, логирање, регистрација Service Worker-а). У package.json пројекта највишег нивоа постављати "sideEffects": false само ако су сви модули чисти. Ако постоје стилови — постављати "sideEffects": ["*.css"] прецизно.
{
"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"] значи: све CSS датотеке имају споредне ефекте (не могу се уклонити) и polyfills.js такође. Све остале JS датотеке у пакету су чисте — могу се безбедно shake-овати. Поље module показује путању до ES верзије пакета коју алат за изградњу треба да користи уместо CommonJS верзије (main) за Tree Shaking.
React Native са Metro Bundler-ом подржава ограничену верзију Tree Shaking-а. Metro не врши потпуну статичку анализу коришћених извоза (usedExports) као Webpack. Уместо тога, Metro се ослања на Terser за уклањање неискоришћеног дела модула у фази минификације. Ефикасност овог приступа је нижа од пуног Tree Shaking-а у Webpack-у.
За максималну оптимизацију React Native пројеката препоручује се: коришћење библиотека са ES-модулима (поље module у package.json), повезивање додатака babel-plugin-transform-remove-console за уклањање отклањајућег кода и подешавање Metro transformer.minifierConfig за Terser. Додатно, Ram Bundle (подела пакета на модуле) смањује учитавање неискоришћених екрана.
Често постављана питања
CommonJS (require/module.exports) не подржава статичку анализу — require се може позивати динамички унутар услова и функција. Алат за изградњу не може одредити који делови модула се стварно користе. Само ES-модули са статичким import/export омогућавају Tree Shaking.
TypeScript је потпуно компатибилан са Tree Shaking-ом под условом да је tsconfig.json подешен на ES-модуле: "module": "esnext". TypeScript компајлер мора да сачува import/export без претварања у CommonJS. Babel са @babel/preset-typescript и modules: false такође коректно прослеђује ES-модуле у Webpack.
Webpack Bundle Analyzer — додатак који визуализује састав пакета у облику интерактивног дијаграма. Ако библиотека постоји у пакету, али њене функције нису коришћене, Tree Shaking није радио. Такође се може анализирати излазна датотека: пронаћи неискоришћени извоз у тексту пакета путем grep-а.
Lodash v4 се дистрибуира као CommonJS пакет. За Tree Shaking треба користити lodash-es — ES верзију библиотеке. Замените import throttle from "lodash/throttle" са import { throttle } from "lodash-es" и подесите resolve.alias у Webpack-у за замену lodash са lodash-es.
Tree Shaking незнатно повећава време изградње (за 5–15%), јер додаје фазу анализе графа зависности и означавања коришћених извоза. У development режиму, Tree Shaking је обично искључен ради брзине. У production-у, додатно време је оправдано значајним смањењем величине пакета.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође