Spec-Zone.ru › esbuild

API

К API можно получить доступ на одном из трёх языков: в командной строке, на JavaScript и на Go. Концепции и параметры в основном идентичны для всех трёх языков, поэтому они будут представлены здесь вместе, вместо того, чтобы иметь отдельные документации для каждого языка. Вы можете переключаться между языками, используя вкладки CLI, JS и Go в правом верхнем углу каждого примера кода. Некоторые особенности для каждого языка:

  • Командная строка (CLI): Если вы используете API командной строки, вам может быть полезно знать, что флаги представлены в одной из трёх форм: --foo, --foo=bar или --foo:bar. Форма --foo используется для включения булевых флагов, таких как --minify, форма --foo=bar используется для флагов, имеющих одно значение и указываемых только один раз, таких как --platform=, а форма --foo:bar используется для флагов, имеющих несколько значений и которые могут быть указаны несколько раз, таких как --external:.

    Также имейте в виду, что использование CLI (вообще, а не специфичного для esbuild) означает, что ваша текущая оболочка интерпретирует аргументы команды перед тем, как команда, которую вы выполняете, увидит их. Например, даже если команда echo просто выводит то, что она считывает, echo "foo" может вывести foo вместо "foo", а echo *.json может вывести package.json вместо *.json (конкретное поведение зависит от используемой оболочки). Если вы хотите избежать проблем, которые могут вызывать особенности работы оболочки, то вы должны использовать JavaScript или Go API esbuild вместо CLI esbuild.

  • JavaScript: Если вы используете JavaScript, обязательно ознакомьтесь с разделами Подробности для JavaScript и браузер ниже. Вам также может быть полезной TypeScript документация типов для esbuild в качестве справочника.

  • Go: Если вы используете Go, вам может быть полезной автоматически сгенерированная документация Go для esbuild. Существует отдельная документация для обоих публичных пакетов Go: pkg/api и pkg/cli.

Обзор

Два наиболее часто используемых API esbuild — это build и transform. Каждый из них описан ниже на высоком уровне, а затем представлена документация для каждого отдельного варианта API.

Build

Это основной интерфейс для esbuild. Обычно вы передаёте один или несколько файлов точек входа для обработки вместе с различными параметрами, а затем esbuild записывает результаты обратно в файловую систему. Вот простой пример, который включает сборку с каталогом вывода:

esbuild app.ts --bundle --outdir=dist
import * as esbuild from 'esbuild'

let result = await esbuild.build({
  entryPoints: ['app.ts'],
  bundle: true,
  outdir: 'dist',
})
console.log(result)
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Bundle:      true,
    Outdir:      "dist",
  })
  if len(result.Errors) != 0 {
    os.Exit(1)
  }
}

Расширенное использование API build предполагает настройку долгоживущего контекста сборки. Этот контекст является явным объектом в JS и Go, но неявным для CLI. Все сборки, выполненные с заданным контекстом, используют одни и те же параметры сборки, а последующие сборки выполняются инкрементально (т. е. они повторно используют часть работы из предыдущих сборок для повышения производительности). Это полезно для разработки, потому что esbuild может перестраивать ваше приложение в фоновом режиме, пока вы работаете.

Существует три разных API инкрементальной сборки:

  • Режим наблюдения сообщает esbuild следить за файловой системой и автоматически перестраивать её всякий раз, когда вы редактируете и сохраняете файл, который может повлиять на сборку. Вот пример:
esbuild app.ts --bundle --outdir=dist --watch
[watch] build finished, watching for changes...
let ctx = await esbuild.context({
  entryPoints: ['app.ts'],
  bundle: true,
  outdir: 'dist',
})

await ctx.watch()
ctx, err := api.Context(api.BuildOptions{
  EntryPoints: []string{"app.ts"},
  Bundle:      true,
  Outdir:      "dist",
})

err2 := ctx.Watch(api.WatchOptions{})
  • Режим предоставления запускает локальный сервер разработки, который предоставляет результаты последней сборки. Входящие запросы автоматически запускают новые сборки, поэтому ваш веб-приложение всегда будет обновлённым при перезагрузке страницы в браузере. Вот пример:
esbuild app.ts --bundle --outdir=dist --serve

 > Local:   http://127.0.0.1:8000/
 > Network: http://192.168.0.1:8000/

127.0.0.1:61302 - "GET /" 200 [1ms]
let ctx = await esbuild.context({
  entryPoints: ['app.ts'],
  bundle: true,
  outdir: 'dist',
})

let { host, port } = await ctx.serve()
ctx, err := api.Context(api.BuildOptions{
  EntryPoints: []string{"app.ts"},
  Bundle:      true,
  Outdir:      "dist",
})

server, err2 := ctx.Serve(api.ServeOptions{})
  • Режим перестроения позволяет вручную вызвать сборку. Это полезно при интеграции esbuild с другими инструментами (например, при использовании пользовательского наблюдателя файлов или сервера разработки вместо встроенных в esbuild). Вот пример:
# The CLI does not have an API for "rebuild"
let ctx = await esbuild.context({
  entryPoints: ['app.ts'],
  bundle: true,
  outdir: 'dist',
})

for (let i = 0; i < 5; i++) {
  let result = await ctx.rebuild()
}
ctx, err := api.Context(api.BuildOptions{
  EntryPoints: []string{"app.ts"},
  Bundle:      true,
  Outdir:      "dist",
})

for i := 0; i < 5; i++ {
  result := ctx.Rebuild()
}

Эти три API для инкрементной сборки могут быть объединены. Чтобы включить режим автоматической перезагрузки (автоматически перезагружающий страницу при редактировании и сохранении файла), необходимо включить наблюдение и сервирование вместе в одном контексте.

После завершения работы с объектом контекста, можно вызвать dispose() для объекта контекста, чтобы дождаться завершения существующих сборок, остановить режим наблюдения и/или сервирования и освободить ресурсы.

API сборки и контекста оба принимают следующие параметры:

Общие параметры:
  • Сборка
  • Отмена
  • Автоматическая перезагрузка
  • Платформа
  • Перестроение
  • Сервирование
  • Tsconfig
  • Необработанный Tsconfig
  • Наблюдение

Входные данные:
  • Точки входа
  • Загрузчик
  • Стандартный ввод

Содержимое вывода:
  • Заголовок
  • Кодировка символов
  • Подвал
  • Формат
  • Глобальное имя
  • Юридические комментарии
  • Предел строк
  • Разделение

Местоположение вывода:
  • Разрешить перезапись
  • Имена ресурсов
  • Имена фрагментов
  • Имена точек входа
  • Расширение вывода
  • База вывода
  • Директория вывода
  • Файл вывода
  • Публичный путь
  • Запись

Разрешение путей:
  • Псевдоним
  • Условия
  • Внешний
  • Основные поля
  • Пути Node
  • Пакеты
  • Сохранить символические ссылки
  • Разрешить расширения
  • Рабочая директория

Преобразование:
  • JSX
  • JSX dev
  • JSX фабрика
  • JSX фрагмент
  • JSX источник импорта
  • JSX побочные эффекты
  • Поддерживаемые
  • Целевая платформа

Оптимизация:
  • Определить
  • Удалить
  • Удалить метки
  • Игнорировать аннотации
  • Вставить
  • Сохранить имена
  • Сжать свойства
  • Минифицировать
  • Чистый
  • Удаление неиспользуемого кода

Карты исходных данных:
  • Корень исходного кода
  • Файл исходного кода
  • Карта исходного кода
  • Содержимое исходных файлов

Метаданные сборки:
  • Анализировать
  • Метафайл

Ведение журнала:
  • Цвет
  • Форматировать сообщения
  • Уровень ведения журнала
  • Лимит ведения журнала
  • Переопределение ведения журнала

Преобразование

Это ограниченный частный случай сборки, который преобразует строку кода, представляющую файл в памяти, в изолированной среде, полностью отключенной от других файлов. Общие применения включают минификацию кода и преобразование TypeScript в JavaScript. Вот пример:

echo 'let x: number = 1' | esbuild --loader=ts
let x = 1;
import * as esbuild from 'esbuild'

let ts = 'let x: number = 1'
let result = await esbuild.transform(ts, {
  loader: 'ts',
})
console.log(result)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  ts := "let x: number = 1"
  result := api.Transform(ts, api.TransformOptions{
    Loader: api.LoaderTS,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

Использование строки вместо файла в качестве входных данных более удобно для некоторых случаев использования. Изоляция файловой системы имеет определенные преимущества (например, работает в браузере, не зависит от близлежащих package.json файлов) и определенные недостатки (например, не может быть использована с сборкой или плагинами). Если ваш случай использования не подходит для API преобразования, используйте более общий API сборки.

API преобразования принимает следующие параметры:

Общие параметры:
  • Платформа
  • Необработанные настройки tsconfig

Входные данные:
  • Загрузчик

Содержание вывода:
  • Баннер
  • Кодировка символов
  • Подвал
  • Формат
  • Глобальное имя
  • Юридические комментарии
  • Предел строк

Преобразование:
  • JSX
  • JSX разработка
  • JSX фабрика
  • JSX фрагмент
  • JSX источник импорта
  • JSX побочные эффекты
  • Поддерживается
  • Целевая среда

Оптимизация:
  • Определить
  • Удалить
  • Удалить метки
  • Игнорировать аннотации
  • Сохранить имена
  • Изменить имена свойств
  • Минифицировать
  • Чистый
  • Встряхивание дерева

Карты исходных файлов:
  • Корень исходного файла
  • Исходный файл
  • Карта исходных файлов
  • Содержание исходных файлов

Ведение логов:
  • Цвет
  • Формат сообщений
  • Уровень логов
  • Предел логов
  • Переопределение логов

Особенности, специфичные для JavaScript

JS API для esbuild доступен как в асинхронном, так и в синхронном вариантах. Рекомендуется использовать асинхронный API, поскольку он работает во всех средах, быстрее и мощнее. Синхронный API работает только в Node.js и может выполнять определенные действия, но иногда необходим в узкоспециализированных ситуациях, специфичных для Node.js. Подробнее:

Асинхронный API

Вызовы асинхронного API возвращают результаты с использованием промиса. Обратите внимание, что вам, вероятно, придется использовать расширение файла .mjs в Node.js из-за использования ключевых слов import и верхнего уровня await:

import * as esbuild from 'esbuild'

let result1 = await esbuild.transform(code, options)
let result2 = await esbuild.build(options)

Преимущества:

  • Вы можете использовать плагины с асинхронным API
  • Текущая нить не блокируется, поэтому вы можете выполнять другие задачи в это время
  • Вы можете запускать множество одновременных вызовов API esbuild, которые затем распределяются между всеми доступными процессорами для максимальной производительности

Недостатки:

  • Использование промисов может привести к более сложному коду, особенно в CommonJS, где top-level await недоступен
  • Не работает в ситуациях, требующих синхронного выполнения, таких как внутри require.extensions

Синхронный API

Вызовы синхронного API возвращают результаты непосредственно:

let esbuild = require('esbuild')

let result1 = esbuild.transformSync(code, options)
let result2 = esbuild.buildSync(options)

Преимущества:

  • Отказ от промисов может привести к более чистому коду, особенно когда top-level await недоступен
  • Работает в ситуациях, требующих синхронного выполнения, таких как внутри require.extensions

Недостатки:

  • Вы не можете использовать плагины с синхронным API, поскольку плагины асинхронны
  • Это блокирует текущую нить, поэтому вы не можете выполнять другие задачи в это время
  • Использование синхронного API предотвращает распараллеливание вызовов API esbuild

В браузере

API esbuild также может работать в браузере с использованием WebAssembly в WebWorker. Для этого вам нужно установить пакет esbuild-wasm вместо пакета esbuild:

npm install esbuild-wasm

API для браузера аналогичен API для Node.js, за исключением того, что вам нужно сначала вызвать initialize() и передать URL двоичного файла WebAssembly. Синхронные версии API также недоступны. Предполагая, что вы используете бандлер, это будет выглядеть примерно так:

import * as esbuild from 'esbuild-wasm'

await esbuild.initialize({
  wasmURL: './node_modules/esbuild-wasm/esbuild.wasm',
})

let result1 = await esbuild.transform(code, options)
let result2 = esbuild.build(options)

Если вы уже работаете из работника и не хотите, чтобы initialize создавал другого работника, вы можете передать worker: false в него. Тогда он создаст модуль WebAssembly в той же нити, что и нить, вызвавшая initialize.

Вы также можете использовать API esbuild как тег скрипта в HTML-файле без использования бандлера, загрузив файл lib/browser.min.js с помощью тега <script>. В этом случае API создает глобальную переменную esbuild, которая содержит объект API:

<script src="./node_modules/esbuild-wasm/lib/browser.min.js"></script>
<script>
  esbuild.initialize({
    wasmURL: './node_modules/esbuild-wasm/esbuild.wasm',
  }).then(() => {
    ...
  })
</script>

Если вы хотите использовать этот API с модулями ECMAScript, вы должны импортировать файл esm/browser.min.js вместо этого:

<script type="module">
  import * as esbuild from './node_modules/esbuild-wasm/esm/browser.min.js'

  await esbuild.initialize({
    wasmURL: './node_modules/esbuild-wasm/esbuild.wasm',
  })

  ...
</script>

Общие параметры

Сборка

Поддерживается: Сборка

Сборка файла означает встраивание всех импортированных зависимостей в сам файл. Этот процесс рекурсивен, поэтому зависимости зависимостей (и так далее) также будут встроенными. По умолчанию esbuild не собирает входные файлы. Сборка должна быть явно включена, например, так:

esbuild in.js --bundle
import * as esbuild from 'esbuild'

console.log(await esbuild.build({
  entryPoints: ['in.js'],
  bundle: true,
  outfile: 'out.js',
}))
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"in.js"},
    Bundle:      true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

См. руководство по началу работы с примером сборки с реальным кодом.

Обратите внимание, что сборка отличается от конкатенации файлов. Передача esbuild нескольких входных файлов со включённой сборкой создаст несколько отдельных сборок вместо объединения входных файлов. Чтобы объединить набор файлов с помощью esbuild, импортируйте их все в одну точку входа и соберете только этот один файл с помощью esbuild.

Неразборчивые импорты

Пути импорта в настоящее время собираются только в том случае, если они являются строковой литеральной или шаблоном glob. Другие формы путей импорта не собираются и вместо этого сохраняются дословно в выходном коде. Это связано с тем, что сборка — это операция на этапе компиляции, и esbuild не поддерживает все формы разрешения путей во время выполнения. Вот несколько примеров:

// Analyzable imports (will be bundled by esbuild)
import 'pkg';
import('pkg');
require('pkg');
import(`./locale-${foo}.json`);
require(`./locale-${foo}.json`);

// Non-analyzable imports (will not be bundled by esbuild)
import(`pkg/${foo}`);
require(`pkg/${foo}`);
['pkg'].map(require);

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

Некоторые бандлеры, такие как Webpack, пытаются поддержать все формы разрешения путей во время выполнения, включая все потенциально достижимые файлы в сборку и затем эмулируя файловую систему во время выполнения. Однако эмуляция файловой системы во время выполнения выходит за рамки esbuild и не будет реализована. Если вам действительно нужно собрать код, который это делает, вам, вероятно, потребуется использовать другой бандлер вместо esbuild.

Импорты в стиле glob

Пути импорта, которые оцениваются во время выполнения, теперь могут быть собраны в определенных ограниченных ситуациях. Выражение пути импорта должно быть формой конкатенации строк и должно начинаться либо с ./, либо с ../. Каждое нестроковое выражение в цепочке конкатенации строк становится подстановкой в шаблоне glob. Вот несколько примеров:

// These two forms are equivalent
const json1 = require('./data/' + kind + '.json')
const json2 = require(`./data/${kind}.json`)

В этом случае esbuild будет искать на файловой системе все файлы, которые соответствуют шаблону, и включат их все в сборку вместе со списком соответствий, отображающим путь импорта на собранный модуль. Выражение импорта будет заменено запросом в этот список. Ошибка будет сгенерирована во время выполнения, если путь импорта отсутствует в списке. Сгенерированный код будет выглядеть примерно так (несущественные части были опущены для краткости):

// data/bar.json
var require_bar = ...;

// data/foo.json
var require_foo = ...;

// require("./data/**/*.json") in example.js
var globRequire_data_json = __glob({
  "./data/bar.json": () => require_bar(),
  "./data/foo.json": () => require_foo()
});

// example.js
var json1 = globRequire_data_json("./data/" + kind + ".json");
var json2 = globRequire_data_json(`./data/${kind}.json`);

Эта функция работает с require(...) и import(...), так как все они могут принимать выражения во время выполнения. Она не работает с import и export операторами, поскольку они не могут принимать выражения во время выполнения. Чтобы предотвратить попытку esbuild собрать эти импорты, вы должны переместить выражение конкатенации строк за пределы require(...) или import(...). Например:

// This will be bundled
const json1 = require('./data/' + kind + '.json')

// This will not be bundled
const path = './data/' + kind + '.json'
const json2 = require(path)

Обратите внимание, что использование этой функции означает, что esbuild потенциально выполнит много операций ввода-вывода на файловой системе, чтобы найти все возможные файлы, которые могут соответствовать шаблону. Это сделано намеренно и не является ошибкой. Если это вызывает беспокойство, есть два способа уменьшить объем операций ввода-вывода на файловой системе, которые выполняет esbuild:

  1. Самый простой подход — поместить все файлы, которые вы хотите импортировать для данного выражения импорта во время выполнения, в подкаталог, а затем включить этот подкаталог в шаблон. Это ограничивает esbuild поиском внутри этого подкаталога, так как esbuild не рассматривает элементы пути .. при сопоставлении шаблонов.

  2. Другой подход — предотвратить поиск esbuild в любом подкаталоге. Алгоритм сопоставления шаблонов, используемый esbuild, разрешает подстановке соответствовать чему-то, содержащему разделитель пути /, только если у этой подстановки есть разделитель пути / перед ней в шаблоне. Например, './data/' + x + '.json' будет соответствовать x с любыми файлами в любом подкаталоге, а './data-' + x + '.json' будет соответствовать только x с любыми файлами в каталоге верхнего уровня (но не в любом подкаталоге).

Отмена

Поддерживается: Сборка

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

# The CLI does not have an API for "cancel"
import * as esbuild from 'esbuild'
import process from 'node:process'

let ctx = await esbuild.context({
  entryPoints: ['app.ts'],
  bundle: true,
  outdir: 'www',
  logLevel: 'info',
})

// Whenever we get some data over stdin
process.stdin.on('data', async () => {
  try {
    // Cancel the already-running build
    await ctx.cancel()

    // Then start a new build
    console.log('build:', await ctx.rebuild())
  } catch (err) {
    console.error(err)
  }
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  ctx, err := api.Context(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Bundle:      true,
    Outdir:      "www",
    LogLevel:    api.LogLevelInfo,
  })
  if err != nil {
    os.Exit(1)
  }

  // Whenever we get some data over stdin
  buf := make([]byte, 100)
  for {
    if n, err := os.Stdin.Read(buf); err != nil || n == 0 {
      break
    }
    go func() {
      // Cancel the already-running build
      ctx.Cancel()

      // Then start a new build
      result := ctx.Rebuild()
      fmt.Fprintf(os.Stderr, "build: %v\n", result)
    }()
  }
}

Убедитесь, что вы дождались завершения операции отмены, прежде чем начинать новую сборку (т. е. await возвращённый промис при использовании JavaScript), иначе следующая пересборка вернёт только что отменённую сборку, которая ещё не завершилась. Обратите внимание, что обратные вызовы плагина on-end всё равно будут выполнены независимо от того, была ли отменена сборка или нет.

Перезагрузка в реальном времени

Поддерживается: Сборка

Перезагрузка в реальном времени — это подход к разработке, при котором ваш браузер и редактор кода открыты одновременно. Когда вы редактируете и сохраняете исходный код, браузер автоматически перезагружается, и обновлённая версия приложения содержит ваши изменения. Это означает, что вы можете быстрее итеративно работать, так как не нужно вручную переключаться на браузер, перезагружать его и затем возвращаться к редактору кода после каждого изменения. Это очень полезно при изменении CSS, например.

Для прямого использования API esbuild для обновления в реальном времени нет. Вместо этого вы можете создать обновление в реальном времени, объединив режим наблюдения (для автоматического запуска сборки при редактировании и сохранении файла) и режим сервирования (для предоставления последней сборки, но блокировки до её завершения), а также небольшой фрагмент клиентского JavaScript-кода, который вы добавляете в своё приложение только во время разработки.

Первый шаг — включить режимы наблюдения и сервирования вместе:

esbuild app.ts --bundle --outdir=www --watch --servedir=www
import * as esbuild from 'esbuild'

let ctx = await esbuild.context({
  entryPoints: ['app.ts'],
  bundle: true,
  outdir: 'www',
})

await ctx.watch()

let { host, port } = await ctx.serve({
  servedir: 'www',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  ctx, err := api.Context(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Bundle:      true,
    Outdir:      "www",
  })
  if err != nil {
    os.Exit(1)
  }

  err2 := ctx.Watch(api.WatchOptions{})
  if err2 != nil {
    os.Exit(1)
  }

  result, err3 := ctx.Serve(api.ServeOptions{
    Servedir: "www",
  })
  if err3 != nil {
    os.Exit(1)
  }
}

Второй шаг — добавить в свой JavaScript-код код, который подписывается на источник /esbuild событий, отправляемых сервером. Когда вы получите событие change, вы можете перезагрузить страницу, чтобы получить последнюю версию приложения. Вы можете сделать это в одной строке кода:

new EventSource('/esbuild').addEventListener('change', () => location.reload())

Всё готово! Если вы загрузите своё приложение в браузере, страница теперь должна автоматически перезагружаться при редактировании и сохранении файла (при условии отсутствия ошибок сборки).

Этот код должен использоваться только во время разработки и не должен включаться в производство. Один из способов удалить этот код в производстве — защитить его с помощью оператора if, например, if (!window.IS_PRODUCTION), а затем использовать define для установки значения window.IS_PRODUCTION на true в производстве.

Ограничения обновления в реальном времени

Реализация обновления в реальном времени таким способом имеет несколько известных ограничений:

  • Эти события срабатывают только при изменении выходных данных esbuild. Они не срабатывают при изменении файлов, не связанных со сборкой, за которой наблюдают. Если ваш HTML-файл ссылается на другие файлы, о которых esbuild не знает, и эти файлы изменены, вы можете либо вручную перезагрузить страницу, либо реализовать собственную инфраструктуру обновления в реальном времени вместо использования встроенного поведения esbuild.

  • Ожидается, что API EventSource автоматически переподключится для вас. Однако есть ошибка в Firefox, которая нарушает это, если сервер временно недоступен. Решениями являются использование любого другого браузера, ручная перезагрузка страницы в случае возникновения этой проблемы или написание более сложного кода, который вручную закрывает и вновь создаёт объект EventSource в случае ошибки соединения.

  • Производители браузеров решили не реализовывать HTTP/2 без TLS. Это означает, что при использовании протокола http:// каждый источник события /esbuild будет занимать одно из ваших драгоценных 6 одновременных подключений HTTP/1.1 на домен. Таким образом, если вы откроете более шести HTTP-вкладок, использующих этот метод обновления в реальном времени, вы не сможете использовать обновление в реальном времени в некоторых из этих вкладок (и, вероятно, сломаются и другие вещи). Решением является включение протокола https://.

Горячее обновление CSS

Событие change также содержит дополнительную информацию для активации более продвинутых случаев использования. В настоящее время оно содержит массивы added, removed и updated с путями файлов, которые изменились с момента предыдущей сборки, которые можно описать с помощью следующего интерфейса TypeScript:

interface ChangeEvent {
  added: string[]
  removed: string[]
  updated: string[]
}

Нижеприведённый пример кода активирует «горячее обновление» для CSS, при котором CSS автоматически обновляется на месте без перезагрузки страницы. Если приходит событие, не связанное с CSS, вся страница перезагрузится как резервный вариант:

new EventSource('/esbuild').addEventListener('change', e => {
  const { added, removed, updated } = JSON.parse(e.data)

  if (!added.length && !removed.length && updated.length === 1) {
    for (const link of document.getElementsByTagName("link")) {
      const url = new URL(link.href)

      if (url.host === location.host && url.pathname === updated[0]) {
        const next = link.cloneNode()
        next.href = updated[0] + '?' + Math.random().toString(36).slice(2)
        next.onload = () => link.remove()
        link.parentNode.insertBefore(next, link.nextSibling)
        return
      }
    }
  }

  location.reload()
})

Горячее обновление JavaScript

Горячее обновление для JavaScript в настоящее время не реализовано в esbuild. Возможность прозрачной реализации горячего обновления для CSS обусловлена отсутствием состояния в CSS, но JavaScript имеет состояние, поэтому прозрачная реализация горячего обновления для JavaScript, как для CSS, невозможна.

Некоторые другие серверы разработки всё же реализуют горячее обновление для JavaScript, но это требует дополнительных API, иногда требует фреймворк-специфических исправлений и иногда приводит к временным ошибкам, связанным с состоянием, во время сеанса редактирования. Выполнение этого выходит за рамки возможностей esbuild. Если горячее обновление JavaScript является одним из ваших требований, вы можете использовать другие инструменты вместо esbuild.

Однако с обновлением в реальном времени esbuild вы можете сохранять текущее состояние JavaScript вашего приложения в sessionStorage, чтобы легче восстановить состояние JavaScript вашего приложения после перезагрузки страницы. Если ваше приложение загружается быстро (что уже должно быть для ваших пользователей), обновление в реальном времени с JavaScript может быть почти так же быстро, как горячее обновление с JavaScript.

Платформа

Поддерживается: Сборка и Преобразование

По умолчанию, сборщик esbuild настроен на генерацию кода, предназначенного для браузера. Если ваш скомпилированный код предназначен для выполнения в node, вы должны установить платформу на node:

esbuild app.js --bundle --platform=node
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  platform: 'node',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Platform:    api.PlatformNode,
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Когда платформа установлена на browser (значение по умолчанию):

  • При включённой сборке формат вывода по умолчанию устанавливается на iife, который оборачивает сгенерированный JavaScript-код в выражение функции, вызываемой немедленно, чтобы предотвратить утечку переменных в глобальную область видимости.

  • Если пакет указывает карту для поля browser в своём файле package.json, esbuild будет использовать эту карту для замены определённых файлов или модулей их браузерными версиями. Например, пакет может содержать замену path на path-browserify.

  • Настройка главных полей устанавливается на browser,module,main, но с дополнительным специальным поведением: если пакет предоставляет точки входа module и main, но не точку входа browser, то main используется вместо module, если этот пакет импортируется с помощью require(). Это поведение улучшает совместимость с модулями CommonJS, которые экспортируют функцию, назначив её module.exports. Если вы хотите отключить это дополнительное специальное поведение, вы можете явно установить настройку главных полей на browser,module,main.

  • Настройка условий автоматически включает условие browser. Это изменяет интерпретацию поля exports в файлах package.json, чтобы предпочесть браузерный код.

  • Если не настроено никаких пользовательских условий условий, также включается условие module, специфичное для Webpack. Условие module используется авторами пакетов для предоставления альтернативы CommonJS файла ESM, допускающей сокращение, без создания опасности двойного пакета. Вы можете предотвратить включение условия module, явно настроив некоторые пользовательские условия (даже пустой список).

  • При использовании API build все выражения process.env.NODE_ENV автоматически определяются как "production", если все параметры минификации включены, и как "development" в противном случае. Это происходит только в том случае, если process, process.env и process.env.NODE_ENV ещё не определены. Эта замена необходима для предотвращения мгновенного зависания кода на основе React (поскольку process — это API node, а не веб-API).

  • Последовательность символов </script> будет экранирована в JavaScript-коде, а последовательность символов </style> — в CSS-коде. Это делается на случай, если вы непосредственно вставляете выходные данные esbuild в HTML-файл. Это можно отключить с помощью функции esbuild supported, установив значение inline-script (для JavaScript) и/или inline-style (для CSS) на false.

Когда платформа установлена на node:

  • При включённой сборке формат вывода по умолчанию устанавливается на cjs, что соответствует CommonJS (формату модулей, используемому в node). ES6-стилевые экспортные операторы, использующие операторы export, будут преобразованы в геттеры в объекте CommonJS exports.

  • Все встроенные модули node, такие как fs, автоматически помечаются как внешние, чтобы не вызывать ошибок при попытке сборки сборщиком.

  • Настройка главных полей устанавливается на main,module. Это означает, что сокращение, скорее всего, не произойдёт для пакетов, которые предоставляют как module, так и main, так как сокращение работает с модулями ECMAScript, но не с модулями CommonJS.

    К сожалению, некоторые пакеты неправильно интерпретируют module как означающее «браузерный код», а не «код модуля ECMAScript», поэтому это поведение по умолчанию необходимо для совместимости. Вы можете вручную настроить настройку главных полей на module,main, если хотите включить сокращение и знаете, что это безопасно.

  • Настройка условий автоматически включает условие node. Это изменяет интерпретацию поля exports в файлах package.json, чтобы предпочесть код, специфичный для node.

  • Если не настроено никаких пользовательских условий условий, также включается условие module, специфичное для Webpack. Условие module используется авторами пакетов для предоставления альтернативы CommonJS файла ESM, допускающей сокращение, без создания опасности двойного пакета. Вы можете предотвратить включение условия module, явно настроив некоторые пользовательские условия (даже пустой список).

  • При установке формата на cjs, но точка входа является ESM, esbuild добавит специальные аннотации для всех именованных экспортов, чтобы позволить импортировать эти именованные экспортные данные с помощью синтаксиса ESM из полученного файла CommonJS. Документация node содержит дополнительную информацию о обнаружении node именованных экспортов CommonJS.

  • Загрузчик binary будет использовать встроенный API Buffer.from node для декодирования данных base64, встроенных в пакет, в Uint8Array. Это быстрее, чем то, что может сделать esbuild, так как оно реализовано в node нативном коде.

Когда платформа установлена в neutral:

  • Если включено связывание, по умолчанию используется выходной формат esm, который использует синтаксис export, представленный с ECMAScript 2015 (т.е. ES6). Вы можете изменить формат вывода, если этот вариант не подходит.

  • По умолчанию настройка основных полей пуста. Если вы хотите использовать пакеты в стиле npm, вам, вероятно, придётся настроить это на что-то другое, например, на main, для стандартного основного поля, используемого node.

  • Настройка условий автоматически не включает какие-либо платформенно-специфичные значения.

См. также связывание для браузера и связывание для node.

Перестроение

Поддерживается: Сборка

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

  • Файлы хранятся в памяти и не считываются из файловой системы повторно, если метаданные файла не изменились с момента последней сборки. Эта оптимизация применяется только к путям файловой системы. Она не применяется к виртуальным модулям, созданным плагинами.

  • Разбор AST хранится в памяти, и повторный разбор AST избегается, если содержимое файла не изменилось с момента последней сборки. Эта оптимизация применяется к виртуальным модулям, созданным плагинами, в дополнение к модулям файловой системы, при условии, что виртуальный путь модуля остается неизменным.

Вот как выполнить перестроение:

# The CLI does not have an API for "rebuild"
import * as esbuild from 'esbuild'

let ctx = await esbuild.context({
  entryPoints: ['app.js'],
  bundle: true,
  outfile: 'out.js',
})

// Call "rebuild" as many times as you want
for (let i = 0; i < 5; i++) {
  let result = await ctx.rebuild()
}

// Call "dispose" when you're done to free up resources
ctx.dispose()
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  ctx, err := api.Context(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Outfile:     "out.js",
  })
  if err != nil {
    os.Exit(1)
  }

  // Call "Rebuild" as many times as you want
  for i := 0; i < 5; i++ {
    result := ctx.Rebuild()
    if len(result.Errors) > 0 {
      os.Exit(1)
    }
  }

  // Call "Dispose" when you're done to free up resources
  ctx.Dispose()
}

Отправка

Поддерживается: Сборка

Если вы хотите, чтобы ваше приложение автоматически перезагружалось при редактировании, вы должны ознакомиться с живой перезагрузкой. Она объединяет режим отправки с режимом наблюдения для отслеживания изменений в файловой системе.

Режим отправки запускает веб-сервер, который отправляет ваш код в ваш браузер на вашем устройстве. Вот пример, который связывает src/app.ts в www/js/app.js, а также отправляет каталог www по протоколу http://localhost:8000/:

esbuild src/app.ts --outdir=www/js --bundle --servedir=www
import * as esbuild from 'esbuild'

let ctx = await esbuild.context({
  entryPoints: ['src/app.ts'],
  outdir: 'www/js',
  bundle: true,
})

let { host, port } = await ctx.serve({
  servedir: 'www',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  ctx, err := api.Context(api.BuildOptions{
    EntryPoints: []string{"src/app.ts"},
    Outdir:     "www/js",
    Bundle:      true,
  })
  if err != nil {
    os.Exit(1)
  }

  server, err2 := ctx.Serve(api.ServeOptions{
    Servedir: "www",
  })
  if err2 != nil {
    os.Exit(1)
  }

  // Returning from main() exits immediately in Go.
  // Block forever so we keep serving and don't exit.
  <-make(chan struct{})
}

Если вы создадите файл www/index.html со следующим содержимым, код, содержащийся в src/app.ts, загрузится при переходе к http://localhost:8000/:

<script src="js/app.js"></script>

Одно из преимуществ использования встроенного веб-сервера esbuild вместо другого веб-сервера заключается в том, что при каждой перезагрузке файлы, отправляемые esbuild, всегда актуальны. Это не обязательно так с другими настройками разработки. Одна распространённая настройка заключается в запуске локального монитора файлов, который перестраивает выходные файлы всякий раз, когда изменяются входные файлы, и затем отдельно запускает локальный файловый сервер для отправки этих выходных файлов. Но это означает, что при перезагрузке после редактирования могут загружаться старые выходные файлы, если перестроение ещё не завершено. С веб-сервером esbuild каждый входящий запрос запускает перестроение, если оно не происходит, и затем ждёт завершения текущего перестроения перед отправкой файла. Это означает, что esbuild никогда не отправляет устаревшие результаты сборки.

Обратите внимание, что этот веб-сервер предназначен только для использования в разработке. Не используйте его в производстве.

Аргументы

Аргументы API отправки следующие:

# Enable serve mode
--serve

# Set the port
--serve=9000

# Set the host and port (IPv4)
--serve=127.0.0.1:9000

# Set the host and port (IPv6)
--serve=[::1]:9000

# Set the directory to serve
--servedir=www

# Enable HTTPS
--keyfile=your.key --certfile=your.cert

# Specify a fallback HTML file
--serve-fallback=some-file.html
interface ServeOptions {
  port?: number
  host?: string
  servedir?: string
  keyfile?: string
  certfile?: string
  fallback?: string
  onRequest?: (args: ServeOnRequestArgs) => void
}

interface ServeOnRequestArgs {
  remoteAddress: string
  method: string
  path: string
  status: number
  timeInMS: number
}
type ServeOptions struct {
  Port      uint16
  Host      string
  Servedir  string
  Keyfile   string
  Certfile  string
  Fallback  string
  OnRequest func(ServeOnRequestArgs)
}

type ServeOnRequestArgs struct {
  RemoteAddress string
  Method        string
  Path          string
  Status        int
  TimeInMS      int
}
  • host

    По умолчанию esbuild делает веб-сервер доступным на всех IPv4 сетевых интерфейсах. Это соответствует адресу хоста 0.0.0.0. Если вы хотите настроить другой хост (например, чтобы отправлять только на локальный интерфейс 127.0.0.1 без экспонирования чего-либо в сеть), вы можете указать хост с помощью этого аргумента.

    Если вам необходимо использовать IPv6 вместо IPv4, вам просто нужно указать адрес IPv6 хоста. Эквивалентом локального интерфейса 127.0.0.1 в IPv6 является ::1, а эквивалентом универсального интерфейса 0.0.0.0 в IPv6 является ::.

  • port

    Порт HTTP можно настроить здесь. Если он опущен, он будет по умолчанию использовать свободный порт в диапазоне от 8000 до 8009.

    Обратите внимание, что Go API отличается от CLI и JS API в отношении порта 0. Unix зарезервировал порт 0 для обозначения "выбрать случайный эпизодический порт", но esbuild зарезервировал порт 0 для описанного выше поведения по умолчанию, так как 0 - значение по умолчанию целочисленного поля в Go. Вместо этого esbuild использует контрольное значение -1 для выбора случайного эпизодического порта в Go. CLI и JS API не имеют этой проблемы и позволяют вам указать порт 0, чтобы выбрать случайный эпизодический порт, как и другие стандартные API Unix.

  • servedir

    Это каталог дополнительного контента для веб-сервера esbuild, который будет отправляться вместо 404, когда входящие запросы не соответствуют ни одному из сгенерированных путей выходных файлов. Это позволяет использовать esbuild в качестве универсального локального веб-сервера.

    Например, вы можете создать файл index.html и установить servedir на "." для отправки текущего каталога (который включает файл index.html). Если вы не устанавливаете servedir, esbuild будет отправлять только результаты сборки, но не любые другие файлы.

  • keyfile и certfile

    Если вы передаете приватный ключ и сертификат в esbuild с помощью keyfile и certfile, веб-сервер esbuild будет использовать протокол https:// вместо протокола http://. См. включение HTTPS для получения дополнительной информации.

  • fallback

    Это HTML-файл, который веб-сервер esbuild будет отправлять вместо 404, когда входящие запросы не соответствуют ни одному из сгенерированных путей выходных файлов. Вы можете использовать это для пользовательской страницы "не найдено". Вы также можете использовать это в качестве точки входа одностраничного приложения, которое изменяет текущий URL и, следовательно, требует одновременной отправки с многих различных URL.

  • onRequest

    Вызывается один раз для каждого входящего запроса с некоторой информацией о запросе. Этот обратный вызов используется CLI для вывода сообщения журнала для каждого запроса. Поле time - это время генерации данных для запроса, но оно не включает время потоковой передачи запроса клиенту.

    Обратите внимание, что этот обратный вызов вызывается после завершения запроса. Невозможно использовать этот обратный вызов для изменения запроса каким-либо образом. Если вы хотите это сделать, вы должны поставить прокси перед esbuild вместо этого.

Возвращаемые значения

# The CLI will print the hosts and port like this:

 > Local:   http://127.0.0.1:8000/
 > Network: http://192.168.0.1:8000/
interface ServeResult {
  hosts: string[]
  port: number
}
type ServeResult struct {
  Hosts []string
  Port  uint16
}
  • hosts

    Это массив хостов, которые в конечном итоге были использованы веб-сервером. Если хост - это неопределённый адрес (который является по умолчанию), то массив включает локальный интерфейс, а также любые другие активные сетевые интерфейсы, такие как интерфейс вашей Wi-Fi сети. Например, неопределённый IPv4 адрес 0.0.0.0 может привести к тому, что массив будет содержать как 127.0.0.1, так и 192.168.0.1, а неопределённый IPv6 адрес :: может привести к тому, что массив будет содержать как ::1, так и fe80::b0ba:cafe. Фактические хосты, возвращаемые для неопределённого хоста, зависят от вашей текущей сетевой конфигурации.

  • port

    Это порт, который в конечном итоге был использован веб-сервером. Вам понадобится это, если вы не укажете порт, так как esbuild выберет произвольный свободный порт, и вам нужно будет знать, какой порт он выбрал, чтобы иметь возможность подключиться к нему.

Включение HTTPS

По умолчанию веб-сервер esbuild использует протокол http://. Однако некоторые современные веб-функции недоступны для веб-сайтов HTTP. Если вы хотите использовать эти функции, вам нужно будет указать esbuild использовать протокол https:// вместо него.

Чтобы включить HTTPS с esbuild:

  1. Сгенерируйте самоподписанный сертификат. Существует много способов сделать это. Вот один способ, предполагая, что у вас установлен инструмент openssl:

     openssl req -x509 -newkey rsa:4096 -keyout your.key -out your.cert -days 9999 -nodes -subj /CN=127.0.0.1 
  2. Передайте your.key и your.cert в esbuild с помощью аргументов keyfile и certfile отправки.

  3. Пройдите мимо страшного предупреждения в вашем браузере, когда вы загружаете страницу (самоподписанные сертификаты небезопасны, но это не важно, так как мы просто выполняем локальную разработку).

Если у вас есть более сложные потребности, чем это, вы всё ещё можете поставить прокси перед esbuild и использовать его для HTTPS вместо этого. Обратите внимание, что если при загрузке страницы вы видите сообщение Client sent an HTTP request to an HTTPS server, значит вы используете неверный протокол. Замените http:// на https:// в адресной строке вашего браузера.

Помните, что поддержка HTTPS в esbuild не связана с безопасностью. Единственная причина включения HTTPS в esbuild заключается в том, что браузеры сделали локальную разработку с определёнными современными веб-функциями невозможной без выполнения этих дополнительных шагов. Пожалуйста, не используйте сервер разработки esbuild для чего-либо, что требует безопасности. Он предназначен только для локальной разработки, и нисколько не учитываются потребности производственных сред.

Настройка поведения сервера

Невозможно подключиться к локальному серверу esbuild для настройки поведения самого сервера. Вместо этого поведение должно настраиваться путём размещения прокси перед esbuild.

Вот простой пример сервера прокси, чтобы вы начали, используя встроенный модуль http node. Он добавляет пользовательскую страницу 404 вместо страницы 404 по умолчанию esbuild:

import * as esbuild from 'esbuild'
import http from 'node:http'

// Start esbuild's server on a random local port
let ctx = await esbuild.context({
  // ... your build options go here ...
})

// The return value tells us where esbuild's local server is
let { hosts, port } = await ctx.serve({ servedir: '.' })

// Then start a proxy server on port 3000
http.createServer((req, res) => {
  const options = {
    hostname: hosts[0],
    port: port,
    path: req.url,
    method: req.method,
    headers: req.headers,
  }

  // Forward each incoming request to esbuild
  const proxyReq = http.request(options, proxyRes => {
    // If esbuild returns "not found", send a custom 404 page
    if (proxyRes.statusCode === 404) {
      res.writeHead(404, { 'Content-Type': 'text/html' })
      res.end('<h1>A custom 404 page</h1>')
      return
    }

    // Otherwise, forward the response from esbuild to the client
    res.writeHead(proxyRes.statusCode, proxyRes.headers)
    proxyRes.pipe(res, { end: true })
  })

  // Forward the body of the request to esbuild
  req.pipe(proxyReq, { end: true })
}).listen(3000)

Этот код запускает сервер esbuild на случайном локальном порту и затем запускает сервер прокси на порту 3000. Во время разработки вы бы загрузили http://localhost:3000 в своём браузере, который общается с прокси. Этот пример демонстрирует изменение ответа после обработки запроса esbuild, но вы также можете изменить или заменить запрос до обработки его esbuild.

Вы можете сделать много чего с таким прокси, включая:

  • Вставка собственной страницы 404 (пример выше)
  • Настройка сопоставления маршрутов с файлами на файловой системе
  • Перенаправление некоторых маршрутов на сервер API вместо esbuild

Также можно использовать реальный прокси, такой как nginx, если у вас есть более продвинутые потребности.

Tsconfig

Поддерживается: Build

Обычно API build автоматически обнаруживает файлы tsconfig.json и считывает их содержимое во время сборки. Однако вы также можете настроить использование настраиваемого файла tsconfig.json вместо этого. Это может быть полезно, если вам нужно выполнить несколько сборок одного и того же кода с различными настройками:

esbuild app.ts --bundle --tsconfig=custom-tsconfig.json
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.ts'],
  bundle: true,
  tsconfig: 'custom-tsconfig.json',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Bundle:      true,
    Tsconfig:    "custom-tsconfig.json",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Tsconfig raw

Поддерживается: Build и Transform

Этот параметр можно использовать для передачи вашего файла tsconfig.json API transform, который не обращается к файловой системе. Он также может использоваться для передачи содержимого вашего файла tsconfig.json API build встраиваемо без записи в файл. Его использование выглядит так:

echo 'class Foo { foo }' | esbuild --loader=ts --tsconfig-raw='{"compilerOptions":{"useDefineForClassFields":false}}'
import * as esbuild from 'esbuild'

let ts = 'class Foo { foo }'
let result = await esbuild.transform(ts, {
  loader: 'ts',
  tsconfigRaw: `{
    "compilerOptions": {
      "useDefineForClassFields": false,
    },
  }`,
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  ts := "class Foo { foo }"

  result := api.Transform(ts, api.TransformOptions{
    Loader: api.LoaderTS,
    TsconfigRaw: `{
      "compilerOptions": {
        "useDefineForClassFields": false,
      },
    }`,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

Watch

Поддерживается: Build

Включение режима наблюдения (watch) сообщает esbuild о прослушивании изменений на файловой системе и автоматической пересборке при изменении файлов, которые могут повлиять на сборку. Его использование выглядит следующим образом:

esbuild app.js --outfile=out.js --bundle --watch
[watch] build finished, watching for changes...
import * as esbuild from 'esbuild'

let ctx = await esbuild.context({
  entryPoints: ['app.js'],
  outfile: 'out.js',
  bundle: true,
})

await ctx.watch()
console.log('watching...')
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  ctx, err := api.Context(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Outfile:     "out.js",
    Bundle:      true,
    Write:       true,
  })
  if err != nil {
    os.Exit(1)
  }

  err2 := ctx.Watch(api.WatchOptions{})
  if err2 != nil {
    os.Exit(1)
  }
  fmt.Printf("watching...\n")

  // Returning from main() exits immediately in Go.
  // Block forever so we keep watching and don't exit.
  <-make(chan struct{})
}

Если вы хотите остановить режим наблюдения в будущем, вы можете вызвать dispose на объекте контекста для завершения наблюдения за файлами:

# Use Ctrl+C to stop the CLI in watch mode
import * as esbuild from 'esbuild'

let ctx = await esbuild.context({
  entryPoints: ['app.js'],
  outfile: 'out.js',
  bundle: true,
})

await ctx.watch()
console.log('watching...')

await new Promise(r => setTimeout(r, 10 * 1000))
await ctx.dispose()
console.log('stopped watching')
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "os"
import "time"

func main() {
  ctx, err := api.Context(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Outfile:     "out.js",
    Bundle:      true,
    Write:       true,
  })
  if err != nil {
    os.Exit(1)
  }

  err2 := ctx.Watch(api.WatchOptions{})
  if err2 != nil {
    os.Exit(1)
  }
  fmt.Printf("watching...\n")

  time.Sleep(10 * time.Second)
  ctx.Dispose()
  fmt.Printf("stopped watching\n")
}

Режим наблюдения в esbuild реализован с помощью опроса вместо специфичных для ОС API файловой системы для обеспечения портативности. Система опроса разработана для использования относительно небольшого количества ЦП по сравнению с более традиционной системой опроса, которая сканирует всю древовидную структуру каталогов сразу. Файловая система по-прежнему регулярно сканируется, но каждое сканирование проверяет только случайную подвыборку файлов, что означает, что изменение файла будет обнаружено вскоре после внесения изменения, но не обязательно мгновенно.

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

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

Если вы используете командную строку, помните, что режим наблюдения будет завершён при закрытии стандартного ввода esbuild. Это предотвращает непредвиденное продолжение работы esbuild после завершения родительского процесса и непредвиденное дальнейшее потребление ресурсов в системе. Если вам нужен сценарий, требующий, чтобы esbuild продолжал наблюдение вечно, даже когда родительский процесс завершён, вы можете использовать --watch=forever вместо --watch.

Входные данные

Точки входа

Поддерживается: Build

Это массив файлов, каждый из которых служит входными данными для алгоритма связывания. Они называются "точками входа", потому что каждый из них предназначен для первоначального скрипта, который оценивается, а затем загружает все другие аспекты кода, который он представляет. Вместо загрузки многих библиотек на вашей странице с тегами <script>, вы вместо этого используете операторы import для их импорта в свою точку входа (или в другой файл, который затем импортируется в вашу точку входа).

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

Простой способ указать точки входа — передать массив путей к файлам:

esbuild home.ts settings.ts --bundle --outdir=out
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['home.ts', 'settings.ts'],
  bundle: true,
  write: true,
  outdir: 'out',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"home.ts", "settings.ts"},
    Bundle:      true,
    Write:       true,
    Outdir:      "out",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Это сгенерирует два выходных файла, out/home.js и out/settings.js, соответствующие двум точкам входа home.ts и settings.ts.

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

  • Имена точек входа
  • Расширение выходного файла
  • Outbase
  • Outdir
  • Outfile

Кроме того, вы также можете указать полностью настраиваемый путь вывода для каждой отдельной точки входа, используя альтернативный синтаксис точки входа:

esbuild out1=home.ts out2=settings.ts --bundle --outdir=out
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: [
    { out: 'out1', in: 'home.ts'},
    { out: 'out2', in: 'settings.ts'},
  ],
  bundle: true,
  write: true,
  outdir: 'out',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPointsAdvanced: []api.EntryPoint{{
      OutputPath: "out1",
      InputPath:  "home.ts",
    }, {
      OutputPath: "out2",
      InputPath:  "settings.ts",
    }},
    Bundle: true,
    Write:  true,
    Outdir: "out",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Это сгенерирует два выходных файла, out/out1.js и out/out2.js, соответствующие двум точкам входа home.ts и settings.ts.

Точки входа в стиле glob

Если точка входа содержит символ *, то она считается шаблоном glob. Это означает, что esbuild будет использовать эту точку входа в качестве шаблона для поиска файлов на файловой системе и заменит эту точку входа любыми найденными совпадающими файлами. Например, точка входа *.js заставит esbuild рассмотреть все файлы в текущем каталоге, которые оканчиваются на .js, как точки входа.

Реализация esbuild glob-совпадения преднамеренно проста и не поддерживает более продвинутые функции, доступные в других библиотеках glob. Поддерживаются только два типа подстановочных знаков:

  • *

    Этот подстановочный знак соответствует любому количеству символов (включая ноль), за исключением символа косой черты (т.е. /), что означает, что он не заставляет esbuild переходить в подкаталоги. Например, *.js будет соответствовать foo.js, но не bar/foo.js.

  • /**/

    Этот подстановочный знак соответствует нулю или более сегментам пути, что позволяет использовать его для указания esbuild на соответствие всей древовидной структуре каталогов. Например, ./**/*.js будет соответствовать ./foo.js, ./bar/foo.js и ./a/b/c/foo.js.

Если вы используете esbuild через командную строку, имейте в виду, что если вы не поставите в кавычки аргументы, содержащие символы оболочки, перед их передачей в esbuild, ваша оболочка, скорее всего, расширит их перед тем, как esbuild их увидит. Поэтому, если вы запустите esbuild "*.js" (в кавычках), esbuild увидит точку входа *.js, и описанные выше правила glob-стиля точек входа будут применены. Но если вы запустите esbuild *.js (без кавычек), esbuild увидит то, что ваша текущая оболочка решила расширить *.js (что может включать отсутствие чего-либо, если ваша оболочка расширила её в пустоту). Поддержка esbuild встроенных шаблонов glob может быть удобным способом обеспечения кроссплатформенной согласованности, избегая поведения, специфичного для оболочки, но требует правильного использования кавычек в аргументах, чтобы оболочка не интерпретировала их.

Загрузчик

Поддерживается: Build и Transform

Этот параметр изменяет способ интерпретации заданного входного файла. Например, js загрузчик интерпретирует файл как JavaScript, а css загрузчик интерпретирует файл как CSS. См. страницу типов содержимого для полного списка всех встроенных загрузчиков.

Настройка загрузчика для заданного типа файла позволяет вам загружать этот тип файла с помощью оператора import или вызова require. Например, настройка расширения файла .png для использования загрузчика data URL означает, что импорт файла .png даёт вам data URL, содержащий содержимое этого изображения:

import url from './example.png'
let image = new Image
image.src = url
document.body.appendChild(image)

import svg from './example.svg'
let doc = new DOMParser().parseFromString(svg, 'application/xml')
let node = document.importNode(doc.documentElement, true)
document.body.appendChild(node)

Вышеупомянутый код можно скомпоновать с помощью вызова API build так:

esbuild app.js --bundle --loader:.png=dataurl --loader:.svg=text
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  loader: {
    '.png': 'dataurl',
    '.svg': 'text',
  },
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Loader: map[string]api.Loader{
      ".png": api.LoaderDataURL,
      ".svg": api.LoaderText,
    },
    Write: true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Этот параметр задаётся по-другому, если вы используете API build с вводом от stdin, поскольку у stdin нет расширения файла. Настройка загрузчика для stdin с помощью API build выглядит так:

echo 'import pkg = require("./pkg")' | esbuild --loader=ts --bundle
import * as esbuild from 'esbuild'

await esbuild.build({
  stdin: {
    contents: 'import pkg = require("./pkg")',
    loader: 'ts',
    resolveDir: '.',
  },
  bundle: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    Stdin: &api.StdinOptions{
      Contents:   "import pkg = require('./pkg')",
      Loader:     api.LoaderTS,
      ResolveDir: ".",
    },
    Bundle: true,
  })
  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Вызов API transform принимает только один загрузчик, так как он не взаимодействует с файловой системой и, следовательно, не работает с расширениями файлов. Настройка загрузчика (в данном случае загрузчика ts) для API transform выглядит так:

echo 'let x: number = 1' | esbuild --loader=ts
let x = 1;
import * as esbuild from 'esbuild'

let ts = 'let x: number = 1'
let result = await esbuild.transform(ts, {
  loader: 'ts',
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  ts := "let x: number = 1"
  result := api.Transform(ts, api.TransformOptions{
    Loader: api.LoaderTS,
  })
  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

Stdin

Поддерживается: Build

Обычно вызов API build принимает один или несколько имён файлов в качестве входных данных. Однако этот параметр позволяет запустить сборку без наличия модуля на файловой системе. Он называется "stdin", потому что соответствует передаче файла в стандартный ввод на командной строке.

В дополнение к указанию содержимого файла stdin, вы можете также указать каталог разрешения (используется для определения места расположения относительных импортов), sourcefile (имя файла, используемое в сообщениях об ошибках и картах исходного кода), и loader (который определяет, как интерпретируется содержимое файла). Командная строка не имеет способа указать каталог разрешения. Вместо этого он автоматически устанавливается в текущий рабочий каталог.

Вот как использовать эту функцию:

echo 'export * from "./another-file"' | esbuild --bundle --sourcefile=imaginary-file.js --loader=ts --format=cjs
import * as esbuild from 'esbuild'

let result = await esbuild.build({
  stdin: {
    contents: `export * from "./another-file"`,

    // These are all optional:
    resolveDir: './src',
    sourcefile: 'imaginary-file.js',
    loader: 'ts',
  },
  format: 'cjs',
  write: false,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    Stdin: &api.StdinOptions{
      Contents: "export * from './another-file'",

      // These are all optional:
      ResolveDir: "./src",
      Sourcefile: "imaginary-file.js",
      Loader:     api.LoaderTS,
    },
    Format: api.FormatCommonJS,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Содержимое вывода

Баннер

Поддерживается: Build и Transform

Используйте это для вставки произвольной строки в начале сгенерированных JavaScript и CSS файлов. Это обычно используется для вставки комментариев:

esbuild app.js --banner:js=//comment --banner:css=/*comment*/
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  banner: {
    js: '//comment',
    css: '/*comment*/',
  },
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Banner: map[string]string{
      "js":  "//comment",
      "css": "/*comment*/",
    },
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Это похоже на footer, который вставляет в конец вместо начала.

Обратите внимание, что если вы вставляете код без комментариев в файл CSS, то CSS игнорирует все правила @import, которые следуют за правилом без @import (кроме правила @charset), поэтому использование баннера для вставки правил CSS может случайно отключить импорт внешних таблиц стилей.

Кодировка символов

Поддерживается: Сборка и Преобразование

По умолчанию вывод esbuild состоит только из ASCII. Любые не-ASCII символы экранируются с помощью последовательностей обратной косой черты. Одна из причин заключается в том, что браузер по умолчанию неправильно интерпретирует не-ASCII символы, что вызывает путаницу. Вам необходимо явно добавить <meta charset="utf-8"> в ваш HTML или передать его с правильным заголовком Content-Type для браузера, чтобы он не искажал ваш код. Другая причина заключается в том, что не-ASCII символы могут значительно замедлить работу парсера браузера. Однако использование последовательностей экранирования делает сгенерированный вывод немного больше, а также затрудняет чтение.

Если вы хотите, чтобы esbuild выводил исходные символы без использования последовательностей экранирования, и вы убедились, что браузер будет интерпретировать ваш код как UTF-8, вы можете отключить экранирование символов, установив кодировку:

echo 'let π = Math.PI' | esbuild
let \u03C0 = Math.PI;
echo 'let π = Math.PI' | esbuild --charset=utf8
let π = Math.PI;
import * as esbuild from 'esbuild'
let js = 'let π = Math.PI'
(await esbuild.transform(js)).code
'let \\u03C0 = Math.PI;\n'
(await esbuild.transform(js, {
  charset: 'utf8',
})).code
'let π = Math.PI;\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "let π = Math.PI"

  result1 := api.Transform(js, api.TransformOptions{})

  if len(result1.Errors) == 0 {
    fmt.Printf("%s", result1.Code)
  }

  result2 := api.Transform(js, api.TransformOptions{
    Charset: api.CharsetUTF8,
  })

  if len(result2.Errors) == 0 {
    fmt.Printf("%s", result2.Code)
  }
}

Некоторые замечания:

  • Это пока не экранирует не-ASCII символы, вложенные в регулярные выражения. Это потому, что esbuild в настоящее время вообще не анализирует содержимое регулярных выражений. Флаг был добавлен, несмотря на это ограничение, потому что он всё ещё полезен для кода, который не содержит таких случаев.

  • Этот флаг не применяется к комментариям. Я считаю, что сохранение не-ASCII данных в комментариях должно быть в порядке, потому что даже если кодировка неверна, среда выполнения должна полностью игнорировать содержимое всех комментариев. Например, в статье блога V8 упоминается оптимизация, которая позволяет избежать декодирования содержимого комментариев. А все комментарии, кроме комментариев, относящихся к лицензированию, в любом случае удаляются esbuild.

  • Этот параметр одновременно применяется ко всем типам выходных файлов (JavaScript, CSS и JSON). Таким образом, если вы настроите свой веб-сервер для отправки правильного заголовка Content-Type и хотите использовать кодировку UTF-8, убедитесь, что ваш веб-сервер настроен для обработки файлов как .js, так и .css как UTF-8.

Подвал

Поддерживается: Сборка и Преобразование

Используйте это для вставки произвольной строки в конец сгенерированных файлов JavaScript и CSS. Это обычно используется для вставки комментариев:

esbuild app.js --footer:js=//comment --footer:css=/*comment*/
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  footer: {
    js: '//comment',
    css: '/*comment*/',
  },
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Footer: map[string]string{
      "js":  "//comment",
      "css": "/*comment*/",
    },
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Это похоже на баннер, который вставляется в начало, а не в конец.

Формат

Поддерживается: Сборка и Преобразование

Это задаёт формат вывода для сгенерированных файлов JavaScript. В настоящее время доступны три возможных значения: iife, cjs и esm. Если формат вывода не указан, esbuild выбирает формат вывода для вас, если включена сборка (как описано ниже), или не выполняет преобразование формата, если сборка отключена.

IIFE

Формат iife означает "немедленно вызываемая функция выражения" и предназначен для выполнения в браузере. Обертывание вашего кода в выражение функции гарантирует, что любые переменные в вашем коде случайно не будут конфликтовать с переменными в глобальной области видимости. Если ваша точка входа имеет экспорты, которые вы хотите экспонировать как глобальную переменную в браузере, вы можете настроить имя этой глобальной переменной, используя настройку имени глобальной переменной. Формат iife будет автоматически включён, когда не указан никакой формат вывода, включена сборка, а платформа установлена в browser (что по умолчанию). Указание формата iife выглядит следующим образом:

echo 'alert("test")' | esbuild --format=iife
(() => {
  alert("test");
})();
import * as esbuild from 'esbuild'

let js = 'alert("test")'
let result = await esbuild.transform(js, {
  format: 'iife',
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "alert(\"test\")"

  result := api.Transform(js, api.TransformOptions{
    Format: api.FormatIIFE,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

CommonJS

Формат cjs означает "CommonJS" и предназначен для выполнения в Node.js. Он предполагает, что среда содержит exports, require и module. Точки входа с экспортами в синтаксисе модулей ECMAScript будут преобразованы в модуль с getter на exports для каждого имени экспорта. Формат cjs будет автоматически включен, когда не указан никакой формат вывода, включена сборка, и платформа установлена в node. Указание формата cjs выглядит следующим образом:

echo 'export default "test"' | esbuild --format=cjs
...
var stdin_exports = {};
__export(stdin_exports, {
  default: () => stdin_default
});
module.exports = __toCommonJS(stdin_exports);
var stdin_default = "test";
import * as esbuild from 'esbuild'

let js = 'export default "test"'
let result = await esbuild.transform(js, {
  format: 'cjs',
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "export default 'test'"

  result := api.Transform(js, api.TransformOptions{
    Format: api.FormatCommonJS,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

ESM

Формат esm означает "модуль ECMAScript". Он предполагает, что среда поддерживает синтаксис import и export. Точки входа с экспортами в синтаксисе модулей CommonJS будут преобразованы в один default экспорт значения module.exports. Формат esm будет автоматически включён, когда не указан никакой формат вывода, включена сборка, а платформа установлена в neutral. Указание формата esm выглядит следующим образом:

echo 'module.exports = "test"' | esbuild --format=esm
...
var require_stdin = __commonJS({
  "<stdin>"(exports, module) {
    module.exports = "test";
  }
});
export default require_stdin();
import * as esbuild from 'esbuild'

let js = 'module.exports = "test"'
let result = await esbuild.transform(js, {
  format: 'esm',
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "module.exports = 'test'"

  result := api.Transform(js, api.TransformOptions{
    Format: api.FormatESModule,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

Формат esm может быть использован как в браузере, так и в Node.js, но вы должны явно загрузить его как модуль. Это происходит автоматически, если вы import его из другого модуля. В противном случае:

  • В браузере вы можете загрузить модуль, используя <script src="file.js" type="module"></script>. Не забудьте type="module", так как это вызовет ошибки в вашем коде (пропуск type="module" означает, что все переменные верхнего уровня попадут в глобальную область видимости, которая затем столкнётся с переменными верхнего уровня с таким же именем в других файлах JavaScript).
  • В Node.js вы можете загрузить модуль, используя node file.mjs. Обратите внимание, что Node.js требует расширение .mjs, если вы не настроите "type": "module" в своём файле package.json. Вы можете настроить расширение выходного файла для файлов, сгенерированных esbuild, используя настройку расширения выходного файла. Подробнее об использовании модулей ECMAScript в Node.js можно узнать здесь.

Имя глобальной переменной

Поддерживается: Сборка и Преобразование

Этот параметр важен только когда значение параметра формат iife (что означает немедленно вызываемая функция выражения). Он устанавливает имя глобальной переменной, используемой для хранения экспортов из точки входа:

echo 'module.exports = "test"' | esbuild --format=iife --global-name=xyz
import * as esbuild from 'esbuild'

let js = 'module.exports = "test"'
let result = await esbuild.transform(js, {
  format: 'iife',
  globalName: 'xyz',
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "module.exports = 'test'"

  result := api.Transform(js, api.TransformOptions{
    Format:     api.FormatIIFE,
    GlobalName: "xyz",
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

Указание имени глобальной переменной с форматом iife сгенерирует код, похожий на этот:

var xyz = (() => {
  ...
  var require_stdin = __commonJS((exports, module) => {
    module.exports = "test";
  });
  return require_stdin();
})();

Имя глобальной переменной также может быть составным выражением свойства, в этом случае esbuild сгенерирует глобальную переменную с этим свойством. Существующие глобальные переменные, с которыми возникает конфликт, не будут перезаписаны. Это может быть использовано для реализации "именования пространства имён", где несколько независимых скриптов добавляют свои экспорты в один и тот же глобальный объект. Например:

echo 'module.exports = "test"' | esbuild --format=iife --global-name='example.versions["1.0"]'
import * as esbuild from 'esbuild'

let js = 'module.exports = "test"'
let result = await esbuild.transform(js, {
  format: 'iife',
  globalName: 'example.versions["1.0"]',
})
console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "module.exports = 'test'"

  result := api.Transform(js, api.TransformOptions{
    Format:     api.FormatIIFE,
    GlobalName: `example.versions["1.0"]`,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

Составное имя глобальной переменной, использованное выше, генерирует код, похожий на этот:

var example = example || {};
example.versions = example.versions || {};
example.versions["1.0"] = (() => {
  ...
  var require_stdin = __commonJS((exports, module) => {
    module.exports = "test";
  });
  return require_stdin();
})();

Комментарии к лицензированию

Поддерживается: Сборка и Преобразование

"Комментарий к лицензированию" - это любой комментарий уровня оператора в JS или комментарий уровня правила в CSS, содержащий @license или @preserve или начинающийся с //! или /*!. По умолчанию эти комментарии сохраняются в выходных файлах, так как это соответствует намерениям первоначальных авторов кода. Однако это поведение можно настроить, используя один из следующих параметров:

  • none
    Не сохранять комментарии к лицензированию.

  • inline
    Сохранить все комментарии к лицензированию.

  • eof
    Переместить все комментарии к лицензированию в конец файла.

  • linked
    Переместить все комментарии к лицензированию в отдельный файл и связать их с комментарием.

  • .LEGAL.txt
    Переместить все комментарии к лицензированию в отдельный файл, но не ссылаться на них.

По умолчанию используется eof, когда включена сборка, и inline в противном случае. Настройка режима обработки комментариев к лицензированию выглядит следующим образом:

esbuild app.js --legal-comments=eof
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  legalComments: 'eof',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:   []string{"app.js"},
    LegalComments: api.LegalCommentsEndOfFile,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Обратите внимание, что "уровень оператора" для JS и "уровень правила" для CSS означает, что комментарий должен появляться в контексте, где разрешены несколько операторов или правил, таких как в глобальной области видимости или в блоке оператора или правила. Поэтому комментарии внутри выражений или на уровне объявления не считаются комментариями к лицензированию.

Предел строки

Поддерживается: Сборка и Преобразование

Этот параметр предназначен для предотвращения генерации esbuild выходных файлов с очень длинными строками, что может повысить производительность редактирования в плохо реализованных текстовых редакторах. Установите это значение на положительное целое число, чтобы указать esbuild на завершение строки сразу после того, как она превысит указанное количество байтов. Например, это обрезает длинные строки после ~80 символов:

esbuild app.ts --line-limit=80
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.ts'],
  lineLimit: 80,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    LineLimit:   80,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Строки обрезаются после достижения предела, а не до него, потому что проще проверить, когда предел пройден, чем предсказать, когда предел будет достигнут, и потому что быстрее избежать возврата и переписывания вещей при генерации выходного файла. Таким образом, предел является приблизительным.

Этот параметр применяется как к JavaScript, так и к CSS и работает даже при отключенной минификации. Обратите внимание, что включение этого параметра увеличит размер файлов, так как дополнительные новые строки занимают дополнительное место в файле (даже после сжатия gzip).

Разделение

Поддерживается: Сборка

Разделение кода всё ещё находится в стадии разработки. В настоящее время он работает только с форматом вывода esm. Также существует известная проблема с порядком операторов import в различных блоках разделения кода. Вы можете следить за запросм по отслеживанию для получения обновлений об этой функции.

Это включает "разделение кода", которое служит двум целям:

  • Код, используемый в нескольких точках входа, выносится в отдельный общий файл, который импортируют обе точки входа. Таким образом, если пользователь сначала переходит на одну страницу, а затем на другую, ему не нужно загружать весь JavaScript для второй страницы с нуля, если общая часть уже загружена и кэширована браузером.

  • Код, на который ссылаются через асинхронное выражение import(), будет вынесен в отдельный файл и загружен только при вычислении этого выражения. Это позволяет улучшить время начальной загрузки приложения, загружая только необходимый код при запуске, а затем лениво загружая дополнительный код при необходимости позже.

    Без включенной возможности разделения кода выражение import() превращается в Promise.resolve().then(() => require()). Это сохраняет асинхронную семантику выражения, но это означает, что импортированный код включен в один и тот же пакет вместо разделения в отдельный файл.

При включении разделения кода необходимо также настроить выходной каталог, используя параметр outdir:

esbuild home.ts about.ts --bundle --splitting --outdir=out --format=esm
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['home.ts', 'about.ts'],
  bundle: true,
  splitting: true,
  outdir: 'out',
  format: 'esm',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"home.ts", "about.ts"},
    Bundle:      true,
    Splitting:   true,
    Outdir:      "out",
    Format:      api.FormatESModule,
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Расположение вывода

Разрешить перезапись

Поддерживается: Build

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

esbuild app.js --outdir=. --allow-overwrite
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  outdir: '.',
  allowOverwrite: true,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:    []string{"app.js"},
    Outdir:         ".",
    AllowOverwrite: true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Имена ресурсов

Поддерживается: Build

Этот параметр контролирует имена файлов дополнительных выходных файлов, генерируемых, когда загрузчик установлен на file. Он настраивает выходные пути с использованием шаблона с плейсхолдерами, которые будут заменены значениями, специфичными для файла, при генерации выходного пути. Например, указание шаблона имени ресурса assets/[name]-[hash] помещает все ресурсы в подкаталог под названием assets внутри выходного каталога и включает хэш содержимого ресурса в имя файла. Это выглядит так:

esbuild app.js --asset-names=assets/[name]-[hash] --loader:.png=file --bundle --outdir=out
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  assetNames: 'assets/[name]-[hash]',
  loader: { '.png': 'file' },
  bundle: true,
  outdir: 'out',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    AssetNames:  "assets/[name]-[hash]",
    Loader: map[string]api.Loader{
      ".png": api.LoaderFile,
    },
    Bundle: true,
    Outdir: "out",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

В шаблонах путей ресурсов можно использовать четыре плейсхолдера:

  • [dir]

    Это относительный путь от каталога, содержащего файл ресурса, к каталогу outbase. Его цель — сделать пути вывода ресурсов более эстетичными, отражая структуру входного каталога внутри выходного каталога.

  • [name]

    Это исходное имя файла ресурса без расширения. Например, если ресурс изначально назывался image.png, то [name] будет заменено на image в шаблоне. Использование этого плейсхолдера необязательно; он существует только для создания удобочитаемых имён ресурсов, что упрощает отладку.

  • [hash]

    Это хэш содержимого ресурса, что полезно для предотвращения коллизий имён. Например, ваш код может импортировать components/button/icon.png и components/select/icon.png, в этом случае вам потребуется хэш для различения двух ресурсов, которые оба называются icon.

  • [ext]

    Это расширение файла ресурса (то есть всё после последнего символа .). Его можно использовать для размещения различных типов ресурсов в разных каталогах. Например, --asset-names=assets/[ext]/[name]-[hash] может записать ресурс с именем image.png как assets/png/image-CQFGD2NG.png.

Шаблоны путей ресурсов не обязательно должны включать расширение файла. Исходное расширение файла ресурса автоматически добавится в конец выходного пути после подстановки шаблона.

Этот параметр аналогичен параметрам chunk names и entry names.

Имена чанков

Поддерживается: Build

Этот параметр контролирует имена файлов чанков общего кода, которые автоматически генерируются при включенном разделении кода. Он настраивает выходные пути с использованием шаблона с плейсхолдерами, которые будут заменены значениями, специфичными для чанка, при генерации выходного пути. Например, указание шаблона имени чанка chunks/[name]-[hash] помещает все сгенерированные чанки в подкаталог chunks внутри выходного каталога и включает хэш содержимого чанка в имя файла. Вот пример:

esbuild app.js --chunk-names=chunks/[name]-[hash] --bundle --outdir=out --splitting --format=esm
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  chunkNames: 'chunks/[name]-[hash]',
  bundle: true,
  outdir: 'out',
  splitting: true,
  format: 'esm',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    ChunkNames:  "chunks/[name]-[hash]",
    Bundle:      true,
    Outdir:      "out",
    Splitting:   true,
    Format:      api.FormatESModule,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

В шаблонах путей чанков можно использовать три плейсхолдера:

  • [name]

    В настоящее время это всегда текст chunk, хотя этот плейсхолдер может принимать дополнительные значения в будущих выпусках.

  • [hash]

    Это хэш содержимого чанка. Это необходимо для различения разных чанков в случае генерации нескольких чанков общего кода.

  • [ext]

    Это расширение файла чанка (т. е. всё после последнего символа .). Его можно использовать для размещения различных типов чанков в разных каталогах. Например, --chunk-names=chunks/[ext]/[name]-[hash] может записать чанк как chunks/css/chunk-DEFJT7KY.css.

Шаблоны путей чанков не обязательно должны включать расширение файла. Настроенное расширение выходного файла для соответствующего типа содержимого будет автоматически добавлено в конец выходного пути после подстановки шаблона.

Обратите внимание, что этот параметр управляет только именами автоматически сгенерированных чанков общего кода. Он не управляет именами выходных файлов, связанных с точками входа. Имена этих файлов в настоящее время определяются путем исходного файла точки входа по отношению к каталогу outbase, и это поведение нельзя изменить. В будущем будет добавлен дополнительный API-параметр, позволяющий изменить имена файлов выходных файлов точек входа.

Этот параметр аналогичен параметрам asset names и entry names.

Имена точек входа

Поддерживается: Build

Этот параметр контролирует имена выходных файлов, соответствующих каждому файлу входной точки входа. Он настраивает выходные пути с использованием шаблона с плейсхолдерами, которые будут заменены значениями, специфичными для файла, при генерации выходного пути. Например, указание шаблона имени точки входа [dir]/[name]-[hash] включает хэш выходного файла в имя файла и помещает файлы в выходной каталог, потенциально в подкаталог (см. детали о [dir] ниже). Вот пример:

esbuild src/main-app/app.js --entry-names=[dir]/[name]-[hash] --outbase=src --bundle --outdir=out
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['src/main-app/app.js'],
  entryNames: '[dir]/[name]-[hash]',
  outbase: 'src',
  bundle: true,
  outdir: 'out',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"src/main-app/app.js"},
    EntryNames:  "[dir]/[name]-[hash]",
    Outbase:     "src",
    Bundle:      true,
    Outdir:      "out",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

В шаблонах путей точек входа можно использовать четыре плейсхолдера:

  • [dir]

    Это относительный путь от каталога, содержащего файл входной точки входа, к каталогу outbase. Его цель — избежать коллизий между идентичными точками входа в разных подкаталогах.

    Например, если есть две точки входа src/pages/home/index.ts и src/pages/about/index.ts, каталог outbase — src, а шаблон имён точек входа — [dir]/[name], выходной каталог будет содержать pages/home/index.js и pages/about/index.js. Если шаблон имён точек входа был бы просто [name], сборка бы не прошла, так как было бы две выходные файла с одинаковым выходным путём index.js внутри выходного каталога.

  • [name]

    Это исходное имя точки входа без расширения. Например, если файл входной точки входа называется app.js, то [name] будет заменено на app в шаблоне.

  • [hash]

    Это хэш содержимого выходного файла, который может использоваться для оптимального использования кэширования браузера. Добавление [hash] к именам точек входа означает, что esbuild рассчитает хэш, который относится ко всему содержимому соответствующего выходного файла (и любому выходному файлу, который он импортирует, если активен раздел кода). Хэш предназначен для изменения только в том случае, если какой-либо из входных файлов, относящихся к этому выходному файлу, изменены.

    После этого ваш веб-сервер может сообщать браузерам, что кэшировать эти файлы навсегда (на практике вы можете сказать, что они истекают через очень долгое время, например, через год). Затем вы можете использовать информацию в metafile, чтобы определить, какой выходной путь соответствует какой входной точке входа, чтобы вы знали, какой путь следует включить в тег <script>.

  • [ext]

    Это расширение файла, в котором будет записана точка входа (то есть параметр out extension, а не исходное расширение файла). Его можно использовать для размещения разных типов точек входа в разных каталогах. Например, --entry-names=entries/[ext]/[name] может записать выходной файл для app.ts в entries/js/app.js.

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

Этот параметр аналогичен параметрам asset names и chunk names.

Расширение выходного файла

Поддерживается: Build

Этот параметр позволяет настроить расширение файла, генерируемого esbuild, на что-то другое, чем .js или .css. В частности, расширения файлов .mjs и .cjs имеют специальное значение в node (они указывают на файл в формате ESM и CommonJS соответственно). Этот параметр полезен, если вы используете esbuild для генерации нескольких файлов и вам нужно использовать параметр outdir вместо outfile. Вы можете использовать его так:

esbuild app.js --bundle --outdir=dist --out-extension:.js=.mjs
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  outdir: 'dist',
  outExtension: { '.js': '.mjs' },
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Outdir:      "dist",
    OutExtension: map[string]string{
      ".js": ".mjs",
    },
    Write: true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Outbase

Поддерживается: Build

Если ваша сборка содержит несколько точек входа в разных каталогах, структура каталогов будет скопирована в выходной каталог относительно каталога outbase. Например, если есть две точки входа src/pages/home/index.ts и src/pages/about/index.ts, а каталог outbase — src, выходной каталог будет содержать pages/home/index.js и pages/about/index.js. Вот как его использовать:

esbuild src/pages/home/index.ts src/pages/about/index.ts --bundle --outdir=out --outbase=src
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: [
    'src/pages/home/index.ts',
    'src/pages/about/index.ts',
  ],
  bundle: true,
  outdir: 'out',
  outbase: 'src',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{
      "src/pages/home/index.ts",
      "src/pages/about/index.ts",
    },
    Bundle:  true,
    Outdir:  "out",
    Outbase: "src",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Если каталог outbase не указан, он по умолчанию устанавливается в каталоге наименьшего общего предка среди всех путей входных точек. Это src/pages в приведённом выше примере, что означает, что по умолчанию выходной каталог будет содержать home/index.js и about/index.js.

Выходной каталог

Поддерживается: Сборка

Этот параметр устанавливает выходной каталог для операции сборки. Например, эта команда сгенерирует каталог под названием out:

esbuild app.js --bundle --outdir=out
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  outdir: 'out',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Outdir:      "out",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Выходной каталог будет сгенерирован, если он ещё не существует, но не будет очищен, если он уже содержит некоторые файлы. Любые сгенерированные файлы будут безмолвно перезаписывать существующие файлы с тем же именем. Вы должны очистить выходной каталог перед запуском esbuild, если хотите, чтобы выходной каталог содержал только файлы из текущей сборки esbuild.

Если ваша сборка содержит несколько входных точек в отдельных каталогах, структура каталогов будет воспроизведена в выходном каталоге, начиная с каталога наименьшего общего предка среди всех путей входных точек. Например, если есть две входные точки src/home/index.ts и src/about/index.ts, выходной каталог будет содержать home/index.js и about/index.js. Если вы хотите настроить это поведение, вы должны изменить каталог outbase.

Имя выходного файла

Поддерживается: Сборка

Этот параметр устанавливает имя выходного файла для операции сборки. Это применимо только в случае единственной входной точки. Если есть несколько входных точек, вы должны использовать параметр outdir для указания выходного каталога. Использование outfile выглядит так:

esbuild app.js --bundle --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Публичный путь

Поддерживается: Сборка

Это полезно в сочетании с загрузчиком внешних файлов. По умолчанию этот загрузчик экспортирует имя импортированного файла как строку, используя default экспорт. Параметр public path позволяет префиксровать базовый путь к экспортированной строке каждого файла, загруженного этим загрузчиком:

esbuild app.js --bundle --loader:.png=file --public-path=https://www.example.com/v1 --outdir=out
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  loader: { '.png': 'file' },
  publicPath: 'https://www.example.com/v1',
  outdir: 'out',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Loader: map[string]api.Loader{
      ".png": api.LoaderFile,
    },
    Outdir:     "out",
    PublicPath: "https://www.example.com/v1",
    Write:      true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Запись

Поддерживается: Сборка

Вызов API сборки может либо напрямую записать в файловую систему, либо вернуть файлы, которые были бы записаны, как буферы в памяти. По умолчанию CLI и JavaScript API записывают в файловую систему, а API Go — нет. Чтобы использовать буферы в памяти:

import * as esbuild from 'esbuild'

let result = await esbuild.build({
  entryPoints: ['app.js'],
  sourcemap: 'external',
  write: false,
  outdir: 'out',
})

for (let out of result.outputFiles) {
  console.log(out.path, out.contents, out.hash, out.text)
}
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Sourcemap:   api.SourceMapExternal,
    Write:       false,
    Outdir:      "out",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }

  for _, out := range result.OutputFiles {
    fmt.Printf("%v %v %s\n", out.Path, out.Contents, out.Hash)
  }
}

Свойство hash — это хэш поля contents и предоставлено для удобства. Алгоритм хэширования (в настоящее время XXH64) зависит от реализации и может быть изменён в любое время между версиями esbuild.

Разрешение путей

Псевдоним

Поддерживается: Сборка

Эта функция позволяет заменить один пакет другим при объединении. В примере ниже пакет oldpkg заменяется пакетом newpkg:

esbuild app.js --bundle --alias:oldpkg=newpkg
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  write: true,
  alias: {
    'oldpkg': 'newpkg',
  },
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Write:       true,
    Alias: map[string]string{
      "oldpkg": "newpkg",
    },
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Эти новые замены происходят первыми до всей другой логики разрешения путей esbuild. Одно из применений этой функции — замена пакета, используемого только в Node.js, на пакет, подходящий для браузера, в коде сторонних разработчиков, которым вы не управляете.

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

Условия

Поддерживается: Сборка

Эта функция управляет тем, как интерпретируется поле exports в package.json. Пользовательские условия могут быть добавлены с помощью настройки условий. Вы можете указать столько таких условий, сколько хотите, и их значение полностью зависит от авторов пакетов. В настоящее время Node.js поддерживает только пользовательские условия pkg/required.cjs и production для рекомендуемого использования. Вот пример добавления пользовательских условий custom1 и custom2:

esbuild src/app.js --bundle --conditions=custom1,custom2
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['src/app.js'],
  bundle: true,
  conditions: ['custom1', 'custom2'],
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"src/app.js"},
    Bundle:      true,
    Conditions:  []string{"custom1", "custom2"},
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Как работают условия

Условия позволяют перенаправлять один и тот же путь импорта к различным файлам в разных ситуациях. Карта перенаправления, содержащая условия и пути, хранится в поле exports в файле package.json пакета. Например, это перенаправит require('pkg/foo') к pkg/required.cjs и import 'pkg/foo' к pkg/imported.mjs, используя условия import и require:

{
  "name": "pkg",
  "exports": {
    "./foo": {
      "import": "./imported.mjs",
      "require": "./required.cjs",
      "default": "./fallback.js"
    }
  }
}

Условия проверяются в том порядке, в котором они появляются в файле JSON. Таким образом, пример выше работает примерно так:

if (importPath === './foo') {
  if (conditions.has('import')) return './imported.mjs'
  if (conditions.has('require')) return './required.cjs'
  return './fallback.js'
}

По умолчанию в esbuild есть пять условий со специальным поведением, которые не могут быть отключены:

  • default

    Это условие всегда активно. Оно предназначено для того, чтобы стоять последним и предоставлять резервный вариант, когда ни одно другое условие не применимо. Это условие также активно, когда вы выполняете код напрямую в Node.js.

  • import

    Это условие активно только тогда, когда путь импорта происходит из инструкции ESM import или выражения import(). Его можно использовать для предоставления кода, специфичного для ESM. Это условие также активно, когда вы выполняете код напрямую в Node.js (но только в контексте ESM).

  • require

    Это условие активно только тогда, когда путь импорта происходит из вызова CommonJS require(). Его можно использовать для предоставления кода, специфичного для CommonJS. Это условие также активно, когда вы выполняете код напрямую в Node.js (но только в контексте CommonJS).

  • browser

    Это условие активно только тогда, когда настройка платформы esbuild platform установлена в browser. Его можно использовать для предоставления кода, специфичного для браузера. Это условие не активно, когда вы выполняете код напрямую в Node.js.

  • node

    Это условие активно только тогда, когда настройка платформы esbuild platform установлена в node. Его можно использовать для предоставления кода, специфичного для Node.js. Это условие также активно, когда вы выполняете код напрямую в Node.js.

Следующее условие также автоматически включается, когда платформа platform установлена в browser или node и не настроены пользовательские условия. Если настроены любые пользовательские условия (даже пустой список), это условие больше не будет включаться автоматически:

  • module

    Это условие можно использовать для указания esbuild выбрать вариант ESM для данного пути импорта, чтобы обеспечить лучшую обрезку дерева при объединении. Это условие не активно, когда вы выполняете код напрямую в Node.js. Оно специфично для объединяющих инструментов и происходит из Webpack.

Обратите внимание, что при использовании условий require и import ваш пакет может оказаться в сборке несколько раз! Это тонкая проблема, которая может вызвать ошибки из-за дублированных копий состояния вашего кода, а также увеличения размера итоговой сборки. Это обычно известно как проблема дублирования пакетов.

Один из способов избежать проблемы дублирования пакетов, который работает как для объединяющих инструментов, так и для выполнения напрямую в Node.js, — поместить весь ваш код в условие require как CommonJS и иметь условие import просто в качестве легкой оболочки ESM, которая вызывает require для вашего пакета и повторно экспортирует пакет с использованием синтаксиса ESM. Однако этот подход не обеспечивает хорошей обрезки дерева, так как esbuild не обрезает дерево модулей CommonJS.

Другой способ избежать проблемы дублирования пакетов — использовать специфичное для объединяющих инструментов условие module, чтобы направить объединяющие инструменты на постоянную загрузку версии ESM вашего пакета, в то время как Node.js всегда будет использовать версию CommonJS вашего пакета. И import, и module предназначены для использования с ESM, но в отличие от import, условие module всегда активно, даже если путь импорта был загружен с помощью вызова require. Это хорошо работает с объединяющими инструментами, потому что они поддерживают загрузку ESM с использованием require, но это не работает с Node.js, потому что Node.js намеренно не реализует загрузку ESM с использованием require.

Внешние

Поддерживается: Сборка

Вы можете пометить файл или пакет как внешний, чтобы исключить его из сборки. Вместо объединения импорт сохранится (используя require для форматов iife и cjs и используя import для формата esm) и будет вычислен во время выполнения.

Это имеет несколько применений. Во-первых, это можно использовать для удаления ненужного кода из вашей сборки для пути кода, который, как известно, никогда не будет выполнен. Например, пакет может содержать код, выполняемый только в Node.js, но вы будете использовать этот пакет только в браузере. Его также можно использовать для импорта кода в Node.js во время выполнения из пакета, который нельзя объединить. Например, пакет fsevents содержит нативное расширение, которое esbuild не поддерживает. Пометка чего-либо как внешнего выглядит так:

echo 'require("fsevents")' > app.js
esbuild app.js --bundle --external:fsevents --platform=node
// app.js
require("fsevents");
import * as esbuild from 'esbuild'
import fs from 'node:fs'

fs.writeFileSync('app.js', 'require("fsevents")')

await esbuild.build({
  entryPoints: ['app.js'],
  outfile: 'out.js',
  bundle: true,
  platform: 'node',
  external: ['fsevents'],
})
package main

import "io/ioutil"
import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  ioutil.WriteFile("app.js", []byte("require(\"fsevents\")"), 0644)

  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Outfile:     "out.js",
    Bundle:      true,
    Write:       true,
    Platform:    api.PlatformNode,
    External:    []string{"fsevents"},
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Вы также можете использовать символ подстановки * в пути внешнего ресурса, чтобы пометить все файлы, соответствующие этому шаблону, как внешние. Например, вы можете использовать *.png для удаления всех файлов .png или /images/* для удаления всех путей, начинающихся с /images/:

esbuild app.js --bundle "--external:*.png" "--external:/images/*"
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  outfile: 'out.js',
  bundle: true,
  external: ['*.png', '/images/*'],
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Outfile:     "out.js",
    Bundle:      true,
    Write:       true,
    External:    []string{"*.png", "/images/*"},
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Внешние пути применяются как до, так и после разрешения путей, что позволяет сравнивать как путь импорта в исходном коде, так и абсолютный путь к файловой системе. Путь считается внешним, если внешний путь соответствует в любом случае. Конкретное поведение таково:

  • Перед началом разрешения путей импортные пути проверяются на соответствие всем внешним путям. Кроме того, если внешний путь выглядит как путь к пакету (то есть не начинается с / или ./ или ../), пути импорта проверяются на наличие этого пути к пакету в качестве префикса пути.

    Это означает, что --external:@foo/bar неявно также означает --external:@foo/bar/*, который соответствует пути импорта @foo/bar/baz. Таким образом, он отмечает все пути внутри пакета @foo/bar как внешние тоже.

  • После завершения разрешения путей разрешенные абсолютные пути проверяются на соответствие всем внешним путям, которые не похожи на путь к пакету (то есть тем, которые начинаются с / или ./ или ../). Но перед проверкой внешний путь объединяется с текущим рабочим каталогом и затем нормализуется, становясь абсолютным путем (даже если он содержит символ подстановки *).

    Это означает, что вы можете пометить все в каталоге dir как внешнее с помощью --external:./dir/*. Обратите внимание, что ведущий ./ важен. Использование --external:dir/* вместо этого рассматривается как путь к пакету и не проверяется после завершения разрешения путей.

Основные поля

Поддерживается: Сборка

При импорте пакета в node поле main в файле package.json этого пакета определяет, какой файл импортируется (вместе со множеством других правил). Крупные сборщики JavaScript, включая esbuild, позволяют указать дополнительные поля package.json для попытки разрешения пакета. Существуют как минимум три таких поля, которые обычно используются:

  • main

    Это стандартное поле для всех пакетов, предназначенных для использования с node. Имя main жёстко закодировано в логике разрешения модулей node. Поскольку оно предназначено для использования с node, можно ожидать, что путь к файлу в этом поле представляет собой модуль в стиле CommonJS.

  • module

    Это поле появилось из предложения по интеграции модулей ECMAScript в node. Поэтому можно ожидать, что путь к файлу в этом поле является модулем в стиле ECMAScript. Это предложение не было принято node (node использует "type": "module" вместо него), но оно было принято крупными сборщиками, потому что модули в стиле ECMAScript приводят к лучшему удалению неиспользуемого кода.

    Для авторов пакетов: Некоторые пакеты неправильно используют поле module для браузер-специфичного кода, оставляя node-специфичный код для поля main. Это, вероятно, связано с тем, что node игнорирует поле module, и люди обычно используют сборщики только для браузер-специфичного кода. Однако сборка node-специфичного кода тоже полезна (например, она уменьшает время загрузки и запуска), и пакеты, которые помещают браузер-специфичный код в module, мешают сборщикам эффективно выполнять удаление неиспользуемого кода. Если вы пытаетесь опубликовать браузер-специфичный код в пакете, используйте поле browser вместо этого.

  • browser

    Это поле появилось из предложения, которое позволяет сборщикам заменять node-специфичные файлы или модули их браузер-совместимыми версиями. Оно позволяет указать альтернативную браузер-специфичную точку входа. Обратите внимание, что пакет может использовать одновременно поля browser и module (см. примечание ниже).

По умолчанию поля main зависят от текущего параметра платформы. Эти значения по умолчанию должны быть максимально совместимы с существующей экосистемой пакетов. Но вы можете настроить их так, если хотите:

esbuild app.js --bundle --main-fields=module,main
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  mainFields: ['module', 'main'],
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    MainFields:  []string{"module", "main"},
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Для авторов пакетов

Если вы хотите создать пакет, который использует поле browser в сочетании с полем module, то, вероятно, захотите заполнить все четыре записи в полной матрице совместимости CommonJS-vs-ESM и браузер-vs-node. Для этого вам необходимо использовать расширенную форму поля browser, которая представляет собой карту вместо просто строки:

{
  "main": "./node-cjs.js",
  "module": "./node-esm.js",
  "browser": {
    "./node-cjs.js": "./browser-cjs.js",
    "./node-esm.js": "./browser-esm.js"
  }
}

Ожидается, что поле main будет в формате CommonJS, а поле module — в формате ESM. Решение о том, какой формат модуля использовать, независимо от решения о том, использовать ли браузер-специфичную или node-специфичную вариацию. Если вы опустите одну из этих четырёх записей, вы рискуете выбрать неверную вариацию. Например, если вы опустите запись для браузерной сборки CommonJS, то вместо неё может быть выбрана сборка CommonJS для node.

Обратите внимание, что использование main, module и browser — это старый способ. Есть и новый способ, который вы можете предпочесть использовать: поле exports в package.json. Оно предоставляет другой набор компромиссов. Например, оно даёт вам более точный контроль над импортами для всех подпутей в вашем пакете (в то время как поля main предоставляют контроль только над точкой входа), но это может привести к импорту вашего пакета несколько раз в зависимости от того, как вы его настроите.

Пути node

Поддерживается: Сборка

Алгоритм разрешения модулей Node поддерживает переменную среды, называемую NODE_PATH, которая содержит список глобальных каталогов для использования при разрешении путей импорта. Эти пути проверяются на пакеты в дополнение к каталогам node_modules во всех родительских каталогах. Вы можете передать этот список каталогов в esbuild, используя переменную среды с интерфейсом командной строки и массив с интерфейсами JS и Go:

NODE_PATH=someDir esbuild app.js --bundle --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  nodePaths: ['someDir'],
  entryPoints: ['app.js'],
  bundle: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    NodePaths:   []string{"someDir"},
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Если вы используете интерфейс командной строки и хотите передать несколько каталогов, используя NODE_PATH, вам нужно разделить их с помощью : в Unix и ; в Windows. Это тот же формат, который использует сам Node.

Пакеты

Поддерживается: Сборка

Используйте этот параметр для управления тем, исключаются ли все зависимости вашего пакета из сборки или нет. Это полезно при сборке для node, поскольку многие npm-пакеты используют node-специфичные функции, которые esbuild не поддерживает при сборке (например, __dirname, import.meta.url, fs.readFileSync и *.node нативные бинарные модули). Существует два возможных значения:

  • bundle

    Это значение по умолчанию. Это означает, что импорт пакетов разрешено включать в сборку. Обратите внимание, что это значение не означает, что все пакеты будут включены в сборку, только что они могут быть включены. Вы по-прежнему можете исключить отдельные пакеты из сборки, используя external.

  • external

    Это означает, что все импорты пакетов считаются внешними для сборки и не включаются в неё. Обратите внимание, что ваши зависимости должны быть присутствовать на файловой системе, когда ваша сборка запускается. Это имеет тот же эффект, что и ручная передача каждой зависимости в external, но более лаконично. Если вы хотите настроить, какие из ваших зависимостей внешние, а какие нет, установите это значение на bundle, а затем используйте external для отдельных зависимостей.

    Этот параметр рассматривает все пути импорта, которые "выглядят как" импорты пакетов в исходном коде, как импорты пакетов. В частности, пути импорта, которые не начинаются с сегмента пути / или . или .., считаются импортами пакетов. Единственные два исключения из этого правила — это импорты подпутей (которые начинаются с символа #) и переопределения путей TypeScript с помощью paths и/или baseUrl в tsconfig.json (которые применяются в первую очередь).

Использование выглядит так:

esbuild app.js --bundle --packages=external
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  packages: 'external',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Packages:    api.PackagesExternal,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

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

Сохранение символических ссылок

Поддерживается: Сборка

Этот параметр отражает параметр --preserve-symlinks в node. Если вы используете этот параметр (или аналогичный параметр resolve.symlinks в Webpack), вам, вероятно, потребуется включить этот параметр и в esbuild. Его можно включить так:

esbuild app.js --bundle --preserve-symlinks --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  preserveSymlinks: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:      []string{"app.js"},
    Bundle:           true,
    PreserveSymlinks: true,
    Outfile:          "out.js",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Включение этого параметра заставляет esbuild определять идентичность файла по исходному пути к файлу (то есть пути без следования символическим ссылкам) вместо фактического пути к файлу (то есть пути после следования символическим ссылкам). Это может быть полезно с определёнными структурами каталогов. Имейте в виду, что это означает, что файлу может быть присвоено несколько идентификаторов, если на него указывает несколько символических ссылок, что может привести к его появлению несколько раз в сгенерированных выходных файлах.

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

Разрешение расширений

Поддерживается: Сборка

Алгоритм разрешения, используемый node, поддерживает неявные расширения файлов. Вы можете require('./file'), и он будет проверять на наличие ./file, ./file.js, ./file.json и ./file.node в таком порядке. Современные сборщики, включая esbuild, расширяют эту концепцию и на другие типы файлов. Полный порядок неявных расширений файлов в esbuild можно настроить с помощью параметра resolve extensions, который по умолчанию равен .tsx,.ts,.jsx,.js,.css,.json:

esbuild app.js --bundle --resolve-extensions=.ts,.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  resolveExtensions: ['.ts', '.js'],
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:       []string{"app.js"},
    Bundle:            true,
    ResolveExtensions: []string{".ts", ".js"},
    Write:             true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Обратите внимание, что esbuild намеренно не включает новые расширения .mjs и .cjs в этот список. Алгоритм разрешения Node не обрабатывает их как неявные расширения файлов, поэтому esbuild тоже. Если вы хотите импортировать файлы с этими расширениями, вы должны либо явно добавить расширения в пути импорта, либо изменить эту настройку, чтобы включить дополнительные расширения, которые должны быть неявными.

Рабочая директория

Поддерживается: Сборка

Этот параметр API позволяет указать рабочую директорию для сборки. По умолчанию он обычно устанавливается в текущую рабочую директорию процесса, используемого для вызова API esbuild. Рабочая директория используется esbuild для нескольких различных задач, включая разрешение относительных путей, указанных в качестве параметров API, в абсолютные пути и форматирование абсолютных путей как относительных путей в сообщениях журнала. Вот как настроить рабочую директорию esbuild:

cd "/var/tmp/custom/working/directory"
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['file.js'],
  absWorkingDir: '/var/tmp/custom/working/directory',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:   []string{"file.js"},
    AbsWorkingDir: "/var/tmp/custom/working/directory",
    Outfile:       "out.js",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Примечание: если вы используете Yarn Plug'n'Play, имейте в виду, что эта рабочая директория используется для поиска файла манифеста Yarn. Если вы запускаете esbuild из другой директории, вам нужно будет установить эту рабочую директорию в директорию, содержащую файл манифеста (или одну из его дочерних директорий), чтобы esbuild смог найти файл манифеста.

Преобразование

JSX

Поддерживается: Сборка и Преобразование

Этот параметр указывает esbuild, как обрабатывать синтаксис JSX. Вот доступные параметры:

  • transform

    Это указывает esbuild на преобразование JSX в JS с использованием универсального преобразования, используемого во многих библиотеках, использующих синтаксис JSX. Каждый элемент JSX преобразуется в вызов функции JSX factory с компонентом элемента (или фрагментом JSX fragment для фрагментов) в качестве первого аргумента. Вторым аргументом является массив свойств (или null, если свойств нет). Любые дочерние элементы становятся дополнительными аргументами после второго аргумента.

    Если вы хотите настроить этот параметр для каждого файла, вы можете сделать это с помощью комментария // @jsxRuntime classic. Это соглашение из плагина JSX Babel, которому следует esbuild.

  • preserve

    Это сохраняет синтаксис JSX в выходном коде вместо его преобразования в вызовы функций. Элементы JSX обрабатываются как синтаксические конструкции первого класса и по-прежнему затрагиваются другими настройками, такими как минификация и искажение свойств.

    Обратите внимание, что это означает, что выходные файлы больше не являются допустимым JavaScript-кодом. Эта функция предназначена для использования, когда вы хотите преобразовать синтаксис JSX в выходных файлах esbuild с помощью другого инструмента после объединения.

  • automatic

    Это преобразование было введено в React 17+ и очень специфично для React. Оно автоматически генерирует import инструкции из источника импорта JSX и вводит множество специальных случаев относительно того, как обрабатывается синтаксис. Детали слишком сложны для описания здесь. Для получения дополнительной информации, пожалуйста, обратитесь к документации React о новом преобразовании JSX. Если вы хотите включить режим разработки этого преобразования, вам также необходимо включить настройку JSX dev.

    Если вы хотите настроить этот параметр для каждого файла, вы можете сделать это с помощью комментария // @jsxRuntime automatic. Это соглашение из плагина JSX Babel, которому следует esbuild.

Вот пример установки преобразования JSX в preserve:

echo '<div/>' | esbuild --jsx=preserve --loader=jsx
<div />;
import * as esbuild from 'esbuild'

let result = await esbuild.transform('<div/>', {
  jsx: 'preserve',
  loader: 'jsx',
})

console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  result := api.Transform("<div/>", api.TransformOptions{
    JSX:    api.JSXPreserve,
    Loader: api.LoaderJSX,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

JSX dev

Поддерживается: Сборка и Преобразование

Если преобразование JSX установлено в automatic, то включение этой настройки заставляет esbuild автоматически вставлять имя файла и расположение источника в каждый элемент JSX. Ваша библиотека JSX может использовать эту информацию для отладки. Если преобразование JSX установлено на значение, отличное от automatic, то эта настройка не оказывает никакого влияния. Вот пример включения этой настройки:

echo '<a/>' | esbuild --loader=jsx --jsx=automatic
import { jsx } from "react/jsx-runtime";
/* @__PURE__ */ jsx("a", {});
echo '<a/>' | esbuild --loader=jsx --jsx=automatic --jsx-dev
import { jsxDEV } from "react/jsx-dev-runtime";
/* @__PURE__ */ jsxDEV("a", {}, void 0, false, {
  fileName: "<stdin>",
  lineNumber: 1,
  columnNumber: 1
}, this);
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.jsx'],
  jsxDev: true,
  jsx: 'automatic',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.jsx"},
    JSXDev:      true,
    JSX:         api.JSXAutomatic,
    Outfile:     "out.js",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

JSX factory

Поддерживается: Сборка и Преобразование

Это устанавливает функцию, которая вызывается для каждого элемента JSX. Обычно выражение JSX, такое как это:

<div>Example text</div>

преобразуется в вызов функции React.createElement следующим образом:

React.createElement("div", null, "Example text");

Вы можете вызвать что-то другое, чем React.createElement, изменив JSX factory. Например, для вызова функции h вместо (которая используется другими библиотеками, такими как Preact):

echo '<div/>' | esbuild --jsx-factory=h --loader=jsx
/* @__PURE__ */ h("div", null);
import * as esbuild from 'esbuild'

let result = await esbuild.transform('<div/>', {
  jsxFactory: 'h',
  loader: 'jsx',
})

console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  result := api.Transform("<div/>", api.TransformOptions{
    JSXFactory: "h",
    Loader:     api.LoaderJSX,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

В качестве альтернативы, если вы используете TypeScript, вы можете просто настроить JSX для TypeScript, добавив это в свой tsconfig.json файл, и esbuild должен автоматически его подхватить, без необходимости конфигурирования:

{
  "compilerOptions": {
    "jsxFactory": "h"
  }
}

Если вы хотите настроить это на основе каждого файла, вы можете сделать это с помощью комментария // @jsx h. Обратите внимание, что эта настройка не применяется, когда преобразование JSX установлено на automatic.

JSX фрагмент

Поддерживается: Сборка и Преобразование

Это устанавливает функцию, которая вызывается для каждого фрагмента JSX. Обычно выражение JSX фрагмента, такое как это:

<>Stuff</>

преобразуется в использование компонента React.Fragment следующим образом:

React.createElement(React.Fragment, null, "Stuff");

Вы можете использовать другой компонент, кроме React.Fragment, изменив JSX фрагмент. Например, для использования компонента Fragment вместо (который используется другими библиотеками, такими как Preact):

echo '<>x</>' | esbuild --jsx-fragment=Fragment --loader=jsx
/* @__PURE__ */ React.createElement(Fragment, null, "x");
import * as esbuild from 'esbuild'

let result = await esbuild.transform('<>x</>', {
  jsxFragment: 'Fragment',
  loader: 'jsx',
})

console.log(result.code)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  result := api.Transform("<>x</>", api.TransformOptions{
    JSXFragment: "Fragment",
    Loader:      api.LoaderJSX,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

В качестве альтернативы, если вы используете TypeScript, вы можете просто настроить JSX для TypeScript, добавив это в свой tsconfig.json файл, и esbuild должен автоматически его подхватить, без необходимости конфигурирования:

{
  "compilerOptions": {
    "jsxFragmentFactory": "Fragment"
  }
}

Если вы хотите настроить эту настройку на основе каждого файла, вы можете сделать это с помощью комментария // @jsxFrag Fragment. Обратите внимание, что эта настройка не применяется, когда преобразование JSX установлено на automatic.

Источник импорта JSX

Поддерживается: Сборка и Преобразование

Если преобразование JSX установлено на automatic, то установка этого параметра позволяет изменить библиотеку, которую esbuild использует для автоматического импорта своих функций-помощников JSX. Обратите внимание, что это работает только с преобразованием JSX, которое специфично для React 17+. Если вы установите источник импорта JSX на your-pkg, то этот пакет должен экспортировать по крайней мере следующие элементы:

import { createElement } from "your-pkg"
import { Fragment, jsx, jsxs } from "your-pkg/jsx-runtime"
import { Fragment, jsxDEV } from "your-pkg/jsx-dev-runtime"

Подпути /jsx-runtime и /jsx-dev-runtime жестко закодированы по умолчанию и не могут быть изменены. Импорты jsx и jsxs используются, когда режим JSX dev выключен, а импорт jsxDEV используется, когда JSX dev mode включен. Значение этих импортов описано в документации React о новом преобразовании JSX. Импорт createElement используется независимо от режима JSX dev, когда элемент имеет распространение свойств, за которым следует свойство key, что выглядит так:

return <div {...props} key={key} />

Вот пример установки источника импорта JSX на preact:

esbuild app.jsx --jsx-import-source=preact --jsx=automatic
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.jsx'],
  jsxImportSource: 'preact',
  jsx: 'automatic',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:     []string{"app.jsx"},
    JSXImportSource: "preact",
    JSX:             api.JSXAutomatic,
    Outfile:         "out.js",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

В качестве альтернативы, если вы используете TypeScript, вы можете просто настроить источник импорта JSX для TypeScript, добавив это в свой tsconfig.json файл, и esbuild должен автоматически его подхватить, без необходимости конфигурирования:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}

И если вы хотите контролировать эту настройку на основе каждого файла, вы можете сделать это с помощью комментария // @jsxImportSource your-pkg в каждом файле. Вам также может потребоваться добавить комментарий // @jsxRuntime automatic, если преобразование JSX еще не было установлено другими способами или если вы хотите настроить его на основе каждого файла.

JSX побочные эффекты

Поддерживается: Сборка и Преобразование

По умолчанию esbuild предполагает, что выражения JSX не имеют побочных эффектов, что означает, что они аннотированы с помощью /* @__PURE__ */ комментариями и удаляются во время объединения, если они не используются. Это соответствует обычному использованию JSX для виртуального DOM и применимо к подавляющему большинству библиотек JSX. Однако некоторые люди создали библиотеки JSX, которые не обладают этим свойством (конкретно выражения JSX могут иметь произвольные побочные эффекты и не могут быть удалены, если они не используются). Если вы используете такую библиотеку, вы можете использовать эту настройку, чтобы указать esbuild, что выражения JSX имеют побочные эффекты:

esbuild app.jsx --jsx-side-effects
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.jsx'],
  outfile: 'out.js',
  jsxSideEffects: true,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:    []string{"app.jsx"},
    Outfile:        "out.js",
    JSXSideEffects: true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Поддерживаемые

Поддерживается: Сборка и Преобразование

Эта настройка позволяет настроить набор поддерживаемых функций синтаксиса esbuild на уровне отдельных функций синтаксиса. Например, вы можете использовать это, чтобы указать esbuild, что BigInt не поддерживаются, чтобы esbuild выдавал ошибку при попытке их использования. Обычно это настраивается для вас при использовании настройки target, которую вы обычно должны использовать вместо этой настройки. Если целевая версия указана дополнительно к этой настройке, эта настройка переопределит то, что указано целевой версией.

Вот несколько примеров, почему вы можете захотеть использовать эту настройку вместо или в дополнение к настройке целевой версии:

  • JavaScript-среды исполнения часто реализуют новые синтаксические конструкции быстрее, чем эквивалентные старые, но это медленнее. Вы можете ускорить процесс, сказав esbuild, что эта синтаксическая конструкция не поддерживается. Например, V8 имеет долгое время сохраняющуюся проблему производительности, касающуюся разброса объектов, которую можно избежать, вручную копируя свойства вместо использования синтаксиса разброса объектов.

  • Существует множество других реализаций JavaScript помимо тех, которые распознаёт настройка target esbuild, и они могут не поддерживать определённые функции. Если вы нацеливаетесь на такую реализацию, вы можете использовать эту настройку для конфигурирования esbuild с набором совместимости пользовательских синтаксических конструкций, не изменяя сам esbuild. Например, JavaScript-парсер TypeScript может не поддерживать произвольные имена идентификаторов пространства имён модулей, поэтому вы можете отключить их при нацеливании на JavaScript-парсер TypeScript.

  • Возможно, вы обрабатываете вывод esbuild с помощью другого инструмента, и вы можете захотеть, чтобы esbuild преобразовывал определённые функции, а другой инструмент — определённые другие функции. Например, если вы используете esbuild для преобразования файлов по отдельности в ES5, но затем передаёте результат в Webpack для объединения, вы можете захотеть сохранить import() выражения, даже если они являются синтаксической ошибкой в ES5.

Если вы хотите, чтобы esbuild рассматривал определённую синтаксическую конструкцию как неподдерживаемую, вы можете указать это так:

esbuild app.js --supported:bigint=false
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  supported: {
    'bigint': false,
  },
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Supported: map[string]bool{
      "bigint": false,
    },
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Синтаксические конструкции указываются с использованием имен функций, специфичных для esbuild. Полный набор имён функций следующий:

JavaScript:

  • arbitrary-module-namespace-names
  • array-spread
  • arrow
  • async-await
  • async-generator
  • bigint
  • class
  • class-field
  • class-private-accessor
  • class-private-brand-check
  • class-private-method
  • class-private-static-accessor
  • class-private-static-field
  • class-private-static-method
  • class-static-blocks
  • class-static-field
  • const-and-let
  • decorators
  • default-argument
  • destructuring
  • dynamic-import
  • exponent-operator
  • export-star-as
  • for-await
  • for-of
  • function-name-configurable
  • function-or-class-property-access
  • generator
  • hashbang
  • import-assertions
  • import-attributes
  • import-meta
  • inline-script
  • logical-assignment
  • nested-rest-binding
  • new-target
  • node-colon-prefix-import
  • node-colon-prefix-require
  • nullish-coalescing
  • object-accessors
  • object-extensions
  • object-rest-spread
  • optional-catch-binding
  • optional-chain
  • regexp-dot-all-flag
  • regexp-lookbehind-assertions
  • regexp-match-indices
  • regexp-named-capture-groups
  • regexp-set-notation
  • regexp-sticky-and-unicode-flags
  • regexp-unicode-property-escapes
  • rest-argument
  • template-literal
  • top-level-await
  • typeof-exotic-object-is-object
  • unicode-escapes
  • using

CSS:

  • color-functions
  • gradient-double-position
  • gradient-interpolation
  • gradient-midpoints
  • hwb
  • hex-rgba
  • inline-style
  • inset-property
  • is-pseudo-class
  • modern-rgb-hsl
  • nesting
  • rebecca-purple

Целевая среда

Поддерживается: Сборка и Преобразование

Это устанавливает целевую среду для сгенерированного JavaScript- и/или CSS-кода. Это говорит esbuild о преобразовании синтаксиса JavaScript, который слишком новый для этих сред, в более старый синтаксис JavaScript, который будет работать в этих средах. Например, оператор ?? был представлен в Chrome 80, поэтому esbuild преобразует его в эквивалентное (но более подробное) условное выражение при нацеливании на Chrome 79 или более ранние версии.

Обратите внимание, что это касается только синтаксических конструкций, а не API. Это не автоматически добавляет полифилы для новых API, которые не используются этими средами. Вам нужно будет явно импортировать полифилы для необходимых API (например, импортировав core-js). Автоматическая инъекция полифилов не входит в область действия esbuild.

Каждая целевая среда — это имя среды, за которым следует номер версии. Следующие имена сред в настоящее время поддерживаются:

  • chrome
  • deno
  • edge
  • firefox
  • hermes
  • ie
  • ios
  • node
  • opera
  • rhino
  • safari

Кроме того, вы также можете указать версии языка JavaScript, такие как es2020. По умолчанию целевая среда — esnext, что означает, что esbuild по умолчанию будет предполагать, что все самые последние функции JavaScript и CSS поддерживаются. Вот пример, который настраивает несколько целевых сред. Вам не нужно указывать все из них; вы можете указать только подмножество целевых сред, которые важны для вашего проекта. Также вы можете быть более точными в отношении номеров версий (например, node12.19.0 вместо просто node12):

esbuild app.js --target=es2020,chrome58,edge16,firefox57,node12,safari11
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  target: [
    'es2020',
    'chrome58',
    'edge16',
    'firefox57',
    'node12',
    'safari11',
  ],
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Target:      api.ES2020,
    Engines: []api.Engine{
      {Name: api.EngineChrome, Version: "58"},
      {Name: api.EngineEdge, Version: "16"},
      {Name: api.EngineFirefox, Version: "57"},
      {Name: api.EngineNode, Version: "12"},
      {Name: api.EngineSafari, Version: "11"},
    },
    Write: true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Вы можете обратиться к загрузчику JavaScript за подробностями о том, какие синтаксические конструкции были представлены с какими версиями языка. Имейте в виду, что, хотя версии языка JavaScript, такие как es2020, идентифицируются по году, это год утверждения спецификации. Это не имеет отношения к году, когда все основные браузеры реализуют эту спецификацию, что часто происходит раньше или позже, чем этот год.

Если вы используете синтаксическую конструкцию, преобразование которой esbuild ещё не поддерживает для вашей текущей целевой среды, esbuild выведет ошибку, где используется неподдерживаемый синтаксис. Это часто происходит при нацеливании на версию языка es5, например, поскольку esbuild поддерживает преобразование только большинства новых синтаксических конструкций JavaScript в es6.

Если вам нужно настроить набор поддерживаемых синтаксических конструкций на уровне отдельных функций помимо или вместо того, что предоставляет target, вы можете сделать это с помощью настройки supported.

Оптимизация

Определение

Поддерживается: Сборка и Преобразование

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

echo 'hooks = DEBUG && require("hooks")' | esbuild --define:DEBUG=true
hooks = require("hooks");
echo 'hooks = DEBUG && require("hooks")' | esbuild --define:DEBUG=false
hooks = false;
import * as esbuild from 'esbuild'let js = 'hooks = DEBUG && require("hooks")'(await esbuild.transform(js, {
  define: { DEBUG: 'true' },
})).code
'hooks = require("hooks");\n'
(await esbuild.transform(js, {
  define: { DEBUG: 'false' },
})).code
'hooks = false;\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "hooks = DEBUG && require('hooks')"

  result1 := api.Transform(js, api.TransformOptions{
    Define: map[string]string{"DEBUG": "true"},
  })

  if len(result1.Errors) == 0 {
    fmt.Printf("%s", result1.Code)
  }

  result2 := api.Transform(js, api.TransformOptions{
    Define: map[string]string{"DEBUG": "false"},
  })

  if len(result2.Errors) == 0 {
    fmt.Printf("%s", result2.Code)
  }
}

Каждый define элемент сопоставляет идентификатор строке кода, содержащей выражение. Выражение в строке должно быть либо JSON-объектом (null, boolean, number, string, массив или объект), либо одиночным идентификатором. Заменяющие выражения, отличные от массивов и объектов, подставляются непосредственно, что означает, что они могут участвовать в свёртке констант. Выражения-замены, являющиеся массивами или объектами, хранятся в переменной, а затем ссылаются с помощью идентификатора вместо непосредственной подстановки, что предотвращает подстановку повторяющихся копий значения, но означает, что значения не участвуют в свёртке констант.

Если вы хотите заменить что-либо строковой литеральной, имейте в виду, что значение замены, передаваемое esbuild, само должно содержать кавычки, потому что каждый define элемент сопоставляет строке, содержащей код. Опускание кавычек означает, что значение замены — это идентификатор вместо этого. Это показано в примере ниже:

echo 'id, str' | esbuild --define:id=text --define:str=\"text\"
text, "text";
import * as esbuild from 'esbuild'(await esbuild.transform('id, str', {
  define: { id: 'text', str: '"text"' },
})).code
'text, "text";\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  result := api.Transform("id, text", api.TransformOptions{
    Define: map[string]string{
      "id":  "text",
      "str": "\"text\"",
    },
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

Если вы используете командную строку, имейте в виду, что разные оболочки имеют разные правила для экранирования символов двойных кавычек (которые необходимы, когда значение замены — строка). Используйте экранирование с помощью \" обратной косой черты, так как оно работает как в bash, так и в командной строке Windows. Другие способы экранирования двойных кавычек, работающие в bash, такие как окружение их одинарными кавычками, не будут работать в Windows, так как командная строка Windows не удаляет одинарные кавычки. Это актуально при использовании командной строки из npm-скрипта в вашем файле package.json, что люди ожидают, что будет работать на всех платформах:

{
  "scripts": {
    "build": "esbuild --define:process.env.NODE_ENV=\\\"production\\\" app.js"
  }
}

Если вы всё ещё сталкиваетесь с проблемами экранирования кавычек между платформами в различных оболочках, вам, вероятно, придётся перейти к использованию JavaScript-API вместо этого. Там вы можете использовать обычный синтаксис JavaScript для устранения межплатформенных различий.

Если вы ищете более сложную форму функции define, которая может заменить выражение чем-то, отличным от константы (например, заменив глобальную переменную на заглушку), вы, возможно, сможете использовать похожую функцию inject.

Удаление

Поддерживается: Сборка и Преобразование

Это говорит esbuild, чтобы изменить ваш исходный код перед сборкой, удалив определённые конструкции. В настоящее время существует две возможные вещи, которые можно удалить:

  • debugger

    Передача этого флага приводит к удалению всех debugger инструкций из вывода. Это аналогично флагу drop_debugger: true, доступному в популярных минификаторах JavaScript UglifyJS и Terser.

    debugger JavaScript-инструкции заставляют активный отладчик обрабатывать инструкцию как автоматически настроенную точку останова. Код, содержащий эту инструкцию, будет автоматически приостановлен при открытом отладчике. Если отладчик не открыт, инструкция ничего не делает. Удаление этих инструкций из вашего кода просто предотвращает автоматическое приостановление отладчика при выполнении вашего кода.

    Вы можете удалить debugger инструкции так:

esbuild app.js --drop:debugger
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  drop: ['debugger'],
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Drop:        api.DropDebugger,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}
  • console

    Передача этого флага приводит к тому, что все console вызовы API будут удалены из вывода. Это аналогично флагу drop_console: true в популярных минификаторах JavaScript UglifyJS и Terser.

    Использование этого флага может привести к ошибкам в вашем коде! Этот флаг удаляет всю выражение вызова, включая все аргументы вызова. Это сделано намеренно, так как удаление вычисления аргументов вызова полезно для повышения производительности в продакшене, если эти аргументы дорого вычисляются. Однако, если какие-либо из этих аргументов имели важные побочные эффекты, использование этого флага изменит поведение вашего кода. Будьте очень осторожны при использовании этого флага.

    Если вы хотите удалить вызовы API консоли, не удаляя аргументы с побочными эффектами (чтобы не вносить ошибок), вы должны пометить соответствующие вызовы API как чистые вместо этого. Например, вы можете пометить console.log как чистый с помощью --pure:console.log. Это позволит безопасно удалить эти вызовы API при включении минификации.

    Вы можете удалить console вызовы API следующим образом:

esbuild app.js --drop:console
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  drop: ['console'],
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Drop:        api.DropConsole,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Удаление меток

Поддерживается: Сборка и Преобразование

Это говорит esbuild о редактировании исходного кода перед сборкой для удаления меченых операторов с определенными именами меток. Например, рассмотрите следующий код:

function example() {
  DEV: doAnExpensiveCheck()
  return normalCodePath()
}

Если вы используете эту опцию для удаления всех меток с именем DEV, то esbuild выдаст:

function example() {
  return normalCodePath();
}

Вы можете настроить эту функцию следующим образом (что приведет к удалению и меток DEV и TEST):

esbuild app.js --drop-labels=DEV,TEST
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  dropLabels: ['DEV', 'TEST'],
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    DropLabels:  []string{"DEV", "TEST"},
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Обратите внимание, что это не единственный способ условного удаления кода. Другой, более распространенный способ — использовать функцию define для замены определенных глобальных переменных на булево значение. Например, рассмотрите следующий код:

function example() {
  DEV && doAnExpensiveCheck()
  return normalCodePath()
}

Если вы зададите DEV в false, то esbuild выдаст:

function example() {
  return normalCodePath();
}

Это почти то же самое, что и использование метки. Однако преимущество использования метки вместо глобальной переменной для условного удаления кода заключается в том, что вам не нужно беспокоиться о том, что глобальная переменная не определена, потому что кто-то забыл настроить esbuild на ее замену чем-либо. Некоторыми недостатками подхода с метками являются то, что условное удаление кода, когда метка не удаляется, несколько сложнее читать, и он не работает для кода, вложенного в вложенные выражения. Какой подход использовать для конкретного проекта — дело личных предпочтений.

Игнорирование аннотаций

Поддерживается: Сборка и Преобразование

Поскольку JavaScript — динамичный язык, идентификация неиспользуемого кода иногда очень сложна для компилятора, поэтому сообщество разработало определённые аннотации, чтобы помочь компиляторам определить, какой код следует считать свободным от побочных эффектов и пригодным для удаления. В настоящее время esbuild поддерживает два вида аннотаций побочных эффектов:

  • Встроенные /* @__PURE__ */ комментарии перед вызовами функций сообщают esbuild, что вызов функции можно удалить, если возвращаемое значение не используется. Подробнее см. опцию API pure.

  • Поле sideEffects в package.json можно использовать, чтобы сообщить esbuild, какие файлы в вашем пакете можно удалить, если все импорты из этого файла окажутся неиспользуемыми. Это соглашение из Webpack, и многие библиотеки, опубликованные в npm, уже имеют это поле в своём определении пакета. Подробнее о этом поле можно узнать в документации Webpack для этого поля.

Эти аннотации могут быть проблематичными, потому что компилятор полностью зависит от точности разработчиков, а разработчики иногда публикуют пакеты с некорректными аннотациями. Поле sideEffects особенно подвержено ошибкам для разработчиков, потому что по умолчанию оно заставляет считать все файлы в вашем пакете мёртвым кодом, если не используется ни один импорт. Если вы добавите новый файл с побочными эффектами и забудете обновить это поле, ваш пакет, вероятно, сломается, когда люди попытаются его собрать.

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

esbuild app.js --bundle --ignore-annotations
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  ignoreAnnotations: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:       []string{"app.js"},
    Bundle:            true,
    IgnoreAnnotations: true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Включение этого означает, что esbuild больше не будет учитывать /* @__PURE__ */ комментарии или поле sideEffects. Тем не менее, он всё ещё будет автоматически выполнять устранение неиспользуемых импортов, так как это не зависит от аннотаций разработчиков. В идеале этот флаг — только временная мера. Вы должны сообщить об этих проблемах разработчику пакета, чтобы они были исправлены, так как они указывают на проблему с пакетом, и они, скорее всего, приведут к ошибкам и у других пользователей.

Вставка

Поддерживается: Сборка

Эта опция позволяет автоматически заменить глобальную переменную импортом из другого файла. Это может быть полезным инструментом для адаптации кода, которым вы не управляете, к новой среде. Например, предположим, у вас есть файл под названием process-cwd-shim.js, который экспортирует плагин с именем экспорта process.cwd:

// process-cwd-shim.js
let processCwdShim = () => ''
export { processCwdShim as 'process.cwd' }
// entry.js
console.log(process.cwd())

Это предназначено для замены использования функции process.cwd() нода, чтобы предотвратить сбой пакетов, которые её вызывают, при запуске в браузере. Вы можете использовать функцию вставки для замены всех ссылок на глобальную переменную process.cwd импортом из этого файла:

esbuild entry.js --inject:./process-cwd-shim.js --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['entry.js'],
  inject: ['./process-cwd-shim.js'],
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"entry.js"},
    Inject:      []string{"./process-cwd-shim.js"},
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Это приведет к чему-то подобному:

// out.js
var processCwdShim = () => "";
console.log(processCwdShim());

Можно считать, что функция вставки подобна функции define, за исключением того, что она заменяет выражение импортом в файл, а не константой, и выражение для замены указывается с помощью имени экспорта в файле, а не с помощью встроенной строки в API esbuild.

Автоматический импорт для JSX

React (библиотека, для которой была первоначально создана синтаксическая конструкция JSX) имеет режим, который они называют automatic, где вам не нужно import ничего, чтобы использовать синтаксис JSX. Вместо этого трансформер JSX-в-JS автоматически импортирует для вас правильную функцию-фабрику JSX. Вы можете включить режим JSX automatic с помощью параметра jsx esbuild. Если вы хотите автоматический импорт для JSX и используете достаточно новую версию React, то вы должны использовать режим automatic JSX.

Однако, установка jsx в automatic, к сожалению, также означает, что вы используете высокоспецифичный для React трансформер JSX вместо стандартного универсального трансформера JSX. Это означает, что создание функции-фабрики JSX усложняется, а также что режим automatic не работает с библиотеками, которые ожидают использования стандартного трансформера JSX (включая более старые версии React).

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

const { createElement, Fragment } = require('react')
export {
  createElement as 'React.createElement',
  Fragment as 'React.Fragment',
}

В данном коде библиотека React используется в качестве примера, но этот подход можно использовать и с любой другой JSX библиотекой, внеся соответствующие изменения.

Вставка файлов без импортов

Вы также можете использовать эту функцию с файлами, не имеющими экспортов. В этом случае вставленный файл просто идёт первым перед остальной частью вывода, как если бы каждый входной файл содержал import "./file.js". Из-за того, как работают модули ECMAScript, эта вставка по-прежнему "гигиенична" в том смысле, что символы с одинаковым именем в разных файлах переименовываются, чтобы не сталкиваться друг с другом.

Условная вставка файла

Если вы хотите условно импортировать файл только в том случае, если экспорт действительно используется, вы должны пометить вставленный файл как не имеющий побочных эффектов, поместив его в пакет и добавив "sideEffects": false в файл package.json этого пакета. Этот параметр является соглашением из Webpack, которое esbuild соблюдает для любого импортированного файла, а не только для файлов, используемых с inject.

Сохранение имён

Поддерживается: Сборка и Преобразование

В JavaScript свойство name функций и классов по умолчанию соответствует ближайшему идентификатору в исходном коде. Эти синтаксические формы устанавливают свойство name функции в "fn":

function fn() {}
let fn = function() {};
fn = function() {};
let [fn = function() {}] = [];
let {fn = function() {}} = {};
[fn = function() {}] = [];
({fn = function() {}} = {});

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

esbuild app.js --minify --keep-names
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  minify: true,
  keepNames: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:       []string{"app.js"},
    MinifyWhitespace:  true,
    MinifyIdentifiers: true,
    MinifySyntax:      true,
    KeepNames:         true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Обратите внимание, что эта функция недоступна, если целевая среда была установлена на старую среду, не позволяющую esbuild изменять свойство name функций и классов. Это относится к средам, которые не поддерживают ES6.

Изменение свойств

Поддерживается: Сборка и Преобразование

Использование этой функции может привести к скрытым ошибкам в вашем коде. Не используйте эту функцию, если вы не знаете, что делаете, и точно понимаете, как она повлияет на ваш код и все ваши зависимости.

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

Вот пример, который использует регулярное выражение _$ для изменения всех свойств, заканчивающихся на подчёркивание, таких как foo_. Это изменяет print({ foo_: 0 }.foo_) на print({ a: 0 }.a):

esbuild app.js --mangle-props=_$
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  mangleProps: /_$/,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    MangleProps: "_$",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Разумной эвристикой является искажение только свойств, оканчивающихся на подчеркивание, так как обычный JS-код обычно не содержит идентификаторов такого типа. API браузеров также не используют эту конвенцию именования, поэтому это также позволяет избежать конфликтов с API браузера. Если вы хотите избежать искажения имён, таких как __defineGetter__, вы можете рассмотреть использование более сложного регулярного выражения, например, [^_]_$ (т.е. должно заканчиваться на символ, не являющийся подчеркиванием, за которым следует подчеркивание).

Это отдельный параметр, а не часть параметра minify, потому что это небезопасное преобразование, которое не работает с произвольным JavaScript-кодом. Оно работает только в том случае, если предоставленное регулярное выражение соответствует всем свойствам, которые вы хотите исказить, и не соответствует ни одному из свойств, которые вы не хотите искажать. Оно также работает только в том случае, если вы ни при каких обстоятельствах не ссылаетесь на искажённое свойство косвенно. Например, это означает, что вы не можете использовать obj[prop] для ссылки на свойство, где prop — строка, содержащая имя свойства. Конкретно, следующие синтаксические конструкции являются единственными подходящими для искажения свойств:

Синтаксис Пример
Доступ к свойству через точку x.foo_
Опциональные цепочки через точку x?.foo_
Свойства объекта x = { foo_: y }
Методы объекта x = { foo_() {} }
Поля класса class x { foo_ = y }
Методы класса class x { foo_() {} }
Связывание деструктурирования объектов let { foo_: x } = y
Присваивание деструктурирования объектов ({ foo_: x } = y)
Член выражения элемента JSX <X.foo_></X.foo_>
Имена атрибутов JSX <X foo_={y} />
Экспорт пространства имён TypeScript namespace x { export let foo_ = y }
Параметрические свойства TypeScript class x { constructor(public foo_) {} }

Используя эту функцию, имейте в виду, что имена свойств искажаются только последовательно в рамках одного вызова API esbuild, но не между вызовами API esbuild. Каждый вызов API esbuild выполняет независимую операцию искажения свойств, поэтому выходные файлы, сгенерированные двумя разными вызовами API, могут исказить одно и то же свойство в два разных имени, что может привести к некорректному поведению результирующего кода.

Свойства в кавычках

По умолчанию esbuild не изменяет содержимое строковых литералов. Это означает, что вы можете избежать искажения свойства для отдельного свойства, заключив его в кавычки. Однако вы должны последовательно использовать кавычки или отсутствие кавычек для данного свойства во всех местах для того, чтобы это работало. Например, print({ foo_: 0 }.foo_) будет искажено в print({ a: 0 }.a), а print({ 'foo_': 0 }['foo_']) не будет искажено.

Если вы хотите, чтобы esbuild также искажал содержимое строковых литералов, вы можете явно включить это поведение следующим образом:

esbuild app.js --mangle-props=_$ --mangle-quoted
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  mangleProps: /_$/,
  mangleQuoted: true,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:  []string{"app.js"},
    MangleProps:  "_$",
    MangleQuoted: api.MangleQuotedTrue,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Включение этого делает следующие синтаксические конструкции также подходящими для искажения свойств:

Синтаксис Пример
Доступ к свойству в кавычках x['foo_']
Опциональные цепочки в кавычках x?.['foo_']
Свойства объекта в кавычках x = { 'foo_': y }
Методы объекта в кавычках x = { 'foo_'() {} }
Поля класса в кавычках class x { 'foo_' = y }
Методы класса в кавычках class x { 'foo_'() {} }
Связывание деструктурирования объектов в кавычках let { 'foo_': x } = y
Присваивание деструктурирования объектов в кавычках ({ 'foo_': x } = y)
Строковые литералы слева от in 'foo_' in x

Искажение других строк

Искажение свойств в кавычках всё ещё искажает только строки в позиции имени свойства. Иногда вам может потребоваться искажать имена свойств в строках в произвольных других местах вашего кода. Для этого вы можете добавить префикс к строке комментарием /* @__KEY__ */, чтобы сообщить esbuild, что содержимое строки следует рассматривать как имя свойства, которое можно исказить. Например:

let obj = {}
Object.defineProperty(
  obj,
  /* @__KEY__ */ 'foo_',
  { get: () => 123 },
)
console.log(obj.foo_)

Это приведет к тому, что содержимое строки 'foo_' будет искажено как имя свойства (предполагается, что искажение свойств mangle-props включено и foo_ подходит для переименования). Комментарий /* @__KEY__ */ — это соглашение из Terser, популярного минимизатора JavaScript с аналогичной функцией искажения свойств.

Предотвращение переименования

Если вы хотите исключить определенные свойства из искажения, вы можете зарезервировать их с помощью дополнительного параметра. Например, это использует регулярное выражение ^__.*__$ для резервирования всех свойств, начинающихся и заканчивающихся двумя символами подчеркивания, таких как __foo__:

esbuild app.js --mangle-props=_$ "--reserve-props=^__.*__$"
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  mangleProps: /_$/,
  reserveProps: /^__.*__$/,
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:  []string{"app.js"},
    MangleProps:  "_$",
    ReserveProps: "^__.*__$",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Сохранение решений по переименованию

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

  • Вы можете настроить, какие искажённые свойства будут переименованы, отредактировав кэш перед передачей его esbuild.

  • Кэш служит списком всех искажённых свойств. Вы можете легко просмотреть его, чтобы увидеть, есть ли какие-либо непредвиденные переименования свойств.

  • Вы можете отключить искажение для отдельных свойств, установив значение переименования в false вместо строки. Это аналогично параметру зарезервировать свойства, но на основе отдельных свойств.

  • Вы можете обеспечить согласованное переименование между сборками (например, файл основной потоковой обработки и веб-работник, или библиотека и плагин). Без этой функции каждая сборка выполняла бы независимую операцию переименования, и имена искажённых свойств, вероятно, не были бы согласованными.

Например, рассмотрим следующий входной файл:

console.log({
  someProp_: 1,
  customRenaming_: 2,
  disabledRenaming_: 3
});

Если мы хотим, чтобы customRenaming_ было переименовано в cR_, и мы не хотим, чтобы disabledRenaming_ было переименовано, мы можем передать следующий JSON-кэш искажения esbuild:

{
  "customRenaming_": "cR_",
  "disabledRenaming_": false
}

JSON-кэш искажения можно передать esbuild следующим образом:

esbuild app.js --mangle-props=_$ --mangle-cache=cache.json
import * as esbuild from 'esbuild'

let result = await esbuild.build({
  entryPoints: ['app.js'],
  mangleProps: /_$/,
  mangleCache: {
    customRenaming_: "cR_",
    disabledRenaming_: false
  },
})

console.log('updated mangle cache:', result.mangleCache)
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    MangleProps: "_$",
    MangleCache: map[string]interface{}{
      "customRenaming_":   "cR_",
      "disabledRenaming_": false,
    },
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }

  fmt.Println("updated mangle cache:", result.MangleCache)
}

При включённом переименовании имен это приведёт к следующему выходному файлу:

console.log({
  a: 1,
  cR_: 2,
  disabledRenaming_: 3
});

И к следующему обновлённому кэшу искажения:

{
  "customRenaming_": "cR_",
  "disabledRenaming_": false,
  "someProp_": "a"
}

Минимизация

Поддерживается: Сборка и Преобразование

При включении сгенерированный код будет минимизирован вместо красивой печати. Минимизированный код, как правило, эквивалентен неминимизированному коду, но меньше, что означает более быструю загрузку, но труднее для отладки. Обычно минимизация кода выполняется в рабочей среде, но не в стадии разработки.

Включение минимизации в esbuild выглядит так:

echo 'fn = obj => { return obj.x }' | esbuild --minify
fn=n=>n.x;
import * as esbuild from 'esbuild'var js = 'fn = obj => { return obj.x }'
(await esbuild.transform(js, {
  minify: true,
})).code
'fn=n=>n.x;\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "fn = obj => { return obj.x }"

  result := api.Transform(js, api.TransformOptions{
    MinifyWhitespace:  true,
    MinifyIdentifiers: true,
    MinifySyntax:      true,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

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

echo 'fn = obj => { return obj.x }' | esbuild --minify-whitespace
fn=obj=>{return obj.x};
echo 'fn = obj => { return obj.x }' | esbuild --minify-identifiers
fn = (n) => {
  return n.x;
};
echo 'fn = obj => { return obj.x }' | esbuild --minify-syntax
fn = (obj) => obj.x;
import * as esbuild from 'esbuild'var js = 'fn = obj => { return obj.x }'
(await esbuild.transform(js, {
  minifyWhitespace: true,
})).code
'fn=obj=>{return obj.x};\n'
(await esbuild.transform(js, {
  minifyIdentifiers: true,
})).code
'fn = (n) => {\n  return n.x;\n};\n'
(await esbuild.transform(js, {
  minifySyntax: true,
})).code
'fn = (obj) => obj.x;\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  css := "div { color: yellow }"

  result1 := api.Transform(css, api.TransformOptions{
    Loader:           api.LoaderCSS,
    MinifyWhitespace: true,
  })

  if len(result1.Errors) == 0 {
    fmt.Printf("%s", result1.Code)
  }

  result2 := api.Transform(css, api.TransformOptions{
    Loader:            api.LoaderCSS,
    MinifyIdentifiers: true,
  })

  if len(result2.Errors) == 0 {
    fmt.Printf("%s", result2.Code)
  }

  result3 := api.Transform(css, api.TransformOptions{
    Loader:       api.LoaderCSS,
    MinifySyntax: true,
  })

  if len(result3.Errors) == 0 {
    fmt.Printf("%s", result3.Code)
  }
}

Те же концепции также относятся к CSS, а не только к JavaScript:

echo 'div { color: yellow }' | esbuild --loader=css --minify
div{color:#ff0}
import * as esbuild from 'esbuild'var css = 'div { color: yellow }'
(await esbuild.transform(css, {
  loader: 'css',
  minify: true,
})).code
'div{color:#ff0}\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  css := "div { color: yellow }"

  result := api.Transform(css, api.TransformOptions{
    Loader:            api.LoaderCSS,
    MinifyWhitespace:  true,
    MinifyIdentifiers: true,
    MinifySyntax:      true,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

Алгоритм минимизации JavaScript в esbuild обычно генерирует выходные данные, размер которых очень близок к размеру минимизированных выходных данных стандартных инструментов минимизации JavaScript. Этот тест содержит пример сравнения размеров выходных данных разных минимизаторов. Хотя esbuild не является оптимальным минимизатором JavaScript во всех случаях (и не пытается им быть), он стремится генерировать минимизированные выходные данные в пределах нескольких процентов от размера выходных данных специализированных инструментов минимизации для большинства кодов, и, конечно же, делает это намного быстрее, чем другие инструменты.

Рекомендации

Вот несколько моментов, которые следует учитывать при использовании esbuild в качестве минимизатора:

  • Вероятно, вам также следует установить опцию target, когда включена минификация. По умолчанию esbuild использует возможности современного JavaScript для уменьшения размера вашего кода. Например, a === undefined || a === null ? 1 : a может быть сжат до a ?? 1. Если вы не хотите, чтобы esbuild использовал возможности современного JavaScript при минификации, вам следует использовать более старую целевую версию языка, например, --target=es6.

  • Последовательность символов для экранирования \n будет заменена символом новой строки в литералах шаблонов JavaScript. Литералы строк также будут преобразованы в литералы шаблонов, если target поддерживает их и если это приведет к уменьшению выходного кода. Это не ошибка. Минификация означает, что вы хотите получить меньший выходной код, а последовательность экранирования \n занимает два байта, в то время как символ новой строки занимает один байт. Вы можете узнать больше об этом в статье FAQ по этому вопросу.

  • По умолчанию esbuild не будет минифицировать имена объявленных переменных верхнего уровня. Это связано с тем, что esbuild не знает, что вы будете делать с выходным кодом. Возможно, вы будете вставлять сжатый код в середину другого кода, в этом случае минификация имён объявленных переменных верхнего уровня будет небезопасной. Установка выходного формата (или включение связывания, которое выбирает формат вывода за вас, если вы его не задали) сообщает esbuild, что выходной код будет выполняться в собственной области видимости, что означает, что тогда безопасно минифицировать имена объявлений верхнего уровня.

  • Минификация не безопасна для 100% всего JavaScript-кода. Это справедливо как для esbuild, так и для других популярных минификаторов JavaScript, таких как terser. В частности, esbuild не предназначен для сохранения значения вызова .toString() на функции. Причина в том, что если весь код внутри всех функций должен сохраняться дословно, минификация практически не изменит размер кода и будет практически бесполезной. Однако это означает, что JavaScript-код, зависящий от возвращаемого значения .toString(), вероятно, сломается при минификации. Например, некоторые шаблоны в фреймворке AngularJS ломаются при минификации кода, потому что AngularJS использует .toString() для чтения имён аргументов функций. Обходным путём является использование явных аннотаций.

  • По умолчанию esbuild не сохраняет значение .name для объектов функций и классов. Это связано с тем, что большинство кодов не полагаются на это свойство, а использование более коротких имён является важной оптимизацией размера. Однако некоторые кодовые базы полагаются на свойство .name для регистрации и связывания. Если вам нужно полагаться на это, вы должны включить опцию keep names.

  • Минификатор предполагает, что встроенные функции JavaScript ведут себя так, как ожидается. Эти предположения помогают esbuild генерировать более компактный код. Если вы хотите минификатор JavaScript, который не делает никаких предположений о поведении встроенных функций JavaScript, то esbuild может не быть подходящим для вас. Вот некоторые примеры таких предположений (это не исчерпывающий список):

    • Ожидается, что Array.prototype.join ведет себя как указано. Это означает, что минификатор esbuild может безопасно преобразовать x = [1, 2, 3] + '' в x="1,2,3";.

    • Доступ к свойству log глобального объекта console не должен вызывать побочных эффектов. Это означает, что минификатор esbuild может безопасно преобразовать var a, b = a ? console.log(x) : console.log(y); в var a,b=console.log(a?x:y); (т. е. esbuild предполагает, что оценка console.log не может изменить значение a).

  • Использование определённых функций JavaScript может отключить многие оптимизации esbuild, включая минификацию. В частности, использование прямого eval и/или инструкции with предотвращает переименование идентификаторов в более короткие имена, поскольку эти функции приводят к привязке идентификаторов во время выполнения вместо компиляции. Это практически всегда непреднамеренно и происходит только потому, что люди не знают, что такое прямое eval и почему это плохо.

    Если вы планируете написать код такого вида:

    // Direct eval (will disable minification for the whole file)
    let result = eval(something)
    

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

    // Indirect eval (has no effect on the surrounding code)
    let result = (0, eval)(something)
    

    Более подробная информация о последствиях использования прямого eval и доступных альтернативах находится здесь.

  • Алгоритм минификации в esbuild пока не выполняет сложных оптимизаций кода. В частности, следующие оптимизации кода JavaScript возможны, но не выполняются esbuild (неисчерпывающий список):

    • Устранение мёртвого кода внутри функций
    • Встраивание функций
    • Переменная константная пропагация между выражениями
    • Моделирование формы объектов
    • Опускание выделения
    • Девиртуализация метода
    • Символьное выполнение
    • Подъём выражений JSX
    • Обнаружение и встраивание перечислений TypeScript

    Если ваш код использует шаблоны, требующие некоторых из этих видов оптимизации кода для сжатия, или если вы ищете оптимальный алгоритм минификации JavaScript для вашего случая использования, вы должны рассмотреть возможность использования других инструментов. Некоторые примеры инструментов, которые реализуют некоторые из этих расширенных оптимизаций кода, включают Terser и Google Closure Compiler.

Чистый

Поддерживается: Build и Transform

Различные инструменты JavaScript используют соглашение, где специальный комментарий, содержащий либо /* @__PURE__ */, либо /* #__PURE__ */ перед выражением `new` или вызовом функции, означает, что это выражение может быть удалено, если результирующее значение не используется. Он выглядит так:

let button = /* @__PURE__ */ React.createElement(Button, null);

Эта информация используется сборщиками кода, такими как esbuild, во время удаления мёртвого кода (также известного как `tree shaking`), чтобы выполнить точное удаление неиспользуемых импортов через границы модулей в ситуациях, когда сборщик не может сам доказать, что удаление безопасно из-за динамической природы JavaScript-кода.

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

Некоторые выражения, такие как JSX и определённые встроенные глобальные переменные, автоматически аннотируются как /* @__PURE__ */ в esbuild. Вы также можете настроить дополнительные глобальные переменные, чтобы они помечались как /* @__PURE__ */. Например, вы можете пометить глобальную функцию document.createElement таким образом, чтобы она автоматически удалялась из вашего пакета, когда пакет минифицируется, при условии, что результат не используется.

Стоит отметить, что эффект аннотации распространяется только на сам вызов, а не на аргументы. Аргументы с побочными эффектами по-прежнему сохраняются, даже когда включена минификация:

echo 'document.createElement(elemName())' | esbuild --pure:document.createElement
/* @__PURE__ */ document.createElement(elemName());
echo 'document.createElement(elemName())' | esbuild --pure:document.createElement --minify
elemName();
import * as esbuild from 'esbuild'let js = 'document.createElement(elemName())'
(await esbuild.transform(js, {
  pure: ['document.createElement'],
})).code
'/* @__PURE__ */ document.createElement(elemName());\n'
(await esbuild.transform(js, {
  pure: ['document.createElement'],
  minify: true,
})).code
'elemName();\n'
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "document.createElement(elemName())"

  result1 := api.Transform(js, api.TransformOptions{
    Pure: []string{"document.createElement"},
  })

  if len(result1.Errors) == 0 {
    fmt.Printf("%s", result1.Code)
  }

  result2 := api.Transform(js, api.TransformOptions{
    Pure:         []string{"document.createElement"},
    MinifySyntax: true,
  })

  if len(result2.Errors) == 0 {
    fmt.Printf("%s", result2.Code)
  }
}

Обратите внимание, что если вы пытаетесь удалить все вызовы console API-методов, таких как console.log, а также хотите удалить оценку аргументов с побочными эффектами, существует специальный случай: вы можете использовать функцию drop вместо помечания вызовов console API как чистых. Однако этот механизм специфичен для console API и не работает с другими выражениями вызова.

Удаление неиспользуемого кода

Поддерживается: Build и Transform

Удаление неиспользуемого кода — это термин, используемый сообществом JavaScript для обозначения устранения мёртвого кода, распространённой оптимизации компилятора, которая автоматически удаляет недостижимый код. В esbuild этот термин конкретно относится к удалению неиспользуемого кода на уровне объявления.

Удаление неиспользуемого кода легче всего объяснить на примере. Рассмотрим следующий файл. Есть одна используемая функция и одна неиспользуемая функция:

// input.js
function one() {
  console.log('one')
}
function two() {
  console.log('two')
}
one()

Если вы собьёте этот файл с помощью esbuild --bundle input.js --outfile=output.js, неиспользуемая функция будет автоматически удалена, оставив вам следующий выходной код:

// input.js
function one() {
  console.log("one");
}
one();

Это работает даже если мы разделим наши функции в отдельный файл библиотеки и импортируем их с помощью оператора import:

// lib.js
export function one() {
  console.log('one')
}
export function two() {
  console.log('two')
}
// input.js
import * as lib from './lib.js'
lib.one()

Если вы собьёте этот файл с помощью esbuild --bundle input.js --outfile=output.js, неиспользуемая функция и неиспользуемый импорт по-прежнему будут автоматически удалены, оставив вам следующий выходной код:

// lib.js
function one() {
  console.log("one");
}

// input.js
one();

Таким образом, esbuild будет связывать только те части ваших пакетов, которые вы фактически используете, что иногда может значительно сэкономить место. Обратите внимание, что реализация удаления неиспользуемого кода в esbuild опирается на использование ECMAScript-модулей import и export. Она не работает с модулями CommonJS. Многие пакеты в npm содержат оба формата, и esbuild пытается по умолчанию выбрать формат, который работает с удалением неиспользуемого кода. Вы можете настроить, какой формат esbuild выбирает, используя опции main fields и/или conditions в зависимости от пакета.

По умолчанию удаление неиспользуемого кода включено только при включении сборки или когда выходной формат установлен на iife, в противном случае оно отключено. Вы можете принудительно включить удаление неиспользуемого кода, установив его в true:

esbuild app.js --tree-shaking=true
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  treeShaking: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    TreeShaking: api.TreeShakingTrue,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Вы также можете принудительно отключить удаление неиспользуемого кода, установив его в false:

esbuild app.js --tree-shaking=false
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  treeShaking: false,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    TreeShaking: api.TreeShakingFalse,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Удаление неиспользуемого кода и побочные эффекты

Обнаружение побочных эффектов, используемое для удаления неиспользуемого кода, является консервативным, что означает, что esbuild рассматривает код для удаления только как мёртвый код, если может быть уверен, что скрытых побочных эффектов нет. Например, примитивные литералы, такие как 12.34 и "abcd", не имеют побочных эффектов и могут быть удалены, в то время как выражения, такие как "ab" + cd и foo.bar, не являются лишенными побочных эффектов (соединение строк вызывает toString(), который может иметь побочные эффекты, а доступ к члену может вызвать геттер, который также может иметь побочные эффекты). Даже ссылка на глобальный идентификатор рассматривается как побочный эффект, потому что он вызовет ReferenceError, если глобальная переменная с таким именем отсутствует. Вот пример:

// These are considered side-effect free
let a = 12.34;
let b = "abcd";
let c = { a: a };

// These are not considered side-effect free
// since they could cause some code to run
let x = "ab" + cd;
let y = foo.bar;
let z = { [x]: x };

Иногда желательно разрешить сжатие кода даже если невозможно автоматически определить, что в нём нет побочных эффектов. Это можно сделать с помощью аннотации чистой аннотации, которая сообщает esbuild о доверии автору кода, что в аннотированном коде нет побочных эффектов. Аннотация имеет вид /* @__PURE__ */ и может предшествовать только выражениям new или call. Вы можете аннотировать выражение функции, вызываемой немедленно, и поместить произвольные побочные эффекты в тело функции:

// This is considered side-effect free due to
// the annotation, and will be removed if unused
let gammaTable = /* @__PURE__ */ (() => {
  // Side-effect detection is skipped in here
  let table = new Uint8Array(256);
  for (let i = 0; i < 256; i++)
    table[i] = Math.pow(i / 255, 2.2) * 255;
  return table;
})();

Хотя тот факт, что /* @__PURE__ */ работает только с выражениями вызова, иногда делает код более громоздким, большое преимущество этой синтаксической конструкции заключается в том, что она портативна для многих других инструментов экосистемы JavaScript, включая популярные минификаторы JavaScript UglifyJS и Terser (которые используются другими крупными инструментами, включая Webpack и Parcel).

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

Карты исходных кодов

Корневой каталог исходных файлов

Поддерживается: Сборка и Преобразование

Эта функция актуальна только при включённых картах исходных кодов. Она позволяет задать значение поля sourceRoot в карте исходных кодов, которое указывает путь, относительно которого все другие пути в карте исходных кодов интерпретируются. Если этого поля нет, все пути в карте исходных кодов интерпретируются как относительные к каталогу, содержащему карту исходных кодов.

Вы можете настроить sourceRoot следующим образом:

esbuild app.js --sourcemap --source-root=https://raw.githubusercontent.com/some/repo/v1.2.3/
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  sourcemap: true,
  sourceRoot: 'https://raw.githubusercontent.com/some/repo/v1.2.3/',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Sourcemap:   api.SourceMapInline,
    SourceRoot:  "https://raw.githubusercontent.com/some/repo/v1.2.3/",
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Имя исходного файла

Поддерживается: Сборка и Преобразование

Этот параметр задаёт имя файла при использовании входных данных без имени файла. Это происходит при использовании API преобразования и при использовании API сборки со стандартным вводом. Настроенное имя файла отображается в сообщениях об ошибках и в картах исходных кодов. Если оно не настроено, по умолчанию используется имя файла <stdin>. Его можно настроить так:

cat app.js | esbuild --sourcefile=example.js --sourcemap
import * as esbuild from 'esbuild'
import fs from 'node:fs'

let js = fs.readFileSync('app.js', 'utf8')
let result = await esbuild.transform(js, {
  sourcefile: 'example.js',
  sourcemap: 'inline',
})

console.log(result.code)
package main

import "fmt"
import "io/ioutil"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js, err := ioutil.ReadFile("app.js")
  if err != nil {
    panic(err)
  }

  result := api.Transform(string(js),
    api.TransformOptions{
      Sourcefile: "example.js",
      Sourcemap:  api.SourceMapInline,
    })

  if len(result.Errors) == 0 {
    fmt.Printf("%s %s", result.Code)
  }
}

Карты исходных кодов

Поддерживается: Сборка и Преобразование

Карты исходных кодов могут облегчить отладку вашего кода. Они кодируют информацию, необходимую для перевода смещения строки/колонки в сгенерированном выходном файле обратно в смещение строки/колонки в соответствующем исходном файле. Это полезно, если сгенерированный код достаточно отличается от исходного кода (например, ваш исходный код — TypeScript или вы включили минификацию). Это также полезно, если вы предпочитаете просматривать отдельные файлы в средствах разработчика браузера вместо одного большого объединённого файла.

Обратите внимание, что вывод карты исходных кодов поддерживается как для JavaScript, так и для CSS, и те же параметры применяются к обоим. Всё, что ниже описывается для файлов .js, также аналогично относится к файлам .css.

Существует четыре разных режима генерации карт исходных кодов:

  1. linked

    В этом режиме карта исходных кодов генерируется в отдельный файл .js.map в дополнение к выходному файлу .js, и выходной файл .js содержит специальный комментарий //# sourceMappingURL=, указывающий на файл .js.map. Таким образом, браузер знает, где найти карту исходных кодов для данного файла при открытии отладчика. Используйте режим карты исходных кодов linked так:

esbuild app.ts --sourcemap --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.ts'],
  sourcemap: true,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Sourcemap:   api.SourceMapLinked,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}
  1. external

    В этом режиме карта исходных кодов генерируется в отдельный файл .js.map в дополнение к выходному файлу .js, но в отличие от режима linked, выходной файл .js не содержит комментарий //# sourceMappingURL=. Используйте режим карты исходных кодов external так:

esbuild app.ts --sourcemap=external --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.ts'],
  sourcemap: 'external',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Sourcemap:   api.SourceMapExternal,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}
  1. inline

    В этом режиме карта исходных кодов добавляется в конец выходного файла .js как базовый64-данные в комментарии //# sourceMappingURL=. Дополнительный выходной файл .js.map не генерируется. Имейте в виду, что карты исходных кодов обычно очень большие, так как они содержат весь исходный код, поэтому обычно не следует передавать код, содержащий inline карты исходных кодов. Для удаления исходного кода из карты исходных кодов (оставляя только имена файлов и соответствия строк/колонок), используйте опцию содержимого исходных файлов. Используйте режим карты исходных кодов inline так:

esbuild app.ts --sourcemap=inline --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.ts'],
  sourcemap: 'inline',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Sourcemap:   api.SourceMapInline,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}
  1. both

    Этот режим сочетает режимы inline и external. Карта исходных кодов добавляется в строку в конец выходного файла .js, и ещё одна копия той же карты исходных кодов записывается в отдельный выходной файл .js.map в дополнение к выходному файлу .js. Используйте режим карты исходных кодов both так:

esbuild app.ts --sourcemap=both --outfile=out.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.ts'],
  sourcemap: 'both',
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.ts"},
    Sourcemap:   api.SourceMapInlineAndExternal,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

API сборки build поддерживает все четыре режима карт исходных кодов, перечисленные выше, но API преобразования transform не поддерживает режим linked. Это связано с тем, что возвращаемый API преобразования вывод не имеет связанного имени файла. Если вы хотите, чтобы вывод API преобразования содержал комментарий карты исходных кодов, вы можете добавить его самостоятельно. Кроме того, командная форма API преобразования поддерживает только режим inline, потому что вывод записывается в стандартный вывод, поэтому создание нескольких выходных файлов невозможно.

Если вы хотите «заглянуть под капот», чтобы увидеть, что делает карта исходных кодов (или отладить проблемы с вашей картой исходных кодов), вы можете загрузить соответствующий выходной файл и связанную карту исходных кодов сюда: Визуализация карты исходных кодов.

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

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

  • Chrome: ⚙ → Включить карты исходных кодов JavaScript
  • Safari: ⚙ → Источники → Включить карты исходных кодов
  • Firefox: ··· → Включить карты исходных кодов

В node карты исходных кодов поддерживаются по умолчанию, начиная с версии v12.12.0. Эта функция по умолчанию отключена, но может быть включена с флагом. В отличие от браузера, фактические стеки вызовов также изменяются в node, поэтому при проверке error.stack в вашем коде будет отображаться отображенный стек вызовов, содержащий исходный код. Вот как включить этот параметр в node (флаг --enable-source-maps должен идти перед именем файла скрипта):

node --enable-source-maps app.js

Содержимое исходных файлов

Поддерживается: Сборка и Преобразование

Карты исходных кодов генерируются с использованием версии 3 формата карты исходных кодов Source Map, которая является наиболее распространённой. Каждая карта исходных кодов будет выглядеть примерно так:

{
  "version": 3,
  "sources": ["bar.js", "foo.js"],
  "sourcesContent": ["bar()", "foo()\nimport './bar'"],
  "mappings": ";AAAA;;;ACAA;",
  "names": []
}

Поле sourcesContent — это необязательное поле, которое содержит весь исходный код. Это полезно для отладки, потому что это означает, что исходный код будет доступен в отладчике.

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

esbuild --bundle app.js --sourcemap --sources-content=false
import * as esbuild from 'esbuild'

await esbuild.build({
  bundle: true,
  entryPoints: ['app.js'],
  sourcemap: true,
  sourcesContent: false,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    Bundle:         true,
    EntryPoints:    []string{"app.js"},
    Sourcemap:      api.SourceMapInline,
    SourcesContent: api.SourcesContentExclude,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Метаданные сборки

Анализ

Поддерживается: Сборка

Если вам нужна интерактивная визуализация, попробуйте анализатор размера пакета esbuild вместо этого. Вы можете загрузить вашу метаданные сборки esbuild, чтобы увидеть разбивку по размеру пакета.

Использование функции анализа генерирует лёгкий для чтения отчёт о содержимом вашего пакета:

esbuild --bundle example.jsx --outfile=out.js --minify --analyze

  out.js                                                                    27.6kb  100.0%
   ├ node_modules/react-dom/cjs/react-dom-server.browser.production.min.js  19.2kb   69.8%
   ├ node_modules/react/cjs/react.production.min.js                          5.9kb   21.4%
   ├ node_modules/object-assign/index.js                                     962b     3.4%
   ├ example.jsx                                                             137b     0.5%
   ├ node_modules/react-dom/server.browser.js                                 50b     0.2%
   └ node_modules/react/index.js                                              50b     0.2%

...
import * as esbuild from 'esbuild'

let result = await esbuild.build({
  entryPoints: ['example.jsx'],
  outfile: 'out.js',
  minify: true,
  metafile: true,
})

console.log(await esbuild.analyzeMetafile(result.metafile))
package main

import "github.com/evanw/esbuild/pkg/api"
import "fmt"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:       []string{"example.jsx"},
    Outfile:           "out.js",
    MinifyWhitespace:  true,
    MinifyIdentifiers: true,
    MinifySyntax:      true,
    Metafile:          true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }

  fmt.Printf("%s", api.AnalyzeMetafile(result.Metafile, api.AnalyzeMetafileOptions{}))
}

Информация показывает, какие входные файлы попали в каждый выходной файл, а также какой процент выходного файла они заняли. Если вы хотите дополнительную информацию, вы можете включить режим «подробный». В настоящее время он показывает путь импорта от точки входа к каждому входному файлу, что подсказывает, почему данный входной файл включается в пакет:

esbuild --bundle example.jsx --outfile=out.js --minify --analyze=verbose

  out.js ─────────────────────────────────────────────────────────────────── 27.6kb ─ 100.0%
   ├ node_modules/react-dom/cjs/react-dom-server.browser.production.min.js ─ 19.2kb ── 69.8%
   │  └ node_modules/react-dom/server.browser.js
   │     └ example.jsx
   ├ node_modules/react/cjs/react.production.min.js ───────────────────────── 5.9kb ── 21.4%
   │  └ node_modules/react/index.js
   │     └ example.jsx
   ├ node_modules/object-assign/index.js ──────────────────────────────────── 962b ──── 3.4%
   │  └ node_modules/react-dom/cjs/react-dom-server.browser.production.min.js
   │     └ node_modules/react-dom/server.browser.js
   │        └ example.jsx
   ├ example.jsx ──────────────────────────────────────────────────────────── 137b ──── 0.5%
   ├ node_modules/react-dom/server.browser.js ──────────────────────────────── 50b ──── 0.2%
   │  └ example.jsx
   └ node_modules/react/index.js ───────────────────────────────────────────── 50b ──── 0.2%
      └ example.jsx

...
import * as esbuild from 'esbuild'

let result = await esbuild.build({
  entryPoints: ['example.jsx'],
  outfile: 'out.js',
  minify: true,
  metafile: true,
})

console.log(await esbuild.analyzeMetafile(result.metafile, {
  verbose: true,
}))
package main

import "github.com/evanw/esbuild/pkg/api"
import "fmt"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints:       []string{"example.jsx"},
    Outfile:           "out.js",
    MinifyWhitespace:  true,
    MinifyIdentifiers: true,
    MinifySyntax:      true,
    Metafile:          true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }

  fmt.Printf("%s", api.AnalyzeMetafile(result.Metafile, api.AnalyzeMetafileOptions{
    Verbose: true,
  }))
}

Этот анализ — просто визуализация информации, которая может быть найдена в метаданных. Если этот анализ не соответствует вашим потребностям, вы можете создать свою визуализацию, используя информацию в метаданных.

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

Метаданные

Поддерживается: Сборка

Этот параметр сообщает esbuild о создании метаданных сборки в формате JSON. В следующем примере метаданные помещаются в файл meta.json:

esbuild app.js --bundle --metafile=meta.json --outfile=out.js
import * as esbuild from 'esbuild'
import fs from 'node:fs'

let result = await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  metafile: true,
  outfile: 'out.js',
})

fs.writeFileSync('meta.json', JSON.stringify(result.metafile))
package main

import "io/ioutil"
import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    Bundle:      true,
    Metafile:    true,
    Outfile:     "out.js",
    Write:       true,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }

  ioutil.WriteFile("meta.json", []byte(result.Metafile), 0644)
}

Эти данные затем могут быть проанализированы другими инструментами. Для интерактивной визуализации вы можете использовать собственный анализатор размера бандлов esbuild — Bundle Size Analyzer. Для быстрого текстового анализа вы можете использовать встроенную функцию esbuild analyze. Или вы можете написать свой собственный анализ, использующий эту информацию.

Формат метаданных JSON выглядит следующим образом (описан с использованием интерфейса TypeScript):

interface Metafile {
  inputs: {
    [path: string]: {
      bytes: number
      imports: {
        path: string
        kind: string
        external?: boolean
        original?: string
        with?: Record<string, string>
      }[]
      format?: string
      with?: Record<string, string>
    }
  }
  outputs: {
    [path: string]: {
      bytes: number
      inputs: {
        [path: string]: {
          bytesInOutput: number
        }
      }
      imports: {
        path: string
        kind: string
        external?: boolean
      }[]
      exports: string[]
      entryPoint?: string
      cssBundle?: string
    }
  }
}

Ведение журнала

Цвет

Поддерживается: Build и Transform

Этот параметр включает или отключает цвета в сообщениях об ошибках и предупреждениях, которые esbuild выводит в файл stderr в терминале. По умолчанию цвет автоматически включается, если stderr — это сессия TTY, и автоматически отключается в противном случае. Вывод с цветами в esbuild выглядит так:

▲ [WARNING] The "typeof" operator will never evaluate to "null" [impossible-typeof]

    example.js:2:16:
      2 │ log(typeof x == "null")
        ╵                 ~~~~~~

  The expression "typeof x" actually evaluates to "object" in JavaScript, not "null". You need to
  use "x === null" to test for null.

✘ [ERROR] Could not resolve "logger"

    example.js:1:16:
      1 │ import log from "logger"
        ╵                 ~~~~~~~~

  You can mark the path "logger" as external to exclude it from the bundle, which will remove this
  error and leave the unresolved path in the bundle.

Вывод с цветами можно принудительно включить, установив цвет в true. Это полезно, если вы направляете вывод stderr esbuild в TTY сами:

echo 'typeof x == "null"' | esbuild --color=true 2> stderr.txt
import * as esbuild from 'esbuild'

let js = 'typeof x == "null"'
await esbuild.transform(js, {
  color: true,
})
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "typeof x == 'null'"

  result := api.Transform(js, api.TransformOptions{
    Color: api.ColorAlways,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

Вывод с цветами также можно установить в false, чтобы отключить цвета.

Форматирование сообщений

Поддерживается: Build и Transform

Этот API-вызов можно использовать для форматирования сообщений об ошибках и предупреждениях журнала, возвращаемых API build и API transform, как строку с использованием того же форматирования, что и сам esbuild. Это полезно, если вы хотите настроить работу журналирования esbuild, например, обработать сообщения журнала перед их отображением или направить их не в консоль. Вот пример:

import * as esbuild from 'esbuild'

let formatted = await esbuild.formatMessages([
  {
    text: 'This is an error',
    location: {
      file: 'app.js',
      line: 10,
      column: 4,
      length: 3,
      lineText: 'let foo = bar',
    },
  },
], {
  kind: 'error',
  color: false,
  terminalWidth: 100,
})

console.log(formatted.join('\n'))
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"
import "strings"

func main() {
  formatted := api.FormatMessages([]api.Message{
    {
      Text: "This is an error",
      Location: &api.Location{
        File:     "app.js",
        Line:     10,
        Column:   4,
        Length:   3,
        LineText: "let foo = bar",
      },
    },
  }, api.FormatMessagesOptions{
    Kind:          api.ErrorMessage,
    Color:         false,
    TerminalWidth: 100,
  })

  fmt.Printf("%s", strings.Join(formatted, "\n"))
}

Параметры

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

interface FormatMessagesOptions {
  kind: 'error' | 'warning';
  color?: boolean;
  terminalWidth?: number;
}
type FormatMessagesOptions struct {
  Kind          MessageKind
  Color         bool
  TerminalWidth int
}
  • kind

    Определяет, выводятся ли эти сообщения журнала как ошибки или предупреждения.

  • color

    Если это true, для цветного вывода включаются управляющие коды эскейпа терминала в стиле Unix.

  • terminalWidth

    Укажите положительное значение для перевода длинных строк, чтобы они не выходили за пределы заданной ширины колонки. Укажите 0, чтобы отключить перенос слов.

Уровень журнала

Поддерживается: Build и Transform

Уровень журнала можно изменить, чтобы предотвратить вывод сообщений об ошибках и/или предупреждениях esbuild в терминал. Шесть уровней журнала:

  • silent
    Не отображать никакой вывод журнала. Это уровень журнала по умолчанию при использовании JS API transform.

  • error
    Отображать только ошибки.

  • warning
    Отображать только предупреждения и ошибки. Это уровень журнала по умолчанию при использовании JS API build.

  • info
    Отображать предупреждения, ошибки и сводку файла вывода. Это уровень журнала по умолчанию при использовании командной строки.

  • debug
    Регистрировать всё с info и некоторые дополнительные сообщения, которые могут помочь вам отладить сбойный бандл. Этот уровень журнала имеет влияние на производительность, и некоторые сообщения могут быть ложными срабатываниями, поэтому эта информация не отображается по умолчанию.

  • verbose
    Это генерирует множество сообщений журнала и было добавлено для отладки проблем с драйверами файловой системы. Оно не предназначено для общего использования.

Уровень журнала можно установить следующим образом:

echo 'typeof x == "null"' | esbuild --log-level=error
import * as esbuild from 'esbuild'

let js = 'typeof x == "null"'
await esbuild.transform(js, {
  logLevel: 'error',
})
package main

import "fmt"
import "github.com/evanw/esbuild/pkg/api"

func main() {
  js := "typeof x == 'null'"

  result := api.Transform(js, api.TransformOptions{
    LogLevel: api.LogLevelError,
  })

  if len(result.Errors) == 0 {
    fmt.Printf("%s", result.Code)
  }
}

Предел журнала

Поддерживается: Build и Transform

По умолчанию esbuild прекращает отчёт сообщений журнала после того, как было сообщено 10 сообщений. Это предотвращает случайное создание большого количества сообщений журнала, которые могут легко заблокировать медленные эмуляторы терминала, такие как командная строка Windows. Это также предотвращает случайное заполнение всего буфера прокрутки для эмуляторов терминала с ограниченным буфером прокрутки.

Предел журнала можно изменить на другое значение и полностью отключить, установив его в ноль. Это отобразит все сообщения журнала:

esbuild app.js --log-limit=0
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  logLimit: 0,
  outfile: 'out.js',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    LogLimit:    0,
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Переопределение журнала

Поддерживается: Build и Transform

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

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

esbuild app.js --log-override:unsupported-regexp=warning --target=chrome50
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  logOverride: {
    'unsupported-regexp': 'warning',
  },
  target: 'chrome50',
})
package main

import "github.com/evanw/esbuild/pkg/api"
import "os"

func main() {
  result := api.Build(api.BuildOptions{
    EntryPoints: []string{"app.js"},
    LogOverride: map[string]api.LogLevel{
      "unsupported-regexp": api.LogLevelWarning,
    },
    Engines: []api.Engine{
      {Name: api.EngineChrome, Version: "50"},
    },
  })

  if len(result.Errors) > 0 {
    os.Exit(1)
  }
}

Уровень журнала для каждого типа сообщений может быть переопределён на любое значение, поддерживаемое настройкой уровня журнала. Все доступные типы сообщений перечислены ниже (нажмите на каждый, чтобы увидеть пример сообщения журнала):

  • JS:
    • assert-to-with
      ▲ [WARNING] The "assert" keyword is not supported in the configured target environment [assert-to-with]
      
          example.js:1:31:
            1 │ import data from "./data.json" assert { type: "json" }
              │                                ~~~~~~
              ╵                                with
      
        Did you mean to use "with" instead of "assert"?
    • assert-type-json
      ▲ [WARNING] Non-default import "value" is undefined with a JSON import assertion [assert-type-json]
      
          example.js:1:78:
            1 │ import * as data from "./data.json" assert { type: "json" }; console.log(data.value)
              ╵                                                                               ~~~~~
      
        The JSON import assertion is here:
      
          example.js:1:45:
            1 │ import * as data from "./data.json" assert { type: "json" }; console.log(data.value)
              ╵                                              ~~~~~~~~~~~~
      
        You can either keep the import assertion and only use the "default" import, or you can remove the
        import assertion and use the "value" import.
    • assign-to-constant
      ▲ [WARNING] This assignment will throw because "foo" is a constant [assign-to-constant]
      
          example.js:1:15:
            1 │ const foo = 1; foo = 2
              ╵                ~~~
      
        The symbol "foo" was declared a constant here:
      
          example.js:1:6:
            1 │ const foo = 1; foo = 2
              ╵       ~~~
    • assign-to-define
      ▲ [WARNING] Suspicious assignment to defined constant "DEFINE" [assign-to-define]
      
          example.js:1:0:
            1 │ DEFINE = false
              ╵ ~~~~~~
      
        The expression "DEFINE" has been configured to be replaced with a constant using the "define"
        feature. If this expression is supposed to be a compile-time constant, then it doesn't make sense
        to assign to it here. Or if this expression is supposed to change at run-time, this "define"
        substitution should be removed.
    • assign-to-import
      ▲ [WARNING] This assignment will throw because "foo" is an import [assign-to-import]
      
          example.js:1:23:
            1 │ import foo from "foo"; foo = null
              ╵                        ~~~
      
        Imports are immutable in JavaScript. To modify the value of this import, you must export a setter
        function in the imported file (e.g. "setFoo") and then import and call that function here instead.
    • call-import-namespace
      ▲ [WARNING] Calling "foo" will crash at run-time because it's an import namespace object, not a function [call-import-namespace]
      
          example.js:1:28:
            1 │ import * as foo from "foo"; foo()
              ╵                             ~~~
      
        Consider changing "foo" to a default import instead:
      
          example.js:1:7:
            1 │ import * as foo from "foo"; foo()
              │        ~~~~~~~~
              ╵        foo
    • class-name-will-throw
      ▲ [WARNING] Accessing class "Foo" before initialization will throw [class-name-will-throw]
      
          example.js:1:40:
            1 │ class Foo { static key = "foo"; static [Foo.key] = 123 }
              ╵                                         ~~~
    • commonjs-variable-in-esm
      ▲ [WARNING] The CommonJS "exports" variable is treated as a global variable in an ECMAScript module and may not work as expected [commonjs-variable-in-esm]
      
          example.js:1:0:
            1 │ exports.foo = 1; export let bar = 2
              ╵ ~~~~~~~
      
        This file is considered to be an ECMAScript module because of the "export" keyword here:
      
          example.js:1:17:
            1 │ exports.foo = 1; export let bar = 2
              ╵                  ~~~~~~
    • delete-super-property
      ▲ [WARNING] Attempting to delete a property of "super" will throw a ReferenceError [delete-super-property]
      
          example.js:1:42:
            1 │ class Foo extends Object { foo() { delete super.foo } }
              ╵                                           ~~~~~
    • direct-eval
      ▲ [WARNING] Using direct eval with a bundler is not recommended and may cause problems [direct-eval]
      
          example.js:1:22:
            1 │ let apparentlyUnused; eval("actuallyUse(apparentlyUnused)")
              ╵                       ~~~~
      
        You can read more about direct eval and bundling here: https://esbuild.github.io/link/direct-eval
    • duplicate-case
      ▲ [WARNING] This case clause will never be evaluated because it duplicates an earlier case clause [duplicate-case]
      
          example.js:1:33:
            1 │ switch (foo) { case 1: return 1; case 1: return 2 }
              ╵                                  ~~~~
      
        The earlier case clause is here:
      
          example.js:1:15:
            1 │ switch (foo) { case 1: return 1; case 1: return 2 }
              ╵                ~~~~
    • duplicate-class-member
      ▲ [WARNING] Duplicate member "x" in class body [duplicate-class-member]
      
          example.js:1:19:
            1 │ class Foo { x = 1; x = 2 }
              ╵                    ^
      
        The original member "x" is here:
      
          example.js:1:12:
            1 │ class Foo { x = 1; x = 2 }
              ╵             ^
    • duplicate-object-key
      ▲ [WARNING] Duplicate key "bar" in object literal [duplicate-object-key]
      
          example.js:1:16:
            1 │ foo = { bar: 1, bar: 2 }
              ╵                 ~~~
      
        The original key "bar" is here:
      
          example.js:1:8:
            1 │ foo = { bar: 1, bar: 2 }
              ╵         ~~~
    • empty-import-meta
      ▲ [WARNING] "import.meta" is not available in the configured target environment ("chrome50") and will be empty [empty-import-meta]
      
          example.js:1:6:
            1 │ foo = import.meta
              ╵       ~~~~~~~~~~~
    • equals-nan
      ▲ [WARNING] Comparison with NaN using the "!==" operator here is always true [equals-nan]
      
          example.js:1:24:
            1 │ foo = foo.filter(x => x !== NaN)
              ╵                         ~~~
      
        Floating-point equality is defined such that NaN is never equal to anything, so "x === NaN" always
        returns false. You need to use "Number.isNaN(x)" instead to test for NaN.
    • equals-negative-zero
      ▲ [WARNING] Comparison with -0 using the "!==" operator will also match 0 [equals-negative-zero]
      
          example.js:1:28:
            1 │ foo = foo.filter(x => x !== -0)
              ╵                             ~~
      
        Floating-point equality is defined such that 0 and -0 are equal, so "x === -0" returns true for
        both 0 and -0. You need to use "Object.is(x, -0)" instead to test for -0.
    • equals-new-object
      ▲ [WARNING] Comparison using the "!==" operator here is always true [equals-new-object]
      
          example.js:1:24:
            1 │ foo = foo.filter(x => x !== [])
              ╵                         ~~~
      
        Equality with a new object is always false in JavaScript because the equality operator tests
        object identity. You need to write code to compare the contents of the object instead. For
        example, use "Array.isArray(x) && x.length === 0" instead of "x === []" to test for an empty
        array.
    • html-comment-in-js
      ▲ [WARNING] Treating "<!--" as the start of a legacy HTML single-line comment [html-comment-in-js]
      
          example.js:1:0:
            1 │ <!-- comment -->
              ╵ ~~~~
    • impossible-typeof
      ▲ [WARNING] The "typeof" operator will never evaluate to "null" [impossible-typeof]
      
          example.js:1:32:
            1 │ foo = foo.map(x => typeof x !== "null")
              ╵                                 ~~~~~~
      
        The expression "typeof x" actually evaluates to "object" in JavaScript, not "null". You need to
        use "x === null" to test for null.
    • indirect-require
      ▲ [WARNING] Indirect calls to "require" will not be bundled [indirect-require]
      
          example.js:1:8:
            1 │ let r = require, fs = r("fs")
              ╵         ~~~~~~~
    • private-name-will-throw
      ▲ [WARNING] Writing to getter-only property "#foo" will throw [private-name-will-throw]
      
          example.js:1:39:
            1 │ class Foo { get #foo() {} bar() { this.#foo++ } }
              ╵                                        ~~~~
    • semicolon-after-return
      ▲ [WARNING] The following expression is not returned because of an automatically-inserted semicolon [semicolon-after-return]
      
          example.js:1:6:
            1 │ return
              ╵       ^
    • suspicious-boolean-not
      ▲ [WARNING] Suspicious use of the "!" operator inside the "in" operator [suspicious-boolean-not]
      
          example.js:1:4:
            1 │ if (!foo in bar) {
              │     ~~~~
              ╵     (!foo)
      
        The code "!x in y" is parsed as "(!x) in y". You need to insert parentheses to get "!(x in y)"
        instead.
    • suspicious-define
      ▲ [WARNING] "process.env.NODE_ENV" is defined as an identifier instead of a string (surround "production" with quotes to get a string) [suspicious-define]
      
          <js>:1:34:
            1 │ define: { 'process.env.NODE_ENV': 'production' }
              │                                   ~~~~~~~~~~~~
              ╵                                   '"production"'
    • suspicious-logical-operator
      ▲ [WARNING] The "&&" operator here will always return the left operand [suspicious-logical-operator]
      
          example.js:1:25:
            1 │ const isInRange = x => 0 && x <= 1
              ╵                          ~~
      
        The "=>" symbol creates an arrow function expression in JavaScript. Did you mean to use the
        greater-than-or-equal-to operator ">=" here instead?
      
          example.js:1:20:
            1 │ const isInRange = x => 0 && x <= 1
              │                     ~~
              ╵                     >=
    • suspicious-nullish-coalescing
      ▲ [WARNING] The "??" operator here will always return the left operand [suspicious-nullish-coalescing]
      
          example.js:1:26:
            1 │ return name === user.name ?? ""
              ╵                           ~~
      
        The left operand of the "??" operator here will never be null or undefined, so it will always be
        returned. This usually indicates a bug in your code:
      
          example.js:1:7:
            1 │ return name === user.name ?? ""
              ╵        ~~~~~~~~~~~~~~~~~~
    • this-is-undefined-in-esm
      ▲ [WARNING] Top-level "this" will be replaced with undefined since this file is an ECMAScript module [this-is-undefined-in-esm]
      
          example.js:1:0:
            1 │ this.foo = 1; export let bar = 2
              │ ~~~~
              ╵ undefined
      
        This file is considered to be an ECMAScript module because of the "export" keyword here:
      
          example.js:1:14:
            1 │ this.foo = 1; export let bar = 2
              ╵               ~~~~~~
    • unsupported-dynamic-import
      ▲ [WARNING] This "import" expression will not be bundled because the argument is not a string literal [unsupported-dynamic-import]
      
          example.js:1:0:
            1 │ import(foo)
              ╵ ~~~~~~
    • unsupported-jsx-comment
      ▲ [WARNING] Invalid JSX factory: 123 [unsupported-jsx-comment]
      
          example.jsx:1:8:
            1 │ // @jsx 123
              ╵         ~~~
    • unsupported-regexp
      ▲ [WARNING] The regular expression flag "d" is not available in the configured target environment ("chrome50") [unsupported-regexp]
      
          example.js:1:3:
            1 │ /./d
              ╵    ^
      
        This regular expression literal has been converted to a "new RegExp()" constructor to avoid
        generating code with a syntax error. However, you will need to include a polyfill for "RegExp" for
        your code to have the correct behavior at run-time.
    • unsupported-require-call
      ▲ [WARNING] This call to "require" will not be bundled because the argument is not a string literal [unsupported-require-call]
      
          example.js:1:0:
            1 │ require(foo)
              ╵ ~~~~~~~

  • CSS:
    • css-syntax-error
      ▲ [WARNING] Expected identifier but found "]" [css-syntax-error]
      
          example.css:1:4:
            1 │ div[] {
              ╵     ^
    • invalid-@charset
      ▲ [WARNING] "@charset" must be the first rule in the file [invalid-@charset]
      
          example.css:1:19:
            1 │ div { color: red } @charset "UTF-8";
              ╵                    ~~~~~~~~
      
        This rule cannot come before a "@charset" rule
      
          example.css:1:0:
            1 │ div { color: red } @charset "UTF-8";
              ╵ ^
    • invalid-@import
      ▲ [WARNING] All "@import" rules must come first [invalid-@import]
      
          example.css:1:19:
            1 │ div { color: red } @import "foo.css";
              ╵                    ~~~~~~~
      
        This rule cannot come before an "@import" rule
      
          example.css:1:0:
            1 │ div { color: red } @import "foo.css";
              ╵ ^
    • invalid-@layer
      ▲ [WARNING] "initial" cannot be used as a layer name [invalid-@layer]
      
          example.css:1:7:
            1 │ @layer initial {
              ╵        ~~~~~~~
    • invalid-calc
      ▲ [WARNING] "-" can only be used as an infix operator, not a prefix operator [invalid-calc]
      
          example.css:1:20:
            1 │ div { z-index: calc(-(1+2)); }
              ╵                     ^
      
      ▲ [WARNING] The "+" operator only works if there is whitespace on both sides [invalid-calc]
      
          example.css:1:23:
            1 │ div { z-index: calc(-(1+2)); }
              ╵                        ^
    • js-comment-in-css
      ▲ [WARNING] Comments in CSS use "/* ... */" instead of "//" [js-comment-in-css]
      
          example.css:1:0:
            1 │ // comment
              ╵ ~~
    • undefined-composes-from
      ▲ [WARNING] The value of "zoom" in the "foo" class is undefined [undefined-composes-from]
      
          example.module.css:1:1:
            1 │ .foo { composes: bar from "lib.module.css"; zoom: 1; }
              ╵  ~~~
      
        The first definition of "zoom" is here:
      
          lib.module.css:1:7:
            1 │ .bar { zoom: 2 }
              ╵        ~~~~
      
        The second definition of "zoom" is here:
      
          example.module.css:1:44:
            1 │ .foo { composes: bar from "lib.module.css"; zoom: 1; }
              ╵                                             ~~~~
      
        The specification of "composes" does not define an order when class declarations from separate
        files are composed together. The value of the "zoom" property for "foo" may change unpredictably
        as the code is edited. Make sure that all definitions of "zoom" for "foo" are in a single file.
    • unsupported-@charset
      ▲ [WARNING] "UTF-8" will be used instead of unsupported charset "ASCII" [unsupported-@charset]
      
          example.css:1:9:
            1 │ @charset "ASCII";
              ╵          ~~~~~~~
    • unsupported-@namespace
      ▲ [WARNING] "@namespace" rules are not supported [unsupported-@namespace]
      
          example.css:1:0:
            1 │ @namespace "ns";
              ╵ ~~~~~~~~~~
    • unsupported-css-property
      ▲ [WARNING] "widht" is not a known CSS property [unsupported-css-property]
      
          example.css:1:6:
            1 │ div { widht: 1px }
              │       ~~~~~
              ╵       width
      
        Did you mean "width" instead?
    • unsupported-css-nesting
      ▲ [WARNING] Transforming this CSS nesting syntax is not supported in the configured target environment ("chrome50") [unsupported-css-nesting]
      
          example.css:2:5:
            2 │ .foo & {
              ╵      ^
      
        The nesting transform for this case must generate an ":is(...)" but the configured target
        environment does not support the ":is" pseudo-class.

  • Bundler:
    • ambiguous-reexport
      ▲ [WARNING] Re-export of "foo" in "example.js" is ambiguous and has been removed [ambiguous-reexport]
      
        One definition of "foo" comes from "a.js" here:
      
          a.js:1:11:
            1 │ export let foo = 1
              ╵            ~~~
      
        Another definition of "foo" comes from "b.js" here:
      
          b.js:1:11:
            1 │ export let foo = 2
              ╵            ~~~
    • different-path-case
      ▲ [WARNING] Use "foo.js" instead of "Foo.js" to avoid issues with case-sensitive file systems [different-path-case]
      
          example.js:2:7:
            2 │ import "./Foo.js"
              ╵        ~~~~~~~~~~
    • empty-glob
      ▲ [WARNING] The glob pattern import("./icon-*.json") did not match any files [empty-glob]
      
          example.js:2:16:
            2 │   return import("./icon-" + name + ".json")
              ╵                 ~~~~~~~~~~~~~~~~~~~~~~~~~~
    • ignored-bare-import
      ▲ [WARNING] Ignoring this import because "node_modules/foo/index.js" was marked as having no side effects [ignored-bare-import]
      
          example.js:1:7:
            1 │ import "foo"
              ╵        ~~~~~
      
        "sideEffects" is false in the enclosing "package.json" file:
      
          node_modules/foo/package.json:2:2:
            2 │   "sideEffects": false
              ╵   ~~~~~~~~~~~~~
    • ignored-dynamic-import
      ▲ [WARNING] Importing "foo" was allowed even though it could not be resolved because dynamic import failures appear to be handled here: [ignored-dynamic-import]
      
          example.js:1:7:
            1 │ import("foo").catch(e => {
              ╵        ~~~~~
      
        The handler for dynamic import failures is here:
      
          example.js:1:14:
            1 │ import("foo").catch(e => {
              ╵               ~~~~~
    • import-is-undefined
      ▲ [WARNING] Import "foo" will always be undefined because the file "foo.js" has no exports [import-is-undefined]
      
          example.js:1:9:
            1 │ import { foo } from "./foo"
              ╵          ~~~
    • require-resolve-not-external
      ▲ [WARNING] "foo" should be marked as external for use with "require.resolve" [require-resolve-not-external]
      
          example.js:1:26:
            1 │ let foo = require.resolve("foo")
              ╵                           ~~~~~

  • Source maps:
    • invalid-source-mappings
      ▲ [WARNING] Bad "mappings" data in source map at character 3: Invalid original column value: -2 [invalid-source-mappings]
      
          example.js.map:2:18:
            2 │   "mappings": "aAAFA,UAAU;;"
              ╵                   ^
      
        The source map "example.js.map" was referenced by the file "example.js" here:
      
          example.js:1:21:
            1 │ //# sourceMappingURL=example.js.map
              ╵                      ~~~~~~~~~~~~~~
    • sections-in-source-map
      ▲ [WARNING] Source maps with "sections" are not supported [sections-in-source-map]
      
          example.js.map:2:2:
            2 │   "sections": []
              ╵   ~~~~~~~~~~
      
        The source map "example.js.map" was referenced by the file "example.js" here:
      
          example.js:1:21:
            1 │ //# sourceMappingURL=example.js.map
              ╵                      ~~~~~~~~~~~~~~
    • missing-source-map
      ▲ [WARNING] Cannot read file ".": is a directory [missing-source-map]
      
          example.js:1:21:
            1 │ //# sourceMappingURL=.
              ╵                      ^
    • unsupported-source-map-comment
      ▲ [WARNING] Unsupported source map comment: could not decode percent-escaped data: invalid URL escape "%\"" [unsupported-source-map-comment]
      
          example.js:1:21:
            1 │ //# sourceMappingURL=data:application/json,"%"
              ╵                      ~~~~~~~~~~~~~~~~~~~~~~~~~

  • Resolver:
    • package.json
      ▲ [WARNING] "esm" is not a valid value for the "type" field [package.json]
      
          package.json:1:10:
            1 │ { "type": "esm" }
              ╵           ~~~~~
      
        The "type" field must be set to either "commonjs" or "module".
    • tsconfig.json
      ▲ [WARNING] Unrecognized target environment "ES4" [tsconfig.json]
      
          tsconfig.json:1:33:
            1 │ { "compilerOptions": { "target": "ES4" } }
              ╵                                  ~~~~~

Эти типы сообщений должны быть достаточно стабильными, но в будущем могут быть добавлены новые и удалены старые. Если тип сообщения будет удален, все переопределения для этого типа сообщения будут просто проигнорированы.

© 2020 Evan Wallace
Licensed under the MIT License.
https://esbuild.github.io/api/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API