В системе плагинов esbuild пространство имён (namespace) используется как механизм изоляции и маршрутизации модулей на этапе разрешения и загрузки. Оно позволяет разделять источники модулей по логическим каналам, не привязываясь к файловой системе. Виртуальные модули строятся именно на этой основе: они существуют только в памяти сборщика и никогда не появляются как реальные файлы на диске.
Namespace задаётся на уровне результатов хуков onResolve
и onLoad. Каждый модуль, проходящий через систему плагинов,
получает метку пространства имён, которая влияет на дальнейшие шаги
резолва, загрузки и трансформации.
Внутри esbuild модуль описывается не только путём, но и контекстом:
Обычные файлы используют namespace по умолчанию — file.
Однако плагины могут вводить любые собственные пространства имён:
virtualhttpmemcss-moduleembeddedКаждое пространство имён становится независимой “вселенной” для модулей.
Ключевая идея: два модуля с одинаковым path, но разным
namespace, считаются разными сущностями.
Виртуальный модуль создаётся через связку onResolve →
onLoad.
onResolve({ filter: /^virtual:/ }, args => {
return {
path: args.path,
namespace: "virtual"
}
})
Здесь любой импорт вида:
import data from "virtual:config"
перенаправляется в пространство имён virtual.
onLoad({ filter: /.*/, namespace: "virtual" }, args => {
return {
contents: `export default { mode: "memory" }`,
loader: "js"
}
})
На этом этапе esbuild не обращается к файловой системе. Модуль полностью формируется из строки, функции или вычисленного результата.
Внутренний конвейер esbuild при работе с namespace выглядит следующим образом:
onResolve определяет namespace и новый путьonLoad для
соответствующего namespaceВиртуальные модули участвуют в графе зависимостей так же, как обычные файлы.
Namespace позволяет разделять источники данных без конфликтов.
onResolve({ filter: /^https:/ }, args => {
return {
path: args.path,
namespace: "http"
}
})
onLoad({ filter: /.*/, namespace: "http" }, async (args) => {
const res = await fetch(args.path)
const text = await res.text()
return {
contents: text,
loader: "js"
}
})
Здесь каждый URL становится модулем, но не пересекается с файловыми путями.
Одно из типичных применений namespace — генерация конфигурационных модулей.
onResolve({ filter: /^app:config$/ }, () => ({
path: "app:config",
namespace: "virtual-config"
}))
onLoad({ filter: /.*/, namespace: "virtual-config" }, () => {
const config = {
debug: true,
version: "1.0.0",
features: ["auth", "cache"]
}
return {
contents: `export default ${JSON.stringify(config)}`,
loader: "js"
}
})
Такой модуль ведёт себя как обычный ES-модуль:
import config from "app:config"
но физически не существует.
Namespace выполняет роль барьера между различными стратегиями загрузки.
Без namespace:
onLoadС namespace:
onLoad становятся точнымиesbuild кэширует модули по паре:
(path, namespace)
Это означает:
path в разных namespace не пересекаетсяonLoad может не вызываться при идентичном
запросеВиртуальные модули часто используются для создания новых “типов” импорта:
onResolve({ filter: /\.config$/ }, args => ({
path: args.path,
namespace: "config"
}))
onLoad({ filter: /.*/, namespace: "config" }, args => {
const data = { parsed: true }
return {
contents: `export default ${JSON.stringify(data)}`,
loader: "js"
}
})
onResolve({ filter: /\.svg$/ }, args => ({
path: args.path,
namespace: "svg"
}))
onLoad({ filter: /.*/, namespace: "svg" }, () => {
return {
contents: `export default "<svg></svg>"`,
loader: "text"
}
})
Здесь namespace превращает статический ресурс в программный интерфейс.
Namespace позволяет создавать модули на основе вычислений во время сборки.
onResolve({ filter: /^gen:/ }, args => ({
path: args.path,
namespace: "generated"
}))
onLoad({ filter: /.*/, namespace: "generated" }, (args) => {
const value = args.path.replace("gen:", "").toUpperCase()
return {
contents: `export const value = "${value}"`,
loader: "js"
}
})
Импорт:
import { value } from "gen:hello"
Результат:
export const value = "HELLO"
Namespace часто используется для реализации удалённых источников:
Принцип одинаков: onResolve маркирует источник,
onLoad извлекает содержимое.
Namespace не заменяет loader, а определяет контекст загрузки.
Порядок обработки:
namespace определяет обработчик onLoad
contents передаётся в loader
loader определяет тип обработки:
jststextjsonрезультат попадает в трансформационный пайплайн esbuild
Таким образом, namespace — это верхний уровень маршрутизации, а loader — нижний уровень интерпретации.
При проектировании плагинов namespace используется для:
filterПлагин с корректно выбранными namespace обычно требует меньше условий
в onLoad, поскольку сам namespace уже выполняет первичную
классификацию.
Один плагин может обслуживать несколько пространств имён:
onLoad({ namespace: "http" }, handlerA)
onLoad({ namespace: "virtual" }, handlerB)
onLoad({ namespace: "generated" }, handlerC)
Это позволяет строить единый слой маршрутизации, где каждый источник данных изолирован, но управляется одним механизмом.
Граф зависимостей esbuild строится с учётом namespace как части идентификатора узла. Это влияет на:
Модуль с одинаковым путём, но другим namespace, всегда формирует отдельную ветку графа.
Namespace не является механизмом безопасности или sandbox:
Его роль строго сборочная:
Также namespace не предназначен для хранения больших данных; он лишь указывает, откуда и как их получать.
Виртуальные модули на базе namespace формируют слой абстракции между:
Этот слой позволяет превращать сборку в программируемую систему генерации модулей, где каждый импорт может быть не статическим файлом, а результатом функции, запроса или вычисления.