В современных монорепозиториях основная нагрузка при сборке ложится не только на транспиляцию исходного кода, но и на управление зависимостями между проектами, кэширование результатов, параллельное выполнение задач и оптимизацию времени обратной связи для разработчиков. Именно поэтому такие инструменты, как Nx и Turborepo, не заменяют сборщики, а выступают надстройками над ними.
esbuild занимает особое место в этой экосистеме благодаря чрезвычайно высокой скорости работы. Написанный на Go, он способен выполнять сборку проектов в десятки и сотни раз быстрее традиционных JavaScript-инструментов, что особенно заметно в крупных монорепозиториях.
Основные сценарии использования esbuild внутри Nx и Turborepo:
Типичная структура проекта может выглядеть следующим образом:
repo/
├── apps/
│ ├── api/
│ └── web/
├── packages/
│ ├── shared/
│ ├── ui/
│ └── utils/
├── nx.json
├── turbo.json
└── package.json
В такой архитектуре:
Получается следующая цепочка:
Исходный код
↓
Nx/Turborepo определяет граф зависимостей
↓
Запуск задачи build
↓
esbuild компилирует проект
↓
Результат попадает в кэш
↓
Повторные сборки используют кэш
Благодаря такому разделению ответственности достигается высокая масштабируемость монорепозитория.
Nx предоставляет собственные executors для работы с esbuild.
Создание Node.js-приложения:
nx g @nx/node:application api
Создание библиотеки:
nx g @nx/js:library shared
В конфигурации проекта можно использовать executor esbuild:
{
"targets": {
"build": {
"executor": "@nx/esbuild:esbuild",
"options": {
"main": "src/main.ts",
"outputPath": "dist/apps/api",
"tsConfig": "tsconfig.app.json"
}
}
}
}
Здесь Nx становится оркестратором задачи, а непосредственную компиляцию выполняет esbuild.
Executor представляет собой интеграционный слой между системой задач Nx и API esbuild.
Пример расширенной конфигурации:
{
"build": {
"executor": "@nx/esbuild:esbuild",
"options": {
"main": "src/main.ts",
"outputPath": "dist/apps/api",
"tsConfig": "tsconfig.app.json",
"bundle": true,
"platform": "node",
"format": ["cjs"],
"generatePackageJson": true,
"thirdParty": false
}
}
}
Основные параметры:
| Параметр | Назначение |
|---|---|
| main | Точка входа |
| outputPath | Каталог результата |
| bundle | Объединение модулей |
| platform | browser или node |
| format | esm или cjs |
| generatePackageJson | Генерация package.json |
| thirdParty | Включение внешних зависимостей |
Библиотеки являются важнейшим элементом любого монорепозитория.
Пример библиотеки:
libs/
└── shared/
├── src/
│ └── index.ts
└── project.json
Конфигурация сборки:
{
"targets": {
"build": {
"executor": "@nx/esbuild:esbuild",
"options": {
"main": "libs/shared/src/index.ts",
"outputPath": "dist/libs/shared",
"bundle": true,
"format": ["esm"]
}
}
}
}
После выполнения:
nx build shared
будет создан пакет:
dist/libs/shared/
├── index.js
└── package.json
Одним из ключевых преимуществ Nx является автоматическое построение графа проектов.
Пример:
api
├── shared
└── utils
При запуске:
nx build api
Nx определяет:
shared → utils → api
и выполняет сборку в правильном порядке.
Если библиотека shared не изменилась, ее артефакты могут
быть взяты из кэша.
Это особенно эффективно в сочетании с высокой скоростью esbuild.
Nx хранит информацию о:
Если ничего не изменилось:
nx build api
может завершиться практически мгновенно благодаря восстановлению результата из локального или удалённого кэша.
При использовании esbuild скорость выполнения увеличивается ещё сильнее, поскольку даже при необходимости пересборки время компиляции минимально.
Для публикации библиотек полезно автоматически создавать package.json.
Пример:
{
"generatePackageJson": true
}
После сборки:
dist/
└── shared/
├── index.js
└── package.json
Сгенерированный файл содержит только реально используемые зависимости.
Это уменьшает размер публикуемого пакета.
Современные библиотеки часто распространяются одновременно в ESM и CommonJS.
Конфигурация:
{
"format": ["esm", "cjs"]
}
Результат:
dist/
├── index.mjs
├── index.cjs
└── package.json
Подобный подход обеспечивает совместимость со старыми и новыми экосистемами Node.js.
Иногда стандартного executor недостаточно.
Можно использовать собственный конфигурационный файл.
const esbuild = require("esbuild");
esbuild.build({
entryPoints: ["src/main.ts"],
outfile: "dist/main.js",
bundle: true,
platform: "node"
});
Далее создается кастомный target:
{
"targets": {
"build": {
"command": "node build.js"
}
}
}
Nx продолжает управлять зависимостями и кэшированием, а логика сборки полностью контролируется разработчиком.
Turborepo использует иной подход.
Если Nx предоставляет собственные executors, то Turborepo практически не вмешивается в процесс сборки.
Основная задача Turborepo:
Сборщик выбирается самостоятельно.
Очень часто этим сборщиком становится именно esbuild.
apps/
├── api/
└── web/
packages/
├── shared/
├── ui/
└── config/
turbo.json
package.json
Каждый пакет может иметь собственную сборочную конфигурацию.
Например:
{
"scripts": {
"build": "node build.js"
}
}
Базовая конфигурация:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
Параметр:
"dependsOn": ["^build"]
означает:
Сначала собрать зависимости
Потом собрать текущий пакет
Файл build.js:
const esbuild = require("esbuild");
esbuild.build({
entryPoints: ["src/index.ts"],
outdir: "dist",
bundle: true,
format: "esm",
platform: "node"
});
package.json:
{
"scripts": {
"build": "node build.js"
}
}
Запуск:
turbo run build
Turborepo самостоятельно определяет порядок выполнения задач.
Предположим, имеются пакеты:
shared
utils
ui
docs
Независимые проекты могут собираться одновременно:
shared
utils
docs
После завершения:
ui
если ui зависит от shared.
esbuild здесь обеспечивает быстрые отдельные сборки, а Turborepo максимизирует параллелизм.
Одной из сильнейших возможностей Turborepo является Remote Cache.
Процесс выглядит следующим образом:
CI сервер
↓
Выполнил build
↓
Загрузил артефакт
↓
Remote Cache
↓
Другой разработчик
↓
Получил готовый результат
При использовании esbuild это создаёт практически мгновенные повторные сборки.
Типичная конфигурация:
const esbuild = require("esbuild");
esbuild.build({
entryPoints: ["src/index.ts"],
outdir: "dist",
bundle: true,
minify: true,
sourcemap: true,
target: "es2022",
format: "esm"
});
package.json:
{
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
Сборка становится частью общего графа задач Turborepo.
| Возможность | Nx | Turborepo |
|---|---|---|
| Встроенный executor esbuild | Да | Нет |
| Граф зависимостей | Да | Да |
| Локальный кэш | Да | Да |
| Удалённый кэш | Да | Да |
| Генераторы проектов | Да | Нет |
| Интеграция из коробки | Высокая | Минимальная |
| Гибкость настройки | Средняя | Очень высокая |
| Контроль над сборкой | Средний | Полный |
Часто создаётся общий файл:
module.exports = {
bundle: true,
sourcemap: true,
target: "es2022"
};
Далее он импортируется всеми пакетами:
const common = require("../. ./build/esbuild.config");
Это упрощает сопровождение больших репозиториев.
Для серверных приложений полезно исключать зависимости из бандла:
esbuild.build({
bundle: true,
external: [
"express",
"pg",
"typeorm"
]
});
Размер сборки уменьшается, а запуск ускоряется.
Распространённая схема:
apps/
packages/
Где:
Для отладки рекомендуется включать карты исходников:
esbuild.build({
sourcemap: true
});
При возникновении ошибки стек вызовов будет ссылаться на исходный TypeScript-код.
Типичный конвейер:
Git Push
↓
Turbo/Nx анализирует изменения
↓
Запускаются только изменённые проекты
↓
esbuild собирает нужные пакеты
↓
Кэшируются результаты
В больших монорепозиториях такой подход способен сокращать время сборки с десятков минут до нескольких минут или даже секунд для повторных запусков.
Во время разработки esbuild может работать в режиме наблюдения:
await esbuild.context({
entryPoints: ["src/index.ts"],
bundle: true,
outdir: "dist"
}).then(ctx => ctx.watch());
В монорепозитории это позволяет быстро пересобирать только изменённые пакеты без полного запуска всей цепочки задач.
В реальных проектах esbuild нередко используется совместно с другими средствами:
Nx / Turborepo
↓
esbuild
↓
TypeScript
↓
Jest
↓
ESLint
Либо:
Turborepo
↓
esbuild
↓
Vite
↓
React
Подобная архитектура позволяет сохранить преимущества каждого инструмента: скорость esbuild, интеллектуальное управление задачами Nx или Turborepo и специализированные возможности остальных компонентов экосистемы.