esbuild-wasm — это порт высокопроизводительного бандлера esbuild, скомпилированного в WebAssembly, предназначенный для выполнения сборки и трансформации JavaScript/TypeScript прямо в браузерной среде. В отличие от классического Node.js-ориентированного esbuild, wasm-версия работает в условиях ограниченного окружения: отсутствует файловая система, нет доступа к нативным потокам и процессам, а взаимодействие с ресурсами происходит через асинхронные загрузчики и виртуальные источники.
Основная цель esbuild-wasm — обеспечить максимально быстрый bundling и transpiling в браузере, сохраняя ключевую философию esbuild: минимальные накладные расходы, высокая скорость парсинга и параллельная обработка модулей внутри WebAssembly-модуля.
Перед выполнением любых операций сборки необходимо инициализировать
wasm-движок. Это обязательный шаг, так как бинарный модуль загружается
асинхронно и должен быть подготовлен перед вызовами build
или transform.
import * as esbuild from "esbuild-wasm";
await esbuild.initialize({
wasmURL: "https://unpkg.com/esbuild-wasm/esbuild.wasm",
worker: true
});
wasmURL
.wasm файлуworker
Инициализация выполняется один раз за жизненный цикл приложения. Повторный вызов initialize не требуется и может привести к конфликтам состояния.
esbuild-wasm предоставляет API build, который в браузере
работает не с файловой системой, а с виртуальными модулями, загружаемыми
через плагины.
const result = await esbuild.build({
entryPoints: ["index.js"],
bundle: true,
write: false,
format: "esm",
platform: "browser",
plugins: []
});
console.log(result.outputFiles[0].text);
Однако в браузере такой вызов в чистом виде невозможен без определения загрузки модулей. Поэтому ключевая часть работы — это плагины onResolve и onLoad.
В отличие от Node.js, где fs предоставляет доступ к
файлам, в браузере esbuild-wasm использует модель:
Каждый импорт трактуется как запрос, который должен быть обработан плагином.
const httpPlugin = {
name: "http",
setup(build) {
build.onResolve({ filter: /^https?:\/\// }, args => {
return { path: args.path, namespace: "http" };
});
build.onLoad({ filter: /.*/, namespace: "http" }, async (args) => {
const res = await fetch(args.path);
const text = await res.text();
return {
contents: text,
loader: "tsx"
};
});
}
};
onResolve перехватывает импортonLoad загружает содержимоеОдним из ключевых сценариев использования esbuild-wasm является сборка кода прямо из CDN или удалённых источников.
Пример:
await esbuild.build({
entryPoints: ["https://example.com/app/index.js"],
bundle: true,
format: "esm",
platform: "browser",
plugins: [httpPlugin]
});
Здесь весь граф модулей может быть построен динамически, включая вложенные импорты.
esbuild-wasm из коробки поддерживает:
.ts, .tsx)Пример:
const result = await esbuild.transform(`
const App = () => <div>Hello</div>;
`, {
loader: "tsx"
});
Используется для одиночных файлов или строк кода.
const result = await esbuild.transform(code, {
loader: "ts",
minify: true,
sourcemap: "inline"
});
Применения:
Используется для графа зависимостей.
Несмотря на мощь, esbuild-wasm имеет ряд ограничений.
Нет доступа к:
fsВсё заменяется на:
В Node.js esbuild может использовать нативные расширения. В wasm-версии:
WebAssembly работает в ограниченном heap:
await esbuild.initialize({
wasmURL: "/esbuild.wasm",
worker: true
});
Worker позволяет:
Кеширование в plugin layer:
const cache = new Map();
build.onLoad({ filter: /.*/ }, async (args) => {
if (cache.has(args.path)) {
return cache.get(args.path);
}
const res = await fetch(args.path);
const text = await res.text();
const result = {
contents: text,
loader: "js"
};
cache.set(args.path, result);
return result;
});
await esbuild.build({
entryPoints: ["app.js"],
bundle: true,
minify: true,
treeShaking: true,
format: "esm"
});
Механизм:
esbuild-wasm поддерживает генерацию sourcemap прямо в браузере:
await esbuild.build({
entryPoints: ["app.ts"],
bundle: true,
sourcemap: "inline",
format: "esm"
});
Режимы:
esbuild корректно обрабатывает:
const module = await import("./module.js");
В браузере это требует корректного resolution через
onResolve:
build.onResolve({ filter: /^\./ }, args => {
return {
path: new URL(args.path, args.resolveDir + "/").href,
namespace: "http"
};
});
Namespaces позволяют разделять источники:
file — виртуальные файлыhttp — удалённые модулиinline — строки кодаmemory — кешированные модулиПример:
build.onResolve({ filter: /.*/ }, args => {
if (args.path.startsWith("memory:")) {
return { path: args.path, namespace: "memory" };
}
});
esbuild-wasm часто применяется в:
Типичная архитектура:
Сборка результата часто выводится в iframe:
const output = result.outputFiles[0].text;
iframe.sr cdoc = `
<!DOCTYPE html>
<html>
<body>
<script type="module">
${output}
</script>
</body>
</html>
`;
Это позволяет мгновенно отражать изменения кода без серверной сборки.
esbuild возвращает структурированные ошибки:
try {
await esbuild.build({...});
} catch (e) {
console.error(e.errors);
}
Структура ошибки:
| Характеристика | Node.js | esbuild-wasm |
|---|---|---|
| Производительность | выше | высокая |
| Файловая система | есть | отсутствует |
| Плагины | нативные + JS | только JS |
| Среда | сервер | браузер |
| Worker support | нет | есть |