Несколько точек входа

В современных приложениях процесс сборки редко ограничивается одним входным файлом. В рамках одного проекта могут существовать отдельные страницы сайта, независимые модули, административные панели, виджеты, сервисные скрипты, Web Workers и серверные части приложения. Для подобных сценариев Esbuild предоставляет поддержку нескольких точек входа (Multiple Entry Points), позволяя собирать сразу множество независимых графов зависимостей за один запуск.

Использование нескольких точек входа позволяет:

  • собирать несколько приложений одновременно;
  • разделять код между страницами;
  • автоматически создавать отдельные выходные файлы;
  • уменьшать время сборки;
  • организовывать крупные проекты без множества отдельных конфигураций.

Понятие точки входа

Точка входа (entry point) представляет собой исходный файл, с которого начинается построение графа зависимостей.

Простейшая сборка с одной точкой входа:

await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'dist/app.js'
});

В данном случае Esbuild начинает анализ с файла index.js, находит все импорты и формирует единый бандл.

При наличии нескольких независимых приложений количество точек входа увеличивается.

Например:

src/
├── home.js
├── admin.js
└── dashboard.js

Каждый файл представляет отдельное приложение.


Передача массива точек входа

Самый простой способ определить несколько точек входа — передать массив файлов в параметр entryPoints.

await esbuild.build({
  entryPoints: [
    'src/home.js',
    'src/admin.js',
    'src/dashboard.js'
  ],
  bundle: true,
  outdir: 'dist'
});

После сборки:

dist/
├── home.js
├── admin.js
└── dashboard.js

Каждый файл получает собственный результирующий бандл.

Esbuild автоматически использует имя исходного файла для имени выходного файла.


Использование outdir вместо outfile

При одной точке входа обычно применяется параметр outfile.

outfile: 'dist/app.js'

При нескольких точках входа необходимо использовать outdir.

await esbuild.build({
  entryPoints: [
    'src/home.js',
    'src/admin.js'
  ],
  bundle: true,
  outdir: 'dist'
});

Неверная конфигурация:

await esbuild.build({
  entryPoints: [
    'src/home.js',
    'src/admin.js'
  ],
  bundle: true,
  outfile: 'dist/app.js'
});

В таком случае Esbuild выдаст ошибку, поскольку несколько входных файлов невозможно записать в один выходной файл.


Несколько страниц сайта

Один из наиболее распространённых сценариев — многостраничный сайт.

Структура проекта:

src/
├── pages/
│   ├── home.js
│   ├── about.js
│   └── contacts.js
└── shared/
    ├── api.js
    └── utils.js

Конфигурация:

await esbuild.build({
  entryPoints: [
    'src/pages/home.js',
    'src/pages/about.js',
    'src/pages/contacts.js'
  ],
  bundle: true,
  outdir: 'dist'
});

Результат:

dist/
├── home.js
├── about.js
└── contacts.js

Каждая страница получает собственный JavaScript-файл.


Объект вместо массива

Помимо массива путей Esbuild позволяет использовать объект.

await esbuild.build({
  entryPoints: {
    home: 'src/home.js',
    admin: 'src/admin.js',
    dashboard: 'src/dashboard.js'
  },
  bundle: true,
  outdir: 'dist'
});

Результат:

dist/
├── home.js
├── admin.js
└── dashboard.js

Такой подход полезен, когда требуется явно контролировать имена выходных файлов.


Настройка структуры выходных файлов

Параметр entryNames позволяет управлять схемой именования результатов сборки.

Пример:

await esbuild.build({
  entryPoints: [
    'src/home.js',
    'src/admin.js'
  ],
  bundle: true,
  outdir: 'dist',
  entryNames: '[name]-bundle'
});

Результат:

dist/
├── home-bundle.js
└── admin-bundle.js

Поддерживаются шаблоны:

Шаблон Назначение
[name] Имя входного файла
[dir] Исходная директория
[hash] Контрольная сумма
[ext] Расширение

Сохранение структуры каталогов

При работе с большим количеством страниц важно сохранять исходную структуру каталогов.

Исходная структура:

src/
├── public/
│   ├── home.js
│   └── about.js
└── admin/
    └── dashboard.js

Конфигурация:

await esbuild.build({
  entryPoints: [
    'src/public/home.js',
    'src/public/about.js',
    'src/admin/dashboard.js'
  ],
  bundle: true,
  outdir: 'dist',
  entryNames: '[dir]/[name]'
});

Результат:

dist/
├── public/
│   ├── home.js
│   └── about.js
└── admin/
    └── dashboard.js

Подобный подход особенно удобен в крупных приложениях с десятками страниц.


Общие зависимости между точками входа

Рассмотрим структуру:

src/
├── home.js
├── admin.js
└── utils.js

Файл home.js:

import { formatDate } from './utils.js';

Файл admin.js:

import { formatDate } from './utils.js';

Без дополнительной настройки содержимое utils.js будет включено в оба бандла.

Схематически:

home.js
 └─ utils.js

admin.js
 └─ utils.js

Это приводит к дублированию кода.


Автоматическое выделение общих модулей

Для устранения дублирования используется механизм code splitting.

await esbuild.build({
  entryPoints: [
    'src/home.js',
    'src/admin.js'
  ],
  bundle: true,
  splitting: true,
  format: 'esm',
  outdir: 'dist'
});

Результат:

dist/
├── home.js
├── admin.js
└── chunk-XYZ.js

Общий код переносится в отдельный chunk.

Преимущества:

  • уменьшение размера бандлов;
  • отсутствие дублирования;
  • ускорение загрузки страниц;
  • эффективное кеширование.

Несколько точек входа и динамический импорт

Файл:

const module = await import('./analytics.js');

При включённом разделении кода:

await esbuild.build({
  entryPoints: [
    'src/home.js',
    'src/admin.js'
  ],
  bundle: true,
  splitting: true,
  format: 'esm',
  outdir: 'dist'
});

Esbuild создаёт дополнительные чанки:

dist/
├── home.js
├── admin.js
├── analytics-ABC.js
└── chunk-XYZ.js

Загрузка таких файлов происходит только по мере необходимости.


Использование glob-шаблонов

В крупных проектах количество страниц может исчисляться десятками или сотнями.

Вместо ручного перечисления файлов часто используется автоматическое формирование списка точек входа.

Например, через Node.js:

import { glob } from 'glob';

const entryPoints = await glob('src/pages/**/*.js');

await esbuild.build({
  entryPoints,
  bundle: true,
  outdir: 'dist'
});

При добавлении новой страницы конфигурация остаётся неизменной.


Сборка клиентской и серверной частей

Один запуск Esbuild может обрабатывать сразу несколько независимых приложений.

Структура:

src/
├── client.js
├── admin.js
└── worker.js

Конфигурация:

await esbuild.build({
  entryPoints: [
    'src/client.js',
    'src/admin.js',
    'src/worker.js'
  ],
  bundle: true,
  outdir: 'dist'
});

Результат:

dist/
├── client.js
├── admin.js
└── worker.js

Каждый файл имеет собственный набор зависимостей.


Сборка Web Workers

Web Worker часто рассматривается как отдельная точка входа.

Структура:

src/
├── main.js
└── worker.js

Конфигурация:

await esbuild.build({
  entryPoints: [
    'src/main.js',
    'src/worker.js'
  ],
  bundle: true,
  outdir: 'dist'
});

Использование:

const worker = new Worker('/worker.js', {
  type: 'module'
});

Основное приложение и Worker собираются независимо.


Комбинирование нескольких типов приложений

Один проект может содержать:

src/
├── website/
│   ├── home.js
│   └── about.js
├── admin/
│   └── panel.js
├── widgets/
│   ├── chat.js
│   └── feedback.js
└── worker.js

Конфигурация:

await esbuild.build({
  entryPoints: [
    'src/website/home.js',
    'src/website/about.js',
    'src/admin/panel.js',
    'src/widgets/chat.js',
    'src/widgets/feedback.js',
    'src/worker.js'
  ],
  bundle: true,
  splitting: true,
  format: 'esm',
  outdir: 'dist'
});

Esbuild построит общий граф зависимостей, вынесет повторяющийся код в чанки и сформирует набор оптимизированных файлов.


Инкрементальная сборка с несколькими точками входа

Во время разработки особенно важна скорость пересборки.

Пример:

const context = await esbuild.context({
  entryPoints: [
    'src/home.js',
    'src/admin.js',
    'src/dashboard.js'
  ],
  bundle: true,
  outdir: 'dist'
});

await context.watch();

После изменения файлов Esbuild пересобирает только необходимые части графа зависимостей.

Даже при десятках точек входа скорость остаётся значительно выше по сравнению со многими традиционными сборщиками.


Анализ результатов сборки

Для изучения структуры зависимостей удобно использовать метафайл.

await esbuild.build({
  entryPoints: [
    'src/home.js',
    'src/admin.js'
  ],
  bundle: true,
  metafile: true,
  outdir: 'dist'
});

Полученный объект содержит информацию:

  • какие файлы вошли в каждый бандл;
  • размер модулей;
  • зависимости между чанками;
  • объём дублируемого кода.

Это особенно полезно при работе с большим количеством точек входа.


Практические рекомендации

Использование нескольких точек входа оправдано, когда:

  • существует несколько независимых страниц;
  • имеются отдельные административные интерфейсы;
  • используются Web Workers;
  • проект содержит независимые виджеты;
  • требуется разделение клиентской и серверной логики.

Для максимальной эффективности рекомендуется:

  • применять outdir вместо outfile;
  • включать splitting: true при наличии общих зависимостей;
  • использовать формат esm;
  • сохранять структуру каталогов через [dir];
  • автоматически генерировать список входных файлов для крупных проектов;
  • анализировать метафайл для поиска дублирующегося кода.

Механизм нескольких точек входа является одной из ключевых возможностей Esbuild при построении сложных приложений, позволяя объединять множество независимых сборок в единый высокопроизводительный процесс без усложнения конфигурации.