Архитектура плагинов в Esbuild построена вокруг набора хуков, каждый из которых выполняет строго определённую роль в процессе сборки. При этом плагины могут состоять из нескольких хуков, и нередко возникает необходимость передавать контекст или промежуточные данные между ними в пределах одного плагина или даже между разными плагинами, работающими в одном процессе сборки.
Механизм pluginData решает задачу изолированной передачи
данных между этапами обработки модуля, не прибегая к глобальному
состоянию и не нарушая модель потоковой сборки.
В процессе работы Esbuild последовательно вызывает хуки:
onResolve — разрешение пути модуляonLoad — загрузка содержимого файлаonTransform (через onLoad и
resolve) — преобразование кодаonEnd — завершение сборкиКаждый из этих этапов может быть обработан разными плагинами. Внутри одного плагина часто требуется:
onResolve в onLoadПрямая передача данных невозможна, так как хуки независимы и могут выполняться параллельно.
pluginDatapluginData представляет собой механизм контекстной
передачи произвольного объекта между хуками для конкретного модуля.
Ключевые свойства:
onResolve в onLoadonResolve в onLoadОсновной сценарий использования — сохранение результата анализа пути или метаданных модуля на этапе разрешения.
import * as esbuild from 'esbuild';
const examplePlugin = {
name: 'example-plugin',
setup(build) {
build.onResolve({ filter: /\.txt$/ }, (args) => {
return {
path: args.path,
namespace: 'example-text',
pluginData: {
originalImport: args.importer,
timestamp: Date.now(),
isTextFile: true
}
};
});
build.onLoad({ filter: /.*/, namespace: 'example-text' }, (args) => {
const data = args.pluginData;
return {
contents: `// imported from: ${data.originalImport}\n` +
`// loaded at: ${data.timestamp}\n` +
`export default "file content";`,
loader: 'js'
};
});
}
};
pluginData в цепочке хуковEsbuild гарантирует, что:
pluginData, возвращённый из
onResolve, передаётся в соответствующий
onLoadВажно понимать, что pluginData не является кешем и не
хранится между разными файлами автоматически.
Каждый модуль получает собственный экземпляр pluginData.
Это исключает:
Пример:
build.onResolve({ filter: /\.data$/ }, (args) => {
return {
path: args.path,
namespace: 'data-ns',
pluginData: {
id: Math.random()
}
};
});
Каждый импорт .data файла будет иметь уникальный
id, и он не будет доступен другим модулям.
pluginDataНесмотря на гибкость, механизм имеет ряд ограничений:
Если onLoad не вызывается для результата
onResolve, данные теряются.
pluginData не предназначен для обмена данными между
разными модулями или плагинами на уровне всей сборки.
Хотя объект может быть произвольным, на практике безопаснее использовать:
Одним из ключевых сценариев применения является кеширование промежуточных результатов.
build.onResolve({ filter: /\.json$/ }, (args) => {
const stats = fs.statSync(args.path);
return {
path: args.path,
namespace: 'json-ns',
pluginData: {
size: stats.size,
mtime: stats.mtimeMs
}
};
});
build.onLoad({ filter: /.*/, namespace: 'json-ns' }, (args) => {
if (args.pluginData.size > 1024 * 100) {
return {
contents: 'export default {};',
loader: 'js'
};
}
const json = fs.readFileSync(args.path, 'utf8');
return {
contents: `export default ${json};`,
loader: 'js'
};
});
Здесь pluginData используется для принятия решения до
чтения файла.
pluginData часто используется для хранения результатов
анализа кода:
build.onResolve({ filter: /\.js$/ }, (args) => {
return {
path: args.path,
namespace: 'js-ns',
pluginData: {
importedBy: args.importer,
depth: (args.pluginData?.depth || 0) + 1
}
};
});
Здесь создаётся цепочка глубины импорта, передающаяся через каждый
уровень resolve.
pluginData с namespacepluginData часто используется вместе с
namespace, что позволяет:
Пример:
build.onResolve({ filter: /\.svg$/ }, (args) => {
return {
path: args.path,
namespace: 'svg-inline',
pluginData: {
inline: true,
source: args.importer
}
};
});
При каскадных импортax:
A → B → C
каждый переход может формировать собственный pluginData,
который:
Это важно учитывать при построении сложных графов зависимостей.
pluginDataПрактика работы с Esbuild показывает несколько устойчивых подходов:
pluginData должен содержать только:
Глубоко вложенные объекты увеличивают риск:
pluginData не должен использоваться для:
pluginData в архитектуре плагиновМеханизм выполняет роль локального канала передачи данных между фазами обработки одного и того же модуля. Он позволяет:
Наиболее распространённая архитектура:
onResolvepluginDataonLoadpluginDataЕсли onLoad не совпадает по namespace, данные будут
недоступны.
pluginDataЕсли вернуть объект без pluginData из
onResolve, данные не попадут в следующий хук.
Приводит к нарушению модели Esbuild и нестабильному поведению сборки.
pluginData можно рассматривать как:
resolve и
loadОн не расширяет систему хуков, а дополняет её строгой и предсказуемой связностью между этапами обработки одного файла.