Опция platform: node и её последствия

Одной из ключевых настроек Esbuild является опция platform, определяющая целевую среду выполнения собранного кода. От неё зависит не только формат выходного файла, но и множество внутренних решений сборщика: обработка встроенных модулей, выбор условий экспорта пакетов, генерация совместимого кода и стратегия разрешения зависимостей.

Для Node.js используется значение:

await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  platform: 'node',
  outfile: 'dist/app.js'
});

При установке platform: 'node' Esbuild начинает рассматривать проект как серверное приложение, а не как код, предназначенный для браузера.


Отличия от других платформ

Esbuild поддерживает три основных платформы:

Платформа Назначение
browser Веб-приложения
node Приложения Node.js
neutral Универсальная среда без специальных предположений

Пример:

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

и

await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  platform: 'node'
});

могут создать принципиально разные результаты даже при одинаковом исходном коде.


Автоматическая работа со встроенными модулями Node.js

Одно из важнейших последствий использования platform: 'node' связано со встроенными модулями Node.js.

Например:

import fs from 'fs';
import path from 'path';

При браузерной сборке такие зависимости вызовут ошибку или потребуют полифилов.

При использовании Node-платформы Esbuild понимает, что данные модули существуют в среде выполнения, и обращается с ними соответствующим образом.

await esbuild.build({
  bundle: true,
  platform: 'node'
});

В результате модуль fs не будет заменён браузерными заглушками.


Автоматический выбор условий exports

Современные npm-пакеты часто используют поле exports.

Пример:

{
  "exports": {
    "browser": "./browser.js",
    "node": "./node.js",
    "default": "./index.js"
  }
}

Если указано:

platform: 'node'

Esbuild будет искать условие:

"node"

и выберет:

./node.js

При браузерной сборке был бы выбран другой файл.

Это позволяет пакетам поставлять специализированные версии для различных сред выполнения.


Влияние на поле mainFields

При разрешении зависимостей Esbuild анализирует несколько полей в package.json.

Например:

{
  "main": "./dist/index.js",
  "module": "./dist/index.mjs"
}

Для Node-платформы используется порядок поиска, ориентированный на серверную среду.

Фактически Esbuild старается выбрать вариант, который наиболее корректно соответствует работе Node.js.

Это особенно важно при работе со смешанными CommonJS и ES-модулями.


Генерация CommonJS по умолчанию

При использовании Node.js исторически доминировал формат CommonJS.

Поэтому Esbuild автоматически подбирает формат, который лучше соответствует среде Node.

Например:

await esbuild.build({
  platform: 'node',
  bundle: true,
  outfile: 'dist/app.js'
});

Для старых версий Node результат обычно генерируется в стиле CommonJS.

Получаемый код может содержать:

module.exports = value;

или

exports.run = run;

Однако поведение зависит и от параметра format.


Совместное использование с format

Часто платформа указывается вместе с форматом модулей.

CommonJS

await esbuild.build({
  platform: 'node',
  format: 'cjs',
  bundle: true
});

Результат:

const lib = require('./lib');

ESM

await esbuild.build({
  platform: 'node',
  format: 'esm',
  bundle: true
});

Результат:

import lib from './lib.js';

Такой вариант подходит для современных проектов Node.js, использующих:

{
  "type": "module"
}

Работа с require()

Node.js активно использует CommonJS.

Исходный код:

const express = require('express');

При настройке:

platform: 'node'

Esbuild сохраняет корректную совместимость с подобными конструкциями.

Это особенно полезно при миграции старых проектов, где одновременно присутствуют:

require(...)

и

import ...

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

Node.js поддерживает динамическую загрузку модулей.

Исходный код:

const module = await import('./feature.js');

При использовании Node-платформы Esbuild учитывает возможности среды выполнения и не пытается преобразовать конструкцию в браузерный аналог.


Обработка переменной process

В Node.js глобально существует объект:

process

Например:

console.log(process.version);

или

console.log(process.env.NODE_ENV);

При платформе browser обычно требуется дополнительная подстановка значений.

При платформе node Esbuild предполагает наличие объекта process во время выполнения.


Работа с __dirname

Node.js предоставляет специальные переменные:

__dirname

и

__filename

Пример:

console.log(__dirname);

При сборке для Node Esbuild старается сохранить ожидаемое поведение подобных конструкций.

Для браузерной среды такие возможности отсутствуют.


Работа с файловой системой

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

Пример:

import fs from 'fs';

const text = fs.readFileSync('config.json', 'utf8');

При использовании:

platform: 'node'

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

Код остаётся пригодным для выполнения внутри Node.js.


Взаимодействие с пакетами, ориентированными на сервер

Множество библиотек существует исключительно для Node.js.

Например:

import express from 'express';
import bcrypt from 'bcrypt';
import pg from 'pg';

Такие пакеты часто используют:

  • файловую систему;
  • сокеты;
  • потоки;
  • системные вызовы;
  • встроенные модули Node.

Сборка через:

platform: 'node'

позволяет Esbuild корректно работать с подобными зависимостями.


Поведение external-зависимостей

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

Пример:

await esbuild.build({
  bundle: true,
  platform: 'node',
  external: ['express']
});

В результате:

require('express');

останется во внешнем виде.

Модуль будет загружен Node.js во время выполнения.

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


Сборка серверного приложения

Типичная конфигурация:

import esbuild from 'esbuild';

await esbuild.build({
  entryPoints: ['src/server.js'],
  bundle: true,
  platform: 'node',
  target: 'node20',
  outfile: 'dist/server.js'
});

Особенности такой сборки:

  • учитываются возможности Node 20;
  • сохраняется совместимость со встроенными API;
  • корректно разрешаются серверные зависимости;
  • отсутствуют браузерные полифилы.

Использование target вместе с platform

Параметр platform определяет среду выполнения, а target — её версию.

Пример:

platform: 'node',
target: 'node18'

или

platform: 'node',
target: 'node22'

Esbuild анализирует поддерживаемые возможности выбранной версии Node и решает, какие преобразования необходимы.

Например, современные конструкции:

class User {
  #name;
}

могут остаться без изменений при достаточно новой версии Node.


Работа с нативными расширениями

Некоторые пакеты содержат бинарные компоненты:

.node

Примеры:

  • sqlite3
  • sharp
  • bcrypt
  • node-rdkafka

При использовании platform: 'node' Esbuild понимает, что код будет выполняться внутри Node.js, однако сами бинарные файлы автоматически в бандл не встраиваются.

Поэтому после сборки может потребоваться копирование дополнительных ресурсов.


Влияние на tree shaking

Tree shaking продолжает работать и в Node-проектах.

Исходный модуль:

export function used() {}

export function unused() {}

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

import { used } from './utils.js';

used();

После сборки неиспользуемый код будет удалён.

Платформа Node не отключает механизм удаления мёртвого кода.


Особенности работы с ESM-проектами

Современные серверные приложения всё чаще используют:

{
  "type": "module"
}

В таком случае обычно применяется конфигурация:

await esbuild.build({
  platform: 'node',
  format: 'esm',
  bundle: true
});

Это позволяет сохранить естественную поддержку:

import
export
await import()

без возврата к CommonJS.


Когда следует использовать platform: node

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

  • HTTP-серверов;
  • REST API;
  • GraphQL-серверов;
  • CLI-приложений;
  • микросервисов;
  • фоновых задач;
  • систем автоматизации;
  • утилит командной строки;
  • серверного рендеринга;
  • приложений Electron на стороне main process.

Типичный пример:

await esbuild.build({
  entryPoints: ['src/cli.js'],
  bundle: true,
  platform: 'node',
  target: 'node22',
  outfile: 'bin/cli.js'
});

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