Skip to content
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, и предпочитай именованные экспорты объектам.

Что такое tree shaking и как ES Modules его поддерживают? | JScriptiser