Встроенные полифилы для browser-платформы

Браузерная платформа изначально не содержит значительной части API, которые присутствуют в Node.js. В контексте сборки через esbuild это приводит к важному архитектурному различию: зависимости, рассчитанные на серверную среду, не могут быть использованы в браузере без адаптации.

Ключевые отсутствующие в браузере модули и глобальные объекты:

  • process (включая process.env)
  • Buffer
  • модули path, fs, os, crypto (Node-реализация)
  • глобальные переменные Node.js (__dirname, __filename)
  • встроенные утилиты потоков и файловой системы

Esbuild не выполняет автоматическую подмену этих API на полифилы. Это принципиальное решение: сборщик ориентируется на скорость и минимальную трансформацию кода, а не на полноценную эмуляцию среды исполнения.


Режим platform: "browser" и его ограничения

Конфигурация сборки с параметром:

platform: "browser"

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

Основные эффекты:

  • предпочтение браузерных полей в package.json
  • использование browser-версий зависимостей (если они указаны)
  • отключение некоторых Node-специфичных предположений при бандлинге

Важно: сам по себе этот режим не обеспечивает совместимость Node API с браузером.


Резолвинг через поле browser в package.json

Многие npm-пакеты предоставляют альтернативную реализацию для браузера:

{
  "main": "dist/index.js",
  "browser": "dist/browser.js"
}

Esbuild при platform: "browser" автоматически:

  • выбирает поле browser, если оно присутствует
  • использует fallback на module или main, если browser отсутствует

Также поддерживаются маппинги вида:

{
  "browser": {
    "fs": false,
    "path": "./shims/path-browser.js"
  }
}

Такой механизм позволяет:

  • полностью исключить Node-модуль (false)
  • заменить модуль на браузерную реализацию

Это базовый уровень полифилинга на уровне резолвинга, без вмешательства в код исполнения.


Отсутствие автоматических полифилов Node.js API

Esbuild сознательно не включает встроенные полифилы. Причины:

  • увеличение размера бандла
  • несовместимость различных реализаций shim-пакетов
  • сложность поддержки актуального Node API
  • необходимость детерминированной и быстрой сборки

В результате любые Node-API в браузере требуют явной стратегии замещения.


Подмена глобальных переменных через define

Механизм define используется для статической подстановки значений на этапе сборки.

Пример замены process.env.NODE_ENV:

esbuild.build({
  entryPoints: ["src/index.js"],
  bundle: true,
  platform: "browser",
  define: {
    "process.env.NODE_ENV": '"production"'
  }
});

Характеристики механизма:

  • выполняет текстовую замену на этапе компиляции
  • не создаёт runtime-объект process
  • подходит только для константных значений
  • не эмулирует поведение Node.js

Часто используется для устранения зависимостей, которые проверяют окружение через process.env.


Полифил process

Многие библиотеки ожидают наличие process, особенно process.env и process.nextTick.

Проблема: в браузере process отсутствует полностью.

Решение через inject:

import process from "process/browser";

esbuild.build({
  entryPoints: ["src/app.js"],
  bundle: true,
  platform: "browser",
  inject: ["./shims/process.js"]
});

shims/process.js:

import process from "process/browser";
export { process };

Альтернативный подход — использование готового пакета:

  • process (npm пакет с browser-реализацией)

Полифил Buffer

Buffer активно используется в криптографии, сетевых библиотеках и кодировках.

Браузерная замена:

import { Buffer } from "buffer";

window.Buffer = Buffer;

Интеграция через esbuild:

inject: ["./shims/buffer.js"]

buffer.js:

import { Buffer } from "buffer";
globalThis.Buffer = Buffer;

Особенности:

  • обеспечивает совместимость с кодом Node.js
  • увеличивает размер бандла
  • часто требует дополнительного полифила process

Полифилы стандартных Node модулей

path

import path from "path-browserify";

Используется для:

  • нормализации путей
  • работы с URL-подобными строками

crypto

import crypto from "crypto-browserify";

Особенности:

  • частично зависит от WebCrypto API
  • может иметь ограничения производительности

stream

import { Readable } from "stream-browserify";

Применяется в потоковой обработке данных.


util

import util from "util/";

Частично эмулирует Node API.


Использование inject как механизм глобальных shim-подстановок

inject — основной механизм подключения полифилов в esbuild.

Пример конфигурации:

esbuild.build({
  entryPoints: ["src/index.js"],
  bundle: true,
  platform: "browser",
  inject: [
    "./shims/process.js",
    "./shims/buffer.js"
  ]
});

Принцип работы:

  • файл выполняется до основного кода
  • экспортируемые символы становятся доступными глобально
  • не требуется явного импорта в каждом модуле

Ограничения:

  • не заменяет динамические require
  • не работает для runtime-генерации импортов
  • может приводить к неявным зависимостям

Использование resolve.alias через плагины

Esbuild не имеет встроенного alias API, но поддерживает плагины.

Пример замены Node модулей:

const polyfillNode = {
  name: "polyfill-node",
  setup(build) {
    build.onResolve({ filter: /^path$/ }, () => ({
      path: "path-browserify"
    }));
  }
};

Использование:

esbuild.build({
  entryPoints: ["src/index.js"],
  bundle: true,
  platform: "browser",
  plugins: [polyfillNode]
});

Такой подход позволяет:

  • точечно заменять Node модули
  • контролировать стратегию полифилов
  • избегать глобальных inject

Готовые наборы полифилов

На практике используются комплексные наборы:

node-polyfills через плагины

  • esbuild-plugin-polyfill-node
  • node-stdlib-browser
  • esbuild-plugin-node-globals

Они обычно включают:

  • process
  • Buffer
  • crypto
  • stream
  • assert
  • url

Разделение зависимостей на browser-safe и node-only

При работе с esbuild важно учитывать структуру зависимостей:

  • browser-safe пакеты используют Web API
  • node-only требуют полифилов или исключения

Типичная стратегия:

resolve: {
  alias: {
    fs: false,
    net: false,
    tls: false
  }
}

Исключение модулей:

  • предотвращает попадание серверного кода в бандл
  • уменьшает размер итоговой сборки
  • снижает количество runtime-ошибок

Conditional exports и современный npm-экосистемный слой

Многие пакеты используют exports:

{
  "exports": {
    "browser": "./dist/browser.js",
    "node": "./dist/node.js"
  }
}

Esbuild учитывает это при platform: "browser", но:

  • не выполняет полную эмуляцию Node resolution
  • может требовать дополнительных настроек при сложных условиях экспорта

Практика комбинирования полифилов

Типовая конфигурация браузерной сборки:

esbuild.build({
  entryPoints: ["src/index.js"],
  bundle: true,
  platform: "browser",
  target: "es2018",
  define: {
    "process.env.NODE_ENV": '"production"'
  },
  inject: [
    "./shims/process.js",
    "./shims/buffer.js"
  ]
});

Дополнительные меры:

  • отключение fs, net, tls
  • использование browser поля пакетов
  • подключение polyfill-плагинов при необходимости

Ограничения и стоимость полифилинга

Подключение полифилов приводит к ряду последствий:

  • увеличение размера бандла
  • рост времени инициализации
  • снижение предсказуемости исполнения
  • возможные расхождения с Node.js поведением

Некоторые библиотеки используют глубокую зависимость от Node API, что делает их частично несовместимыми даже с полифилами.


Поведенческие различия между Node и браузером

Даже при наличии полифилов сохраняются фундаментальные различия:

  • асинхронность event loop отличается от Node
  • файловая система недоступна как концепт
  • криптографические операции могут использовать WebCrypto вместо OpenSSL
  • потоковая модель отличается от Node streams

Эти различия невозможно полностью устранить средствами esbuild или shim-библиотек.