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, де кожен кілобайт має значення. Фреймворк 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()Так
Невикористовуваний importimport { 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 містить побічні ефекти (наприклад, глобальну ініціалізацію), 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. Якщо модуль містить побічні ефекти, збірник не може безпечно видалити його з бандла, навіть якщо жоден експорт не використовується.

Прапорець sideEffects у package.json повідомляє збірнику, які модулі в пакеті не мають побічних ефектів. Для пакета, де всі модулі чисті (тільки експорт функцій), слід вказати "sideEffects": false. Для пакетів з CSS або поліфілами — масив шляхів до файлів з побічними ефектами: "sideEffects": ["*.css"]. Без цього прапорця Tree Shaking не видалить навіть невикористовувані функції.

Як визначити побічні ефекти у своєму коді

Щоб перевірити, чи є в модулі побічні ефекти, поставте запитання: чи виконає цей import які-небудь дії, не пов'язані з експортом значень? import "./styles.css" додає CSS в DOM — це побічний ефект. import { throttle } from "lodash-es" не має побічних ефектів — він тільки робить функцію throttle доступною. Поліфіли (import "core-js/stable") мають побічні ефекти — вони модифікують глобальні прототипи.

Для власних модулів рекомендується: виносити стилі та поліфіли в окремі entry-точки, розділяти чисті утиліти (функції без побічних ефектів) та модулі з побічними ефектами (ініціалізація, логування, реєстрація 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 не спрацював. Також можна проаналізувати output-файл: знайти невикористовуваний експорт у тексті бандла через 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-режимі
  • Побічні ефекти блокують видалення модуля; прапорець sideEffects: false у package.json вирішує проблему для чистих бібліотек
  • Babel повинен бути налаштований з modules: false, щоб не перетворювати ES-модулі в CommonJS
  • React Native Metro має обмежений Tree Shaking, покладаючись на Terser на етапі мініфікації

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також