Современная JavaScript-разработка практически всегда связана с процессом сборки проекта. Исходный код редко доставляется в браузер или среду выполнения в том виде, в котором он написан. Обычно выполняется целый набор операций:
На протяжении многих лет для этих задач активно использовались инструменты вроде Webpack, Rollup и Parcel. Несмотря на богатые возможности, крупные проекты часто сталкивались с проблемой длительного времени сборки. С ростом объёмов кода запуск разработки и пересборка после изменений могли занимать десятки секунд или даже минуты.
Для решения этой проблемы был создан esbuild — сверхбыстрый инструмент сборки JavaScript и TypeScript, разработанный Эваном Уоллесом. Главной целью проекта стало достижение максимальной производительности без ущерба для основных возможностей современных сборщиков.
esbuild — это инструмент для сборки JavaScript-приложений, который объединяет в себе несколько функций:
Проще говоря, esbuild способен выполнять большую часть задач, которые обычно требуются на этапе подготовки приложения к запуску или публикации.
Пример простейшей сборки:
esbuild src/index.js --bundle --outfile=dist/app.js
В результате:
src/index.js.dist/app.js.Главная особенность esbuild — экстремально высокая скорость работы.
Большинство традиционных сборщиков написаны на JavaScript и работают поверх движка V8. Esbuild реализован на языке Go, который компилируется в машинный код и обеспечивает высокий уровень производительности.
Кроме того, архитектура esbuild изначально строилась вокруг:
За счёт этого даже проекты с тысячами файлов могут собираться за доли секунды.
В небольших приложениях разница между сборкой за 2 секунды и за 200 миллисекунд может показаться несущественной. Однако в крупных проектах ситуация меняется.
Представим приложение со следующими характеристиками:
Если пересборка занимает 20–30 секунд, разработчик постоянно ожидает результата после каждого изменения.
Даже небольшие задержки накапливаются:
| Время одной пересборки | Пересборок в день | Потери времени |
|---|---|---|
| 2 секунды | 300 | 10 минут |
| 10 секунд | 300 | 50 минут |
| 20 секунд | 300 | 100 минут |
Поэтому современные инструменты всё чаще ориентируются на производительность, а esbuild стал одним из лидеров этого направления.
Esbuild умеет объединять множество файлов в единый пакет.
Исходные файлы:
// math.js
export function sum(a, b) {
return a + b;
}
// index.js
import { sum } from './math.js';
console.log(sum(5, 10));
Сборка:
esbuild index.js --bundle --outfile=bundle.js
Результат:
(() => {
function sum(a, b) {
return a + b;
}
console.log(sum(5, 10));
})();
Все зависимости оказываются внутри одного файла.
Esbuild способен преобразовывать современный синтаксис в код, совместимый со старыми браузерами.
Исходный код:
const getUserName = (user) => user?.name ?? 'Unknown';
Команда:
esbuild app.js --target=es2017 --outfile=out.js
После обработки синтаксис будет адаптирован под указанный стандарт ECMAScript.
Отдельная настройка TypeScript-компилятора часто не требуется.
Файл:
type User = {
name: string;
};
const user: User = {
name: 'Alex'
};
console.log(user.name);
Сборка:
esbuild app.ts --bundle --outfile=app.js
Типы будут удалены, а результатом станет обычный JavaScript.
Esbuild умеет работать с JSX без дополнительных транспиляторов.
Пример:
function App() {
return <h1>Hello World</h1>;
}
Сборка:
esbuild app.jsx --bundle --outfile=app.js
Это особенно полезно при работе с React и другими библиотеками, использующими JSX-синтаксис.
Для производственной версии приложения размер файлов имеет большое значение.
Исходный код:
function calculateTotal(price, tax) {
return price + tax;
}
console.log(calculateTotal(100, 20));
Минификация:
esbuild app.js --minify --outfile=app.min.js
Результат:
function calculateTotal(o,t){return o+t}console.log(calculateTotal(100,20));
Размер файла становится меньше, а загрузка быстрее.
Внутри процесс сборки состоит из нескольких этапов.
Сначала esbuild считывает точку входа:
esbuild src/index.js
Далее определяется список импортируемых модулей:
import './utils.js';
import './api.js';
import './config.js';
Формируется граф зависимостей.
Каждый файл рассматривается как узел графа.
Например:
index.js
├── api.js
│ └── config.js
└── utils.js
Esbuild определяет порядок обработки и включения файлов в итоговый пакет.
На данном этапе выполняются:
После завершения преобразований создаётся финальный набор файлов:
dist/
├── app.js
├── app.css
└── app.js.map
При необходимости формируются карты исходников.
Высокая скорость достигается благодаря нескольким решениям.
Большинство популярных инструментов написаны на JavaScript.
Esbuild реализован на Go:
JavaScript → V8 → Машинный код
Против:
Go → Машинный код
Это уменьшает накладные расходы выполнения.
Если проект содержит тысячу файлов, esbuild может обрабатывать множество модулей одновременно.
Схематично:
Файл 1 ─┐
Файл 2 ─┼─> Поток 1
Файл 3 ─┘
Файл 4 ─┐
Файл 5 ─┼─> Поток 2
Файл 6 ─┘
Используются все доступные ядра процессора.
Некоторые сборщики многократно проходят по одному и тому же дереву кода.
Esbuild старается выполнять несколько операций за один проход:
Это дополнительно сокращает время работы.
Современная экосистема JavaScript использует несколько систем модулей.
Современный стандарт ECMAScript.
import { sum } from './math.js';
export default sum;
Традиционный формат Node.js.
const fs = require('fs');
module.exports = {};
Самовызывающаяся функция для браузеров.
(() => {
console.log('App');
})();
Универсальный формат, способный работать в разных окружениях.
Esbuild умеет преобразовывать код между различными форматами.
Пример:
esbuild app.js --format=esm
или
esbuild app.js --format=cjs
Хотя esbuild прежде всего ориентирован на JavaScript, он способен обрабатывать и CSS.
Пример импорта:
import './styles.css';
Во время сборки CSS будет включён в результирующий набор файлов.
Команда:
esbuild app.js --bundle --outfile=dist/app.js
Результат:
dist/
├── app.js
└── app.css
Одной из важных возможностей является удаление неиспользуемого кода.
Модуль:
export function add() {}
export function subtract() {}
export function multiply() {}
Использование:
import { add } from './math.js';
add();
При сборке функции subtract и multiply
могут быть удалены из итогового пакета.
Такой процесс называется Tree Shaking.
Преимущества:
Во время разработки часто возникает проблема: браузер показывает ошибку в скомпилированном файле, а исходный код находится в другом месте.
Для решения используются карты исходников.
Создание:
esbuild app.js --sourcemap
Результат:
app.js
app.js.map
Теперь инструменты разработчика смогут связывать итоговый код с оригинальными файлами проекта.
Esbuild может использоваться двумя способами.
Самый простой вариант:
esbuild src/index.js --bundle --outfile=dist/app.js
Подходит для небольших проектов и экспериментов.
Пример:
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/app.js'
});
Такой подход позволяет создавать собственные сценарии сборки и интегрировать esbuild в сложную инфраструктуру.
Сегодня esbuild используется не только как самостоятельный инструмент.
На его основе построены или частично используют его возможности многие современные решения:
Во многих случаях именно esbuild отвечает за быстрый запуск среды разработки, предварительную обработку зависимостей или часть процесса компиляции.
Наибольшую выгоду esbuild приносит в следующих сценариях:
Благодаря сочетанию функций бандлера, транспилятора и минификатора esbuild позволяет существенно сократить время подготовки кода, сохраняя при этом совместимость с современными стандартами JavaScript и существующей экосистемой фронтенд-разработки.