esbuild-wasm: сборка прямо в браузере

esbuild-wasm — это порт высокопроизводительного бандлера esbuild, скомпилированного в WebAssembly, предназначенный для выполнения сборки и трансформации JavaScript/TypeScript прямо в браузерной среде. В отличие от классического Node.js-ориентированного esbuild, wasm-версия работает в условиях ограниченного окружения: отсутствует файловая система, нет доступа к нативным потокам и процессам, а взаимодействие с ресурсами происходит через асинхронные загрузчики и виртуальные источники.

Основная цель esbuild-wasm — обеспечить максимально быстрый bundling и transpiling в браузере, сохраняя ключевую философию esbuild: минимальные накладные расходы, высокая скорость парсинга и параллельная обработка модулей внутри WebAssembly-модуля.


Подключение и инициализация WebAssembly-движка

Перед выполнением любых операций сборки необходимо инициализировать wasm-движок. Это обязательный шаг, так как бинарный модуль загружается асинхронно и должен быть подготовлен перед вызовами build или transform.

import * as esbuild from "esbuild-wasm";

await esbuild.initialize({
  wasmURL: "https://unpkg.com/esbuild-wasm/esbuild.wasm",
  worker: true
});

Ключевые параметры initialize

wasmURL

  • Указывает путь к .wasm файлу
  • Может быть локальным или CDN-адресом
  • Обязателен для загрузки движка

worker

  • Перенос вычислений в Web Worker
  • Существенно снижает блокировку main thread
  • Рекомендуется включать в продакшене

Инициализация выполняется один раз за жизненный цикл приложения. Повторный вызов initialize не требуется и может привести к конфликтам состояния.


Основная модель сборки: build API

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 использует модель:

  • входные точки (entry points)
  • виртуальные пути
  • загрузка через fetch или inline-строки
  • кеширование модулей в памяти

Каждый импорт трактуется как запрос, который должен быть обработан плагином.


Плагины: основа работы в браузере

Структура плагина

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"
      };
    });
  }
};

Логика работы

  1. onResolve перехватывает импорт
  2. определяется namespace (например, http, file, virtual)
  3. onLoad загружает содержимое
  4. esbuild компилирует модуль в граф зависимостей

Поддержка HTTP-импортов

Одним из ключевых сценариев использования esbuild-wasm является сборка кода прямо из CDN или удалённых источников.

Пример:

await esbuild.build({
  entryPoints: ["https://example.com/app/index.js"],
  bundle: true,
  format: "esm",
  platform: "browser",
  plugins: [httpPlugin]
});

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


Поддержка TypeScript и JSX

esbuild-wasm из коробки поддерживает:

  • TypeScript (.ts, .tsx)
  • JSX / TSX
  • современный JavaScript (ESNext)
  • JSX трансформации без Babel

Пример:

const result = await esbuild.transform(`
  const App = () => <div>Hello</div>;
`, {
  loader: "tsx"
});

Особенности трансформации

  • отсутствие type-checking (только синтаксическая трансформация)
  • удаление типов TypeScript без валидации
  • быстрая AST-компиляция внутри wasm

Режим transform vs build

transform

Используется для одиночных файлов или строк кода.

const result = await esbuild.transform(code, {
  loader: "ts",
  minify: true,
  sourcemap: "inline"
});

Применения:

  • редакторы кода в браузере
  • live preview
  • подсветка и быстрый transpile

build

Используется для графа зависимостей.

  • поддерживает bundling
  • работает с плагинами
  • возвращает outputFiles

Ограничения браузерной версии

Несмотря на мощь, esbuild-wasm имеет ряд ограничений.

Отсутствие файловой системы

Нет доступа к:

  • fs
  • абсолютным путям
  • локальным директориям

Всё заменяется на:

  • fetch
  • inline modules
  • виртуальные источники

Отсутствие нативных бинарных плагинов

В Node.js esbuild может использовать нативные расширения. В wasm-версии:

  • только JS-плагины
  • ограниченная производительность плагинов
  • отсутствие низкоуровневого доступа

Ограничения памяти

WebAssembly работает в ограниченном heap:

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

Оптимизация производительности

Использование worker режима

await esbuild.initialize({
  wasmURL: "/esbuild.wasm",
  worker: true
});

Worker позволяет:

  • не блокировать UI
  • параллелить сборку
  • уменьшить лаги при интерактивной работе

Кеширование модулей

Кеширование в 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;
});

Минификация и tree-shaking

await esbuild.build({
  entryPoints: ["app.js"],
  bundle: true,
  minify: true,
  treeShaking: true,
  format: "esm"
});

Механизм:

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

Работа с sourcemap

esbuild-wasm поддерживает генерацию sourcemap прямо в браузере:

await esbuild.build({
  entryPoints: ["app.ts"],
  bundle: true,
  sourcemap: "inline",
  format: "esm"
});

Режимы:

  • inline — встроенный map
  • external — отдельный файл
  • none — отключено

Поддержка динамических импортов

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

Namespaces позволяют разделять источники:

  • file — виртуальные файлы
  • http — удалённые модули
  • inline — строки кода
  • memory — кешированные модули

Пример:

build.onResolve({ filter: /.*/ }, args => {
  if (args.path.startsWith("memory:")) {
    return { path: args.path, namespace: "memory" };
  }
});

Использование в редакторах кода

esbuild-wasm часто применяется в:

  • онлайн IDE
  • песочницах JavaScript
  • live preview системах

Типичная архитектура:

  1. редактор (Monaco / CodeMirror)
  2. esbuild-wasm worker
  3. plugin loader (HTTP / memory)
  4. preview iframe

Интеграция с iframe preview

Сборка результата часто выводится в 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);
}

Структура ошибки:

  • текстовое описание
  • позиция в коде
  • стек модулей
  • тип ошибки (syntax / resolve / transform)

Сравнение с Node.js esbuild

Характеристика Node.js esbuild-wasm
Производительность выше высокая
Файловая система есть отсутствует
Плагины нативные + JS только JS
Среда сервер браузер
Worker support нет есть

Типовые сценарии использования

  • браузерные bundler playground
  • обучение JavaScript/TypeScript
  • онлайн-конструкторы UI
  • sandbox для npm-пакетов через CDN
  • live coding environments

Поток обработки сборки

  1. initialize wasm runtime
  2. запуск build/transform
  3. resolve entry points
  4. onResolve граф зависимостей
  5. onLoad загрузка модулей
  6. компиляция AST
  7. генерация bundle
  8. возврат outputFiles
  9. рендер результата в UI