Tree shaking в esbuild основан на статическом анализе ES Modules и стремится максимально агрессивно удалить весь код, который не участвует в конечном графе зависимостей. В отличие от традиционных сборщиков, где процесс может быть многоэтапным и частично эвристическим, esbuild делает ставку на линейную, высокопроизводительную модель анализа модулей.
Ключевая идея заключается в том, что каждый файл рассматривается как набор именованных экспортов и импортов, а затем строится граф связей, по которому определяется достижимость кода. Всё, что не достижимо из точки входа, считается кандидатом на удаление.
Esbuild работает исключительно со статически анализируемыми структурами ES Modules:
import и export должны быть
верхнеуровневымиПример исходного кода:
// util.js
export function used() {
return "used";
}
export function unused() {
return "unused";
}
// main.js
import { used } from "./util.js";
console.log(used());
После анализа esbuild формирует граф:
main.js → использует usedunused не имеет входящих ссылокВ результате unused полностью исключается из выходного
бандла.
Во внутренней модели esbuild каждый модуль превращается в набор:
Начальная точка — entry file
Все импортируемые модули добавляются в очередь обработки
Для каждого модуля фиксируются:
Итеративно вычисляется достижимое множество
Ключевая особенность: esbuild не просто удаляет файлы, а удаляет конкретные символы внутри файлов.
Tree shaking в esbuild возможен только благодаря структуре ES Modules.
ESM имеет:
require на верхнем уровне (в
идеале)Пример:
import { a } from "./mod.js";
Здесь a известен на этапе парсинга, что позволяет:
Esbuild оперирует не файлами, а символами.
Пример:
export const a = 1;
export const b = 2;
export const c = 3;
Если используется только a, то:
b и c удаляютсяTree shaking тесно связан с другими оптимизациями:
export const DEBUG = false;
Если DEBUG не используется, он исчезает полностью.
Если используется в условиях:
if (DEBUG) {
doSomething();
}
esbuild может удалить целый блок:
// после оптимизации
// блок отсутствует
Если после удаления экспортов остаются неиспользуемые ветки:
function internal() {
return 42;
}
export function api() {
return "ok";
}
internal();
internal() может быть удалена, если:
Критический аспект tree shaking — анализ побочных эффектов (side effects).
Пример:
console.log("module loaded");
export const x = 1;
Даже если x не используется, модуль может быть сохранён
из-за console.log.
В контексте npm-пакетов используется поле:
{
"sideEffects": false
}
Оно позволяет esbuild:
Если sideEffects: true, поведение становится
консервативным.
Esbuild поддерживает CommonJS, но tree shaking для него ограничен.
const a = require("./mod").a;
Здесь:
Esbuild преобразует CommonJS → ESM-подобную модель:
require анализируется как импортexport { a } from "./mod.js";
Esbuild может:
a с исходным модулемexport * from "./mod.js";
Поведение зависит от использования:
Tree shaking в esbuild тесно интегрирован с минификацией.
Это позволяет:
Несмотря на высокую эффективность, есть ограничения:
const name = "a";
export const obj = { [name]: 1 };
Такие конструкции усложняют анализ.
import "./polyfill.js";
Даже без экспорта файл сохраняется.
export * from dynamicPath;
Если путь вычисляется динамически — анализ невозможен.
usedExportsEsbuild использует несколько проходов:
Каждый этап оптимизирован под потоковую обработку, чтобы минимизировать память и время.
Если esbuild не может точно определить использование:
Это важный принцип: отсутствие ложных удалений важнее агрессивной оптимизации.
Логика можно описать как:
Эта модель позволяет esbuild достигать баланса между скоростью и качеством оптимизации, сохраняя предсказуемое поведение при сборке сложных JavaScript-приложений.