Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Tree shaking». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Tree shaking це аналіз імпортів та експортів модулів і видалення тих, які ніде не використовуються.** Збирач бере entry point, будує граф залежностей і викидає все, чого з цього кореня не досягти: функцію, клас чи змінну, яку ніхто не імпортує, у бандл просто не кладуть. Працює це лише з ES Modules, бо `import`/`export` статичні й аналізуються на етапі збірки, тоді як `require()` з динамічним шляхом передбачити неможливо. Код із побічними ефектами збирач не чіпає, тому бібліотеки позначають себе через `"sideEffects": false` у `package.json`. ```javascript // math.js: експортує add і multiply import { add } from './math.js'; // multiply у бандл не потрапить ``` **Ключове:** ESM плюс відсутність побічних ефектів, тоді збирач «струшує» з дерева залежностей усе недосяжне.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Tree shaking це процес аналізу імпортів та експортів модулів і видалення тих, які не використовуються у фінальному коді.** Простіше кажучи: якщо функцію, клас чи змінну ніколи не викликають і не імпортують, збирач просто викидає їх із підсумкового бандла. ## Теорія ### TL;DR - Назва метафорична: дерево це граф залежностей проєкту, а shaking це «струшування» невикористаних гілок. - Збирач бере entry points, будує граф імпортів і видаляє все, що з цих коренів недосяжне. - Працює лише з ES Modules, бо `import`/`export` статичні; CommonJS і динамічний `require()` ламають аналіз. - Код із побічними ефектами не видаляється, навіть якщо його експорти ніде не потрібні. - Поле `sideEffects` у `package.json` підказує збирачу, що модулі можна прибирати агресивніше. - Інструменти: Rollup, Webpack (версія 2 і новіші, у режимі `production`), ESBuild, Vite. ### Швидкий приклад ```javascript // math.js export function add(a, b) { return a + b; } export function multiply(a, b) { return a * b; } // app.js import { add } from './math.js'; console.log(add(2, 3)); ``` Без tree shaking у бандл потрапить і `add`, і `multiply`, хоча `multiply` не використовується. Сучасний збирач (Rollup, Webpack 5 і новіші, ESBuild, Vite) проаналізує, що `multiply` ніде не імпортується, отже його можна безпечно видалити. У результуючому бандлі залишиться тільки: ```javascript function add(a,b){return a+b} console.log(add(2,3)) ``` ### Як збирач розуміє, що можна видалити Tree shaking це аналіз графа залежностей у три кроки: 1. Визначаються «корені», тобто модулі, які реально використовує застосунок (entry points). 2. Будується граф імпортів. 3. Видаляється все, що з цих коренів недосяжне. Варто розрізняти два суміжні поняття. Tree shaking це логічний аналіз імпортів та експортів на рівні модулів. Dead code elimination (DCE) це подальший аналіз на рівні виразів, який зазвичай робить мініфікатор (Terser, UglifyJS). Ланцюжок виглядає так: ```text Webpack / Rollup -> позначають невикористані експорти | v Terser -> фізично вирізає код ``` ### Чому потрібні саме ES Modules Tree shaking працює тільки з ES Modules (ESM), бо вони статичні: ```javascript // Працює: ESM import { add } from './math.js'; // Не працює: CommonJS, аналіз на етапі збірки неможливий const math = require('./math'); ``` Збирач має бачити всі імпорти та експорти на етапі компіляції, а не під час виконання. Динамічні `require()` зі змінною в шляху ламають цю оптимізацію, бо передбачити, що саме буде імпортовано, неможливо. ### Побічні ефекти і поле sideEffects Tree shaking не видаляє код із побічними ефектами, тобто той, який може змінити глобальний стан: ```javascript // utils.js console.log('модуль завантажено'); // побічний ефект export const a = 1; export const b = 2; ``` Навіть якщо ви не імпортуєте ні `a`, ні `b`, збирач не видалить модуль, бо бачить побічний ефект: виклик `console.log`, зміну глобальної змінної тощо. У пакетах можна явно сказати, що код побічних ефектів не має, і тоді tree shaking працює агресивніше: ```json { "name": "my-lib", "sideEffects": false } ``` або вибірково, залишивши в списку лише те, що справді має ефекти: ```json { "sideEffects": [ "*.css", "./polyfills.js" ] } ``` ### Налаштування у збирачах **Webpack.** Режим `production` вмикає tree shaking разом із мініфікацією: ```javascript // webpack.config.js module.exports = { mode: 'production', // вмикає tree shaking та мініфікацію optimization: { usedExports: true, // позначає використані експорти } }; ``` Якщо подивитися в bundle-analyzer, ви побачите коментарі `/* unused harmony export */`, це і є tree shaking у дії. **Rollup.** Його від початку будували навколо tree shaking, тому він робить це найкраще: ```javascript // rollup.config.js export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'esm' } }; ``` Rollup видаляє не лише невикористані експорти, а й цілі ланцюжки функцій, якщо їх ніхто не викликає. **Vite та ESBuild.** Вони завжди працюють з ESM і підтримують tree shaking вбудовано, налаштовувати нічого не треба. Головне не використовувати CommonJS і не мати прихованих побічних ефектів. Підсумок: | Що | Опис | | --- | --- | | **Tree shaking** | Видалення невикористаних імпортів та експортів | | **Працює лише з** | ES Modules (`import` / `export`) | | **Не видаляє** | Код із побічними ефектами | | **Налаштування** | `sideEffects: false` у `package.json`, `mode: production` | | **Інструменти** | Webpack ≥ 2, Rollup, ESBuild, Vite | | **Мета** | Зменшити розмір бандла без ручного видалення коду | Головна ідея: збирач будує «дерево залежностей» і струшує з нього все, що не використовується, звідси й назва tree shaking. ### Типові помилки Найчастіші причини, через які tree shaking не спрацьовує: | Причина | Чому | | --- | --- | | Використовується CommonJS (`require`) | немає статичного аналізу залежностей | | Імпорт усього модуля (`import * as m`) або `require(variable)` | збирач не може передбачити, що саме імпортується | | Побічні ефекти в модулях | їх не можна безпечно видалити | | Плагіни або транспіляція ламають ESM | наприклад, Babel перетворює `import/export` на `require` | | Не увімкнено `mode: 'production'` у Webpack | у dev-режимі оптимізації вимкнені | Окремо варто пам'ятати: - **Чекати, що tree shaking сам зменшить бандл у dev-збірці.** Вимірювати розмір треба лише на production-збірці. - **Ставити `"sideEffects": false` наосліп.** Якщо у пакеті є CSS-імпорти, поліфіли чи реєстрація глобальних хелперів, збирач їх викине, і застосунок зламається у production, хоча локально все працювало. - **Плутати tree shaking із code splitting.** Перше прибирає невикористаний код, друге ділить використаний код на окремі чанки; це різні оптимізації.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.