Архитектура плагинов: name и setup

Система плагинов в Esbuild построена вокруг минималистичной, но строго определённой модели расширения поведения сборщика. В отличие от более сложных инструментов, где плагины имеют множество жизненных циклов и хуков, Esbuild ограничивается компактным набором возможностей, где центральную роль играют два ключевых элемента: name и setup.

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


Общая структура плагина

Плагин в Esbuild представляет собой объект, содержащий:

  • обязательное поле name
  • обязательную функцию setup

Дополнительные поля не используются самим Esbuild и игнорируются системой, что подчёркивает строгую спецификацию API.

const examplePlugin = {
  name: 'example-plugin',
  setup(build) {
    // логика плагина
  }
};

Каждый плагин регистрируется в массиве plugins при вызове esbuild.build() или esbuild.context().

import * as esbuild from 'esbuild';

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

Поле name: идентичность и диагностика

Назначение name

Поле name задаёт уникальное имя плагина в контексте сборки. Оно используется исключительно для:

  • идентификации плагина в логах
  • диагностики ошибок
  • отладки цепочек выполнения
  • отображения в сообщениях Esbuild

Требования к имени

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

Роль в системе ошибок

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

Пример типичного сообщения:

error: example-plugin: could not resolve "./module"

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

Практика именования

Рекомендуется использовать:

  • имя библиотеки или системы
  • суффикс -plugin при необходимости
  • стабильные идентификаторы без версионности

Примеры корректных значений:

  • alias
  • ts-paths
  • node-modules-polyfill
  • css-modules

Функция setup: точка входа плагина

Назначение setup

setup — это единственная функция, через которую плагин взаимодействует с процессом сборки. Она вызывается один раз при инициализации сборки и получает объект build, через который регистрируются все хуки.

setup(build) {
  // регистрация логики
}

После завершения выполнения setup плагин считается полностью инициализированным.


Объект build: центральный интерфейс

Объект build предоставляет API для интеграции с системой разрешения модулей, чтения файлов и управления процессом компиляции.

Ключевые методы:

  • onResolve
  • onLoad
  • onStart
  • onEnd

Хотя они относятся к другим аспектам архитектуры, регистрация всех этих хуков происходит исключительно внутри setup.


Модель исполнения setup

Однократный вызов

Каждый плагин инициализируется ровно один раз на каждую сборку или контекст. Это означает:

  • отсутствует повторная инициализация при каждом модуле
  • состояние плагина может сохраняться внутри замыкания
  • регистрация хуков происходит единожды

Синхронная регистрация

setup выполняется синхронно, даже если внутри регистрируются асинхронные операции через хуки. Это важное архитектурное ограничение:

  • нельзя ожидать завершения асинхронных операций внутри setup
  • все асинхронные действия происходят в хуках

Регистрация хуков внутри setup

Основная задача setup — декларативно описать поведение плагина через хуки.

Пример базовой структуры:

const plugin = {
  name: 'demo',
  setup(build) {
    build.onResolve({ filter: /.*/ }, args => {
      return { path: args.path, namespace: 'demo' };
    });

    build.onLoad({ filter: /.*/ }, args => {
      return {
        contents: 'export const value = 42',
        loader: 'js'
      };
    });
  }
};

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


Замыкания и состояние плагина

Так как setup выполняется один раз, он часто используется для создания локального состояния плагина:

const cachePlugin = {
  name: 'cache',
  setup(build) {
    const cache = new Map();

    build.onLoad({ filter: /.*/ }, args => {
      if (cache.has(args.path)) {
        return cache.get(args.path);
      }

      const result = {
        contents: 'export const cached = true',
        loader: 'js'
      };

      cache.set(args.path, result);
      return result;
    });
  }
};

Такой подход позволяет:

  • реализовывать кэширование
  • хранить конфигурацию
  • поддерживать внутренние индексы файлов

Ограничения модели setup

Отсутствие динамической перерегистрации

После завершения setup нельзя:

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

Отсутствие доступа к финальному графу

На этапе setup граф модулей ещё не построен, поэтому:

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

Порядок выполнения setup

Если в сборке используется несколько плагинов, их setup вызываются последовательно в порядке массива:

plugins: [pluginA, pluginB, pluginC]

Порядок влияет на:

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

Взаимодействие name и setup

Хотя name и setup не взаимодействуют напрямую, они образуют единую концепцию:

  • name задаёт идентичность
  • setup определяет поведение

Эта связка позволяет Esbuild:

  • точно локализовать ошибки
  • упрощать отладку
  • сохранять минимализм API

Типовая структура сложного плагина

В реальных сценариях setup часто используется как точка композиции нескольких подсистем:

const plugin = {
  name: 'complex-plugin',
  setup(build) {
    registerResolver(build);
    registerLoader(build);
    registerOptimizer(build);
  }
};

function registerResolver(build) {
  build.onResolve({ filter: /^@app\// }, args => {
    return { path: args.path.replace('@app/', 'src/') };
  });
}

function registerLoader(build) {
  build.onLoad({ filter: /\.txt$/ }, args => {
    return {
      contents: JSON.stringify({ file: args.path }),
      loader: 'json'
    };
  });
}

function registerOptimizer(build) {
  build.onEnd(result => {
    // постобработка результата сборки
  });
}

Такая структура демонстрирует, что setup выступает как оркестратор, а не как место реализации всей логики.


Поведенческая модель плагина

Архитектурно плагин Esbuild можно представить как функцию конфигурации системы обработки модулей:

  • name — метка узла в диагностическом графе
  • setup — декларация правил трансформации
  • build — интерфейс подключения к пайплайну

Эта модель исключает избыточные абстракции и оставляет только минимально необходимый слой расширения функциональности.