19 / 33
Что такое tree shaking и как ES Modules его поддерживают?
Полный ответ
Представь библиотеку с 50 функциями, из которых ты используешь 2. Без tree shaking в бандл попадут все 50:
JavaScript
// utils.js — большая библиотека
export function formatDate(d) { /* 100 строк */ }
export function formatNumber(n) { /* 80 строк */ }
export function formatCurrency(n, locale) { /* 200 строк */ }
export function formatPhone(phone) { /* 150 строк */ }
// ... ещё 46 функций
// app.js — используем только 2
import { formatDate, formatNumber } from "./utils.js";
С tree shaking бандлер (webpack, Rollup, Vite) видит, что formatCurrency, formatPhone и остальные 46 функций никто не импортирует, и удаляет их из финального бандла.
Почему это работает только с ESM:
JavaScript
// ESM — статический анализ возможен
import { add } from "./math.js";
// Бандлер ТОЧНО знает: нужен add, остальное — мусор
// CJS — статический анализ невозможен
const method = condition ? "add" : "subtract";
const math = require("./math");
math[method](2, 3);
// Бандлер НЕ ЗНАЕТ заранее, что будет вызвано — нужно тащить всё
Что мешает tree shaking (побочные эффекты):
JavaScript
// ❌ Побочный эффект при импорте — бандлер не может удалить
// polyfill.js
export function unusedHelper() {}
Array.prototype.customMethod = function () {}; // побочный эффект!
// Бандлер не удалит файл, потому что импорт меняет глобальное состояние
// ✅ Помечаем модуль как чистый (без побочных эффектов)
// package.json
{
"sideEffects": false
}
// Или указываем конкретные файлы с побочными эффектами
{
"sideEffects": ["./src/polyfills.js", "*.css"]
}
Как писать код, дружелюбный к tree shaking:
JavaScript
// ❌ Один объект со всеми методами — tree shaking не работает
const utils = {
formatDate() { /* ... */ },
formatNumber() { /* ... */ },
formatCurrency() { /* ... */ },
};
export default utils;
// Импортируешь весь объект, бандлер не может вырезать отдельные методы
// ✅ Отдельные именованные экспорты — tree shaking работает
export function formatDate() { /* ... */ }
export function formatNumber() { /* ... */ }
export function formatCurrency() { /* ... */ }
// ❌ Реэкспорт через namespace
import * as utils from "./utils.js";
export default utils; // бандлер теряет информацию
// ✅ Прямой реэкспорт
export { formatDate, formatNumber } from "./utils.js";
Реальные кейсы
lodash — классический пример:
JavaScript
// ❌ Тянет весь lodash (~70KB gzipped)
import _ from "lodash";
_.get(obj, "a.b.c");
// ✅ Tree shaking через lodash-es
import { get } from "lodash-es";
get(obj, "a.b.c");
// ✅ Или прямой импорт конкретного модуля
import get from "lodash/get";
Проверка размера бандла:
Bash
# webpack-bundle-analyzer покажет, что попало в бандл
npx webpack --analyze
Резюме
Tree shaking автоматически убирает мёртвый код из бандла, но только при использовании ES Modules с именованными экспортами. Чтобы он работал: используй export/import (не CommonJS), избегай побочных эффектов, помечай "sideEffects": false в package.json, и предпочитай именованные экспорты объектам.