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 | 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 какие-либо действия, не связанные с экспортом значений? import "./styles.css" добавляет CSS в DOM — это side effect. import { throttle } from "lodash-es" не имеет side effects — он только делает функцию throttle доступной. Полифиллы (import "core-js/stable") имеют side effects — они модифицируют глобальные прототипы.
Для собственных модулей рекомендуется: выносить стили и полифиллы в отдельные entry-точки, разделять чистые утилиты (функции без 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 не сработал. Также можно проанализировать output-файл: найти неиспользуемый экспорт в тексте бандла через 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также