Tree Shaking: какво е, механизъм за премахване на мъртъв код и инструменти

Автор: IT Sectr Публикувано: 2026-05-18 Време за четене: 8 мин

Tree Shaking — механизъм за премахване на неизползван код (dead code elimination) на етапа на изграждане на приложението. Tree Shaking анализира статичната структура на ES-модулите и изключва експортираните функции, класове и променливи, които не се импортират никъде. Според Webpack Documentation, правилната конфигурация на Tree Shaking може да намали размера на пакета с 30–60% без да променя функционалността на приложението.

Основни точки

  • Tree Shaking — премахване на неизползвани експорти от ES-модули на базата на статичен анализ на import/export
  • ES-модули (import/export) — единственият формат, поддържащ Tree Shaking; CommonJS не се поддържа
  • Webpack и Rollup — основните инструменти за изграждане с поддръжка на Tree Shaking чрез плъгини
  • Side effects — страничните ефекти в модули блокират Tree Shaking; флагът sideEffects: false в package.json решава проблема
  • Used exports — анализ на използването на експорти в production режим на Webpack за точно премахване на мъртъв код

Какво е Tree Shaking?

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

Rollup — първият инструмент за изграждане, внедрил Tree Shaking през 2015 г. За разлика от Webpack, Rollup от самото начало е проектиран за ES-модули и извършва по-агресивно премахване на мъртъв код. Rollup анализира не само отделни експорти, но и цели модули: ако модулът няма странични ефекти и нито един експорт не се използва, Rollup изключва целия модул от пакета.

Rollup е особено ефективен за библиотеки и SDK, където всеки килобайт има значение. Framework-ът 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) { ... }Не (премахва се от минификатор)
Неизвикана функция след DCEfunction a(){} a() където a не е извиканаЧастично

Как работи Tree Shaking: статичен анализ на модули

Механизмът на Tree Shaking се основава на графа на зависимостите (dependency graph), който инструментът за изграждане изгражда от всички import/export в проекта. На първия етап инструментът за изграждане обхожда всички файлове от входната точка (entry point) и събира дървото от модули. На втория етап се анализира кои експорти от всеки модул реално се импортират в други модули.

За всеки модул Webpack или Rollup маркира експортите като използвани или неизползвани. Неизползваните експорти се изключват от пакета. Самият модул обаче остава в пакета, ако поне един негов експорт се използва. Пълното изключване на модул е възможно само чрез флага sideEffects или ако модулът не съдържа никакви странични ефекти.

Пример: преди и след Tree Shaking

js
// 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, "-");
}
js
// app.js — входна точка
import { formatDate } from "./utils";

const today = formatDate(new Date());
console.log(today);
js
// След 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 няма да може да премахне дори неизползваните експорти.

Tree Shaking в Webpack: конфигурация и оптимизация

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 за Tree Shaking

js
// 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 и флагът sideEffects

Side effects (странични ефекти) — действия на модула при неговото импортиране, които не са свързани с експортираните стойности: глобални стилове (import "./styles.css"), полифили (import "core-js/stable"), инициализация на глобални променливи или регистрация на Service Worker. Ако модулът съдържа side effects, инструментът за изграждане не може безопасно да го премахне от пакета, дори ако нито един експорт не се използва.

Флагът sideEffects в package.json информира инструмента за изграждане кои модули в пакета нямат странични ефекти. За пакет, където всички модули са чисти (само експорт на функции), трябва да се зададе "sideEffects": false. За пакети с CSS или полифили — масив от пътища към файлове със странични ефекти: "sideEffects": ["*.css"]. Без този флаг Tree Shaking няма да премахне дори неизползваните функции.

Как да определите side effects в собствения си код

За да проверите дали модулът има странични ефекти, задайте въпроса: ще изпълни ли този import някакви действия, несвързани с експорта на стойности? 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"] точно.

Пример за конфигурация на sideEffects

json
{
  "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.

Tree Shaking в React Native и Metro

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 (разделяне на пакета на модули) намалява зареждането на неизползвани екрани.

Често задавани въпроси

Защо Tree Shaking не работи с CommonJS?

CommonJS (require/module.exports) не поддържа статичен анализ — require може да бъде извикван динамично вътре в условия и функции. Инструментът за изграждане не може да определи кои части на модула реално се използват. Само ES-модули със статични import/export позволяват Tree Shaking.

Може ли да се използва Tree Shaking с TypeScript?

TypeScript е напълно съвместим с Tree Shaking при условие, че tsconfig.json е конфигуриран за ES-модули: "module": "esnext". Компилаторът на TypeScript трябва да запази import/export без преобразуване в CommonJS. Babel с @babel/preset-typescript и modules: false също правилно предава ES-модули на Webpack.

Как да проверя дали Tree Shaking е работил?

Webpack Bundle Analyzer — плъгин, визуализиращ състава на пакета като интерактивна диаграма. Ако библиотека присъства в пакета, но функциите ѝ не се използват, Tree Shaking не е работил. Също така може да се анализира изходният файл: намерете неизползван експорт в текста на пакета чрез grep.

Защо Lodash не се tree-shake-ва по подразбиране?

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 на времето за изграждане?

Tree Shaking леко увеличава времето за изграждане (с 5–15%), тъй като добавя етап на анализ на графа на зависимостите и маркиране на използвани експорти. В development режим Tree Shaking обикновено е изключен за скорост. В production допълнителното време е оправдано от значителното намаляване на размера на пакета.

Резюме

  • Tree Shaking — автоматично премахване на неизползвани ES експорти на етапа на изграждане, намаляване размера на пакета с 30–60%
  • ES-модули — единственият формат, поддържащ статичен анализ; CommonJS не е подходящ за Tree Shaking
  • Webpack и Rollup осигуряват Tree Shaking чрез usedExports и Terser в production режим
  • Side effects блокират премахването на модул; флагът sideEffects: false в package.json решава проблема за чисти библиотеки
  • Babel трябва да бъде конфигуриран с modules: false, за да не преобразува ES-модули в CommonJS
  • React Native Metro има ограничен Tree Shaking, разчитайки на Terser на етапа на минификация

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също