В плагинах Esbuild ошибки формируются не только через выбрасывание
исключений, но и через специализированные механизмы API, которые
позволяют точно привязывать проблему к этапу сборки и исходному коду.
Основные точки вмешательства — хуки onResolve и
onLoad, где происходит разрешение путей и загрузка
модулей.
Внутри этих хуков доступен контекст плагина
(PluginBuild), предоставляющий методы:
this.error(...) — фиксация ошибки сборкиthis.warn(...) — формирование предупрежденияЭти методы предпочтительнее стандартного throw, так как
они позволяют Esbuild корректно агрегировать диагностическую информацию,
привязывать её к файлам и продолжать обработку других модулей.
Пример логики обработки в onResolve:
onResolve({ filter: /\.txt$/ }, (args) => {
if (args.path.includes("forbidden")) {
this.error({
text: "Использование запрещённого пути",
location: {
file: args.importer,
line: 1,
column: 0
}
})
}
return {
path: args.path,
namespace: "text"
}
})
Здесь ошибка не прерывает выполнение немедленно, а регистрируется в системе диагностики сборки.
Хук onLoad отвечает за загрузку содержимого модуля. Это
наиболее частое место для генерации ошибок, связанных с чтением файлов,
парсингом или трансформацией содержимого.
Типовой сценарий — невозможность прочитать файл:
onLoad({ filter: /.*/, namespace: "file" }, async (args) => {
let source
try {
source = await fs.promises.readFile(args.path, "utf8")
} catch (err) {
this.error({
text: `Не удалось прочитать файл: ${err.message}`,
location: null
})
return
}
return {
contents: source,
loader: "text"
}
})
Ключевой момент: возврат undefined после
this.error не обязательно прерывает сборку полностью, но
сигнализирует о проблеме в конкретном модуле.
Использование throw в плагинах Esbuild приводит к
немедленному прерыванию выполнения текущего хука. Это поведение удобно
для критических ошибок, когда дальнейшая сборка теряет смысл.
Однако в большинстве случаев предпочтительнее
this.error, потому что:
throw целесообразен в ситуациях, когда невозможно
продолжить обработку даже текущего файла, например при повреждении
внутреннего состояния плагина.
Предупреждения (this.warn) используются для сигнализации
о некритичных проблемах: устаревшие API, подозрительные импорты,
потенциальные ошибки оптимизации.
onResolve({ filter: /\.legacy\.js$/ }, (args) => {
this.warn({
text: "Используется устаревший модуль",
location: {
file: args.importer,
line: 10,
column: 5
}
})
return {
path: args.path,
namespace: "file"
}
})
В отличие от ошибок, предупреждения не влияют на статус завершения сборки, но могут быть преобразованы в ошибки через настройки CLI или API.
Объекты ошибок и предупреждений в Esbuild поддерживают унифицированный формат:
text — сообщениеlocation — позиция в исходном кодеnotes — дополнительные пояснения (в некоторых
сценариях)detail — расширенные данные (не всегда отображаются в
CLI)Пример расширенной ошибки:
this.error({
text: "Неверный формат конфигурации",
location: {
file: args.path,
line: 3,
column: 15
},
notes: [
{
text: "Ожидается JSON-объект"
}
]
})
Такая структура позволяет формировать многоуровневую диагностику, полезную при разработке сложных трансформационных плагинов.
Esbuild выполняет плагины последовательно в рамках цепочки
onResolve → onLoad. Ошибка может возникнуть на
любом этапе, и её обработка зависит от точки возникновения.
Если ошибка происходит в onResolve, модуль может быть
исключён из дальнейшей обработки. Если в onLoad, модуль
считается частично или полностью не загруженным.
При этом важно учитывать:
Это означает, что плагины не изолированы и ошибки могут накапливаться от разных источников.
Плагины Esbuild часто используют ошибки как механизм управления потоком выполнения. Например, можно остановить дальнейшую обработку конкретного namespace:
onLoad({ namespace: "json" }, (args) => {
const data = JSON.parse(fs.readFileSync(args.path, "utf8"))
if (!data.version) {
this.error({
text: "Отсутствует обязательное поле version"
})
return
}
return {
contents: JSON.stringify(data),
loader: "json"
}
})
Такая модель позволяет строить строгие схемы валидации входных данных прямо на этапе сборки.
Esbuild не останавливается при первой ошибке (если не используется
режим --log-level=error с жёсткими настройками). Вместо
этого:
Это особенно важно для больших проектов, где единичная ошибка не должна блокировать анализ остальных модулей.
При API-использовании результат сборки содержит массив
errors, доступный для постобработки:
const result = await esbuild.build({ /* config */ })
console.log(result.errors)
Корректное заполнение location критически важно для
удобства отладки. Esbuild поддерживает:
file)line)column)Отсутствие точной позиции приводит к менее информативным сообщениям, особенно при трансформации кода.
Пример улучшенной локализации:
this.error({
text: "Синтаксическая ошибка в выражении",
location: {
file: args.path,
line: error.line,
column: error.column
}
})
Плагины часто используют асинхронные операции: чтение файлов, запросы к API, вычисления.
В таких случаях ошибки должны обрабатываться внутри
try/catch, иначе они могут быть потеряны или приведут к
неконтролируемому падению процесса.
onLoad({ filter: /.*/ }, async (args) => {
try {
const res = await fetch(`https://api.example.com/meta?file=${args.path}`)
const json = await res.json()
return {
contents: JSON.stringify(json),
loader: "json"
}
} catch (e) {
this.error({
text: `Ошибка загрузки метаданных: ${e.message}`
})
}
})
Асинхронные ошибки особенно важны в плагинах, которые интегрируются с внешними системами.
При разработке сложных плагинов важно соблюдать предсказуемую модель ошибок:
Это позволяет другим разработчикам интегрировать плагин без необходимости разбираться в нестандартном поведении сборки.
При возврате результата из onLoad можно столкнуться с
ошибками преобразования:
Такие ошибки обычно фиксируются до возврата результата:
onLoad({ filter: /\.data$/ }, (args) => {
const raw = fs.readFileSync(args.path, "utf8")
if (!raw.startsWith("{")) {
this.error({
text: "Ожидается JSON-подобный формат"
})
}
const parsed = JSON.parse(raw)
return {
contents: JSON.stringify(parsed),
loader: "json"
}
})
Предупреждения в Esbuild часто используются не для сигнализации о проблемах, а для анализа качества кода:
Такой подход позволяет использовать плагины как инструмент статического анализа, а не только сборки.
this.warn({
text: "Импорт большого файла может замедлить сборку",
location: {
file: args.importer
}
})
Esbuild выполняет сборку параллельно, поэтому ошибки из разных потоков могут приходить не в строгом порядке. Это влияет на:
Плагины должны быть независимыми и не полагаться на глобальное состояние для диагностики, иначе ошибки становятся недетерминированными.
Для сложных плагинов полезно изолировать ошибки на уровне функций:
function safeParse(input, ctx) {
try {
return JSON.parse(input)
} catch (e) {
ctx.error({
text: "Ошибка парсинга JSON"
})
return null
}
}
Это снижает вероятность распространения ошибки по цепочке обработки.
Один модуль может генерировать несколько ошибок в разных фазах обработки. Esbuild агрегирует их, не останавливаясь после первой.
Это поведение важно учитывать при:
Каждая ошибка должна быть самодостаточной и не зависеть от контекста предыдущих сообщений, чтобы сохранять читаемость отчёта диагностики.