Tree shaking — это процесс автоматического удаления неиспользуемого кода из итоговой сборки приложения. Название происходит от аналогии с деревом зависимостей: сборщик анализирует модули проекта и «стряхивает» ветви, которые нигде не используются.
В современных JavaScript-приложениях значительная часть кода импортируется из сторонних библиотек. Нередко разработчик использует лишь небольшую часть возможностей пакета, однако без механизма tree shaking в финальный бандл попадал бы весь модуль целиком. Это увеличивает размер файлов, время загрузки и потребление памяти.
Parcel поддерживает tree shaking из коробки и автоматически пытается исключить код, который не влияет на работу приложения.
В основе механизма лежит статический анализ импортов и экспортов ES-модулей (ESM).
Рассмотрим простой пример.
Файл math.js:
export function add(a, b) {
return a + b;
}
export function subtract(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));
Parcel анализирует граф зависимостей и определяет:
add() используется;subtract() и multiply() нигде не
импортируются.После оптимизации неиспользуемые функции будут исключены из итоговой сборки.
Условно результат может выглядеть так:
function add(a, b) {
return a + b;
}
console.log(add(2, 3));
Лишний код исчезает ещё на этапе сборки.
Tree shaking возможен благодаря тому, что структура ES-модулей известна заранее.
Пример:
import { formatDate } from "./utils.js";
Сборщик сразу понимает:
Для сравнения, CommonJS обладает динамической природой.
const utils = require("./utils");
const method = process.env.METHOD;
utils[method]();
Здесь невозможно заранее определить, какая функция будет вызвана во время выполнения программы.
Поэтому полноценный tree shaking работает именно с ES-модулями.
Во время сборки Parcel строит граф зависимостей проекта.
Например:
app.js
├── api.js
├── utils.js
└── ui.js
├── button.js
└── modal.js
Каждый модуль рассматривается как узел графа.
Parcel определяет:
После анализа создаётся оптимизированная версия графа, содержащая только необходимый код.
Parcel отслеживает использование экспортируемых сущностей на уровне отдельных символов.
Файл:
export const PI = 3.14;
export function area(r) {
return PI * r * r;
}
export function circumference(r) {
return 2 * PI * r;
}
Импорт:
import { area } from "./geometry.js";
console.log(area(5));
Parcel понимает, что:
area() нужна;PI нужна, так как используется внутри
area();circumference() не нужна.В итоговый бандл попадут только необходимые элементы.
Tree shaking работает и в обратную сторону.
Исходный код:
import {
getUser,
updateUser,
deleteUser
} from "./api.js";
getUser();
Parcel определяет, что используются только:
getUser();
Остальные импорты не включаются в финальную сборку.
Главная сложность tree shaking связана с побочными эффектами (side effects).
Побочным эффектом считается код, который выполняет действия вне своей области видимости:
console.log("Module loaded");
или
window.theme = "dark";
или
document.body.classList.add("loaded");
Если модуль содержит подобный код, его нельзя безопасно удалить даже тогда, когда экспортируемые функции нигде не используются.
Пример:
console.log("Analytics initialized");
export function track() {
// ...
}
Даже если track() не используется, удаление всего файла
приведёт к исчезновению вывода в консоль.
Поэтому Parcel обязан сохранить выполнение такого модуля.
Идеальным кандидатом для tree shaking является модуль без побочных эффектов.
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
Такой файл содержит только определения функций.
Parcel может безопасно исключать неиспользуемые части без риска изменения поведения приложения.
Для более точной оптимизации используется свойство
sideEffects.
Пример:
{
"sideEffects": false
}
Такая запись сообщает сборщику:
Все файлы пакета не содержат побочных эффектов.
В результате Parcel получает возможность агрессивнее удалять неиспользуемые модули.
Иногда большая часть проекта безопасна, но некоторые файлы должны выполняться всегда.
{
"sideEffects": [
"./src/polyfills.js",
"./src/global.css"
]
}
В этом случае Parcel считает:
Предположим, существует пакет:
export function debounce() {}
export function throttle() {}
export function cloneDeep() {}
export function memoize() {}
Использование:
import { debounce } from "my-utils";
При корректной настройке Parcel включит только:
function debounce() {
// ...
}
Остальные функции будут удалены.
Следует учитывать особенности некоторых видов импорта.
Пример:
import * as utils from "./utils.js";
utils.format();
С точки зрения анализа такой код менее прозрачен.
Parcel может сохранить больше кода, поскольку объект
utils потенциально способен использовать любые
экспортируемые функции.
Более эффективный вариант:
import { format } from "./utils.js";
format();
Здесь сборщик точно знает, что нужен только один экспорт.
Часто создаются файлы-агрегаторы.
export { add } from "./math.js";
export { format } from "./date.js";
export { validate } from "./validation.js";
Использование:
import { add } from "./index.js";
Parcel способен проследить цепочку реэкспортов и удалить лишние зависимости.
В сборку попадёт только код, необходимый для работы функции
add.
Механизм работает не только с функциями.
Файл:
export class User {}
export class Admin {}
export class Moderator {}
Импорт:
import { User } from "./models.js";
Parcel удалит неиспользуемые классы Admin и
Moderator.
Аналогичный принцип действует для констант.
export const API_URL = "/api";
export const VERSION = "1.0";
export const BUILD_DATE = "2025-01-01";
Использование:
import { API_URL } from "./config.js";
Неиспользуемые значения могут быть исключены из финального кода.
Tree shaking и минификация — разные процессы.
Удаляет:
function unused() {}
если функция не используется.
Преобразует:
function calculatePrice(price, tax) {
return price + tax;
}
в:
function a(b,c){return b+c}
Обычно Parcel сначала удаляет ненужный код, а затем минифицирует оставшийся.
Комбинация этих механизмов обеспечивает максимальное уменьшение размера бандла.
Tree shaking тесно связан с техникой Dead Code Elimination (DCE).
Она удаляет код, который гарантированно никогда не будет выполнен.
Пример:
if (false) {
console.log("never");
}
После оптимизации:
// код удалён
Другой пример:
const DEBUG = false;
if (DEBUG) {
logger.enable();
}
После подстановки констант Parcel способен убрать всю ветку условия.
Распространённый сценарий:
if (process.env.NODE_ENV !== "production") {
console.log("Debug mode");
}
Во время production-сборки значение известно заранее.
После подстановки:
if (false) {
console.log("Debug mode");
}
Затем неиспользуемая ветка удаляется механизмом DCE.
Динамические импорты работают несколько иначе.
const module = await import("./analytics.js");
Parcel выделяет такой модуль в отдельный чанк.
Однако внутри этого чанка tree shaking продолжает работать.
Если файл analytics.js экспортирует множество функций,
но используются только некоторые из них, ненужный код всё равно может
быть исключён.
Существует ряд ситуаций, затрудняющих анализ.
utils[methodName]();
Parcel не знает, какая функция понадобится во время выполнения.
startAnalytics();
Такой код является побочным эффектом и препятствует удалению файла.
window.config = {};
Подобные действия вынуждают сборщик сохранять модуль.
const lib = require("./lib");
Возможности tree shaking для таких модулей существенно ограничены.
Для максимально эффективной работы Parcel полезно придерживаться следующих принципов:
import { helper } from "./helper.js";
вместо
const helper = require("./helper");
Предпочтительно:
export function init() {}
Вместо:
init();
export function init() {}
Предпочтительно:
import { format } from "./utils.js";
Вместо:
import * as utils from "./utils.js";
{
"sideEffects": false
}
или
{
"sideEffects": [
"./src/polyfills.js"
]
}
Это помогает Parcel принимать более точные решения во время оптимизации.
Production-сборка выполняется командой:
parcel build src/index.html
После завершения сборки в каталоге dist появляются
оптимизированные файлы.
Для анализа размера бандлов обычно изучают:
Сильное уменьшение размера сборки после перехода на ES-модули и
корректной настройки sideEffects является одним из
признаков успешной работы tree shaking.
Во время production-сборки Parcel последовательно выполняет несколько этапов:
В результате финальный бандл содержит только те части приложения и библиотек, которые действительно участвуют в работе программы. Это уменьшает объём передаваемых данных, ускоряет загрузку страниц и снижает расходы на обработку JavaScript в браузере.