API

API は、コマンドライン、JavaScript、Go の 3 つの言語のいずれかでアクセスできます。コンセプトとパラメータは 3 つの言語間でほぼ同じなので、言語ごとに個別のドキュメントを用意する代わりに、ここでまとめて説明します。コード例の右上隅にある CLIJSGo のタブを使用して言語を切り替えることができます。各言語の具体的な内容

概要

最も一般的に使用される esbuild API は、buildtransform の 2 つです。それぞれについて、以下に概要を説明し、次に個々の API オプションのドキュメントを示します。

ビルド

これは esbuild の主要なインターフェースです。通常、処理する 1 つ以上のエントリーポイントファイルをさまざまなオプションとともに渡すと、esbuild は結果をファイルシステムに書き戻します。以下は、バンドル出力ディレクトリで有効にする簡単な例です。

CLI JS Go
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 の高度な使用法には、長時間実行されるビルドコンテキストの設定が含まれます。このコンテキストは、JS および Go では明示的なオブジェクトですが、CLI では暗黙的です。特定のコンテキストで行われたすべてのビルドは、同じビルドオプションを共有し、後続のビルドはインクリメンタルに行われます (つまり、以前のビルドからのいくつかの作業を再利用してパフォーマンスを向上させます)。これは開発に役立ちます。esbuild は、作業中にバックグラウンドでアプリを再構築できるためです。

3 つの異なるインクリメンタルビルド API があります。

CLI JS Go
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{})
CLI JS Go
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{})
CLI JS Go
# 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()
}

これら 3 つのインクリメンタルビルド API は組み合わせることができます。ライブリロード (ファイルを編集して保存したときに自動的にページをリロードする) を有効にするには、同じコンテキストで 監視サーバーを一緒に有効にする必要があります。

コンテキストオブジェクトが完了したら、コンテキストで dispose() を呼び出して、既存のビルドが完了するのを待ち、監視モードおよび/またはサーバーモードを停止し、リソースを解放できます。

ビルド API とコンテキスト API は、両方とも次のオプションを使用します。

変換

これは、ビルドの限定的な特殊ケースであり、他のファイルから完全に切り離された分離された環境にあるインメモリファイルを表現するコード文字列を変換します。一般的な用途には、コードの最小化と TypeScript から JavaScript への変換が含まれます。以下に例を示します。

CLI JS Go
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 ファイルの影響を受けない) と特定の欠点 (たとえば、バンドルまたはプラグインで使用できない) があります。ユースケースが transform API に適合しない場合は、より一般的な ビルド API を使用する必要があります。

transform API は、次のオプションを使用します。

JS固有の詳細

esbuild の JS API には、非同期と同期の 2 つのフレーバーがあります。非同期 API は、すべての環境で動作し、より高速で強力なため、推奨されます。同期 API は node でのみ動作し、特定の操作しか実行できませんが、node 固有の特定の状況では必要な場合があります。詳細

非同期 API

非同期 API 呼び出しは、Promise を使用して結果を返します。import キーワードとトップレベルの await キーワードを使用するため、node で .mjs ファイル拡張子を使用する必要がある可能性が高いことに注意してください。

import * as esbuild from 'esbuild'

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

長所

短所

同期 API

同期 API 呼び出しは、結果をインラインで返します

let esbuild = require('esbuild')

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

長所

短所

ブラウザ内

esbuild API は、Web Worker で WebAssembly を使用してブラウザでも実行できます。これを利用するには、esbuild パッケージの代わりに esbuild-wasm パッケージをインストールする必要があります。

npm install esbuild-wasm

ブラウザ用の API は、最初に initialize() を呼び出す必要があることと、WebAssembly バイナリの URL を渡す必要があることを除いて、node 用の API と似ています。また、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 を渡すことができます。これにより、initialize を呼び出すスレッドと同じスレッドで WebAssembly モジュールが作成されます。

また、<script> タグで lib/browser.min.js ファイルを読み込むことで、バンドラーを使用せずに HTML ファイル内のスクリプトタグとして esbuild の API を使用することもできます。この場合、API は API オブジェクトを保持する esbuild というグローバルを作成します

<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 は入力ファイルをバンドルしません。バンドルは次のように明示的に有効にする必要があります

CLI JS Go
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 でその 1 つのファイルのみをバンドルします。

分析できないインポート

インポートパスは、現在、文字列リテラルまたはglob パターンの場合のみバンドルされます。その他の形式のインポートパスはバンドルされず、代わりに生成された出力で verbatim で保持されます。これは、バンドルがコンパイル時の操作であり、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 ではなく別のバンドラーを使用する必要があるでしょう。

グロブスタイルのインポート

実行時に評価されるインポートパスは、特定の制限された状況下でバンドルできるようになりました。インポートパスの式は、文字列連結の形式である必要があり、./ または ../ のいずれかで始まる必要があります。文字列連結チェーン内の文字列以外の各式は、グロブパターンにおけるワイルドカードになります。いくつかの例を挙げます。

// 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 はパターンに一致する可能性のあるすべてのファイルを検索するために、大量のファイルシステム I/O を実行する可能性があることに注意してください。これは仕様であり、バグではありません。これが懸念される場合は、esbuild が実行するファイルシステム I/O の量を減らす方法が2つあります。

  1. 最も簡単な方法は、特定の実行時インポート式でインポートしたいすべてのファイルをサブディレクトリに入れ、そのサブディレクトリをパターンに含めることです。これにより、esbuild はそのサブディレクトリ内のみを検索するように制限されます。esbuild はパターンマッチング中に .. パス要素を考慮しないためです。

  2. もう1つの方法は、esbuild がどのサブディレクトリも検索しないようにすることです。esbuild が使用するパターンマッチングアルゴリズムでは、ワイルドカードがパターン内で前に / を持つ場合にのみ、/ パス区切り記号を含むものと一致できます。たとえば、'./data/' + x + '.json' は、x を任意のサブディレクトリ内の任意のものと一致させますが、'./data-' + x + '.json' は、x をトップレベルディレクトリ(サブディレクトリ内ではない)内の任意のものとのみ一致させます。

キャンセル

サポート対象: ビルド

リビルドを使用してインクリメンタルビルドを手動で呼び出す場合、このキャンセル API を使用して現在のビルドを早期に終了し、新しいビルドを開始できるようにすることができます。次のように行うことができます。

CLI JS Go
# 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)
    }()
  }
}

新しいビルドを開始する前に、キャンセル操作が完了するまで待機してください(つまり、JavaScript を使用する場合は返された promise を await してください)。そうしないと、次の リビルドは、まだ終了していないキャンセルされたビルドを返します。プラグインのon-end コールバックは、ビルドがキャンセルされたかどうかにかかわらず実行されることに注意してください。

ライブリロード

サポート対象: ビルド

ライブリロードは、コードエディターと同時にブラウザーを開いて表示する開発手法です。ソースコードを編集して保存すると、ブラウザーが自動的にリロードされ、リロードされたアプリのバージョンには変更が含まれます。つまり、変更ごとに手動でブラウザーに切り替えてリロードし、コードエディターに戻る必要がないため、より迅速に反復処理を行うことができます。これは、たとえば CSS を変更する場合に非常に役立ちます。

ライブリロード用の esbuild API はありません。代わりに、ウォッチモード(ファイルを編集して保存したときに自動的にビルドを開始する)とサービスモード(最新のビルドを提供するが、完了するまでブロックする)を組み合わせ、開発中にのみアプリに追加する小さなクライアント側の JavaScript コードを使用して、ライブリロードを構築できます。

最初の手順は、ウォッチサービスを一緒に有効にすることです。

CLI JS Go
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)
  }
}

2番目のステップは、/esbuild サーバー送信イベントソースをサブスクライブするコードをJavaScriptに追加することです。changeイベントを受信すると、ページをリロードしてアプリの最新バージョンを取得できます。これは、1行のコードで実行できます。

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

それだけです! ブラウザでアプリをロードすると、ファイル編集して保存すると、ページが自動的にリロードされるはずです(ビルドエラーがない場合)。

これは開発中にのみ含める必要があり、本番環境には含めないでください。本番環境でこのコードを削除する方法の1つは、if (!window.IS_PRODUCTION) のようなifステートメントで保護し、defineを使用して、本番環境で window.IS_PRODUCTIONtrue に設定することです。

ライブリロードの注意点

このようにライブリロードを実装するには、いくつかの既知の注意点があります。

CSSのホットリロード

change イベントには、より高度なユースケースを可能にする追加情報も含まれています。現在、前のビルドから変更されたファイルのパスを含む addedremoved、および 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はステートフルであるため、CSSのようにJavaScriptのホットリロードを透過的に実装することはできません。

他の開発サーバーでは、いずれにしても JavaScript のホットリロードを実装していますが、追加の API が必要になり、フレームワーク固有のハックが必要になる場合があり、編集セッション中に一時的な状態関連のバグが発生する場合があります。これを行うことは、esbuild の範囲外です。JavaScript のホットリロードが要件の1つである場合は、esbuild の代わりに他のツールを使用してもかまいません。

ただし、esbuildのライブリロードを使用すると、sessionStorageにアプリの現在のJavaScriptの状態を永続化して、ページのリロード後にアプリのJavaScriptの状態をより簡単に復元できます。アプリの読み込みが速い場合(ユーザーのためにも高速である必要があります)、JavaScriptを使用したライブリロードは、JavaScriptを使用したホットリロードとほぼ同じくらい高速になる可能性があります。

プラットフォーム

サポート対象:ビルド変換

デフォルトでは、esbuild のバンドラーは、ブラウザで実行するように設計されたコードを生成するように構成されています。バンドルされたコードが代わりに node で実行されるようにする場合は、プラットフォームを node に設定する必要があります。

CLI JS Go
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 (デフォルト値)に設定されている場合

プラットフォームが node に設定されている場合

プラットフォームがneutralに設定されている場合

ブラウザ用のバンドルNode.js用のバンドルも参照してください。

再構築

サポート対象: ビルド

同じオプションでesbuildのbuild APIを繰り返し呼び出すユースケースがある場合は、このAPIを使用できます。たとえば、独自のファイル監視サービスを実装している場合に役立ちます。再構築は、前のビルドからのデータの一部がキャッシュされ、前のビルドから元のファイルが変更されていない場合は再利用できるため、再度ビルドするよりも効率的です。再構築APIで使用されるキャッシュには、現在2つの形式があります。

再構築の方法は次のとおりです。

CLI JS Go
# 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()
}

Serve

サポート対象: ビルド

編集時にアプリが自動的にリロードされるようにするには、ライブリロードについて読む必要があります。これは、serveモードとwatchモードを組み合わせて、ファイルシステムへの変更をリッスンします。

Serveモードは、デバイス上のブラウザにコードを提供するWebサーバーを起動します。src/app.tswww/js/app.jsにバンドルし、wwwディレクトリをhttp://localhost:8000/で提供する例を次に示します。

CLI JS Go
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を作成すると、http://localhost:8000/にアクセスすると、src/app.tsに含まれるコードがロードされます。

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

esbuildの組み込みWebサーバーを別のWebサーバーの代わりに使用する利点の1つは、リロードするたびに、esbuildが提供するファイルが常に最新であることです。これは、他の開発環境では必ずしも当てはまりません。一般的な設定の1つは、入力ファイルが変更されたときにいつでも出力ファイルを再構築するローカルファイルウォッチャーを実行し、それらの出力ファイルを提供するためにローカルファイルサーバーを個別に実行することです。しかし、これは、編集後のリロードで、再構築がまだ完了していない場合は古い出力ファイルがリロードされる可能性があることを意味します。esbuildのWebサーバーでは、着信リクエストごとに、まだ進行中でない場合は再構築が開始され、現在の再構築が完了するまで待機してからファイルが提供されます。これは、esbuildが古いビルド結果を提供しないことを意味します。

このWebサーバーは、開発でのみ使用することを目的としていることに注意してください。本番環境では使用しないでください。

引数

serve APIへの引数は次のとおりです。

CLI JS Go
# 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
}

戻り値

CLI JS Go
# The CLI will print the host and port like this:

 > Local: http://127.0.0.1:8000/
interface ServeResult {
  host: string
  port: number
}
type ServeResult struct {
  Host string
  Port uint16
}

HTTPSの有効化

デフォルトでは、esbuildのWebサーバーはhttp://プロトコルを使用します。ただし、特定の最新のWeb機能はHTTP Webサイトでは利用できません。これらの機能を使用する場合は、代わりにhttps://プロトコルを使用するようにesbuildに指示する必要があります。

esbuildでHTTPSを有効にするには

  1. 自己署名証明書を生成します。これを行うには多くの方法があります。opensslコマンドがインストールされていると仮定して、その方法の1つを次に示します。

     openssl req -x509 -newkey rsa:4096 -keyout your.key -out your.cert -days 9999 -nodes -subj /CN=127.0.0.1 
  2. keyfilecertfileserve引数を使用して、your.keyyour.certをesbuildに渡します。

  3. ページをロードするときにブラウザの怖い警告をクリックして進みます(自己署名証明書は安全ではありませんが、ローカル開発を行っているだけなので問題ありません)。

これよりも複雑なニーズがある場合は、esbuildの前にプロキシを配置して、代わりにHTTPSに使用できます。ページをロードしたときにClient sent an HTTP request to an HTTPS serverというメッセージが表示された場合は、誤ったプロトコルを使用しています。ブラウザのアドレスバーでhttp://https://に置き換えます。

esbuildのHTTPSサポートはセキュリティとは関係がないことに注意してください。esbuildでHTTPSを有効にする唯一の理由は、ブラウザが、これらの追加の面倒な作業なしでは、特定の最新のWeb機能を使用してローカル開発を行うことを不可能にしたためです。安全である必要のあるものには、esbuildの開発サーバーを使用しないでください。ローカル開発専用であり、本番環境は一切考慮されていません。

サーバーの動作のカスタマイズ

esbuildのローカルサーバーにフックして、サーバー自体の動作をカスタマイズすることはできません。代わりに、esbuildの前にプロキシを配置して動作をカスタマイズする必要があります。

次に、Node.jsの組み込みのhttpモジュールを使用した、プロキシサーバーの簡単な例を示します。esbuildのデフォルトの404ページの代わりに、カスタム404ページを追加します。

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 { host, port } = await ctx.serve({ servedir: '.' })

// Then start a proxy server on port 3000
http.createServer((req, res) => {
  const options = {
    hostname: host,
    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 が処理する前にリクエストを変更または置き換えることもできます。

このようなプロキシを使用すると、さまざまなことができます。

より高度なニーズがある場合は、nginx のような実際のプロキシを使用することもできます。

Tsconfig

サポート対象: ビルド

通常、build API は、ビルド中に tsconfig.json ファイルを自動的に検出してその内容を読み取ります。ただし、代わりにカスタムの tsconfig.json ファイルを使用するように設定することもできます。これは、異なる設定で同じコードを複数回ビルドする必要がある場合に役立ちます。

CLI JS Go
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

サポート対象:ビルド変換

このオプションは、ファイルシステムにアクセスしない transform API に tsconfig.json ファイルを渡すために使用できます。また、tsconfig.json ファイルの内容をファイルに書き込むことなく、インラインで build API に渡すためにも使用できます。使用方法は次のとおりです。

CLI JS Go
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

サポート対象: ビルド

ウォッチモードを有効にすると、esbuild はファイルシステムの変更をリッスンし、ビルドを無効にする可能性のあるファイルが変更されるたびに自動的に再ビルドします。使用方法は次のとおりです。

CLI JS Go
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 を呼び出してファイルウォッチャーを終了できます。

CLI JS Go
# 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 のウォッチモードは、移植性のために OS 固有のファイルシステム API ではなくポーリングを使用して実装されています。ポーリングシステムは、ディレクトリツリー全体を一度にスキャンする従来のポーリングシステムと比較して、比較的少ない CPU を使用するように設計されています。ファイルシステムは定期的にスキャンされますが、各スキャンではファイルのランダムなサブセットのみがチェックされるため、ファイルの変更は変更が行われた直後に検出されますが、必ずしも瞬時には検出されません。

現在のヒューリスティックでは、大規模なプロジェクトは約 2 秒ごとに完全にスキャンされるため、最悪の場合、変更が検出されるまでに最大 2 秒かかる可能性があります。ただし、変更が検出された後、変更のパスは最近変更されたパスの短いリストに追加され、スキャンごとにチェックされるため、最近変更されたファイルへのさらなる変更はほぼ瞬時に検出されます。

ポーリングベースのアプローチを使用しない場合は、esbuild の rebuild API と選択したファイルウォッチャーライブラリを使用して、自分でウォッチモードを実装することもできます。

CLI を使用している場合は、esbuild の stdin が閉じられるとウォッチモードが終了することに注意してください。これにより、esbuild が誤って親プロセスよりも長く存続し、システムのリソースを予期せず消費し続けることを防ぎます。親プロセスが終了した場合でも esbuild が永遠に監視し続ける必要があるユースケースがある場合は、--watch の代わりに --watch=forever を使用できます。

入力

エントリーポイント

サポート対象: ビルド

これは、それぞれがバンドルアルゴリズムへの入力として機能するファイルの配列です。それらは「エントリーポイント」と呼ばれます。なぜなら、それぞれが評価される最初のスクリプトであり、そのスクリプトが表すコードの他のすべての側面をロードすることを目的としているからです。ページに多くのライブラリを <script> タグでロードする代わりに、import ステートメントを使用してそれらをエントリーポイントにインポートします(またはエントリーポイントにインポートされる別のファイルにインポートします)。

単純なアプリにはエントリーポイントが 1 つだけ必要ですが、メインスレッドとワーカースレッド、またはランディングページ、エディターページ、設定ページなどの比較的無関係な領域が分かれているアプリなど、複数の論理的に独立したコードグループがある場合は、追加のエントリーポイントが役立ちます。別々のエントリーポイントは、関心の分離を導入するのに役立ち、ブラウザがダウンロードする必要がある不要なコードの量を減らすのに役立ちます。該当する場合は、コード分割を有効にすると、最初に見られたページとすでにダウンロードされたコードの一部を共有する 2 番目のページを閲覧する際のダウンロードサイズをさらに削減できます。

エントリーポイントを指定する簡単な方法は、ファイルパスの配列を渡すだけです。

CLI JS Go
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)
  }
}

これにより、2 つのエントリーポイント home.tssettings.ts に対応する 2 つの出力ファイル out/home.jsout/settings.js が生成されます。

対応する入力エントリーポイントから出力ファイルのパスを導出する方法をさらに制御するには、次のオプションを検討する必要があります。

さらに、代替のエントリーポイント構文を使用して、個々のエントリーポイントごとに完全にカスタムの出力パスを指定することもできます。

CLI JS Go
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)
  }
}

これにより、2 つのエントリーポイント home.tssettings.ts に対応する 2 つの出力ファイル out/out1.jsout/out2.js が生成されます。

ローダー

サポート対象:ビルド変換

このオプションは、特定の入力ファイルの解釈方法を変更します。たとえば、js ローダーはファイルを JavaScript として解釈し、css ローダーはファイルを CSS として解釈します。組み込みローダーの完全なリストについては、コンテンツタイプのページを参照してください。

特定のファイルタイプのローダーを設定すると、import ステートメントまたは require 呼び出しでそのファイルタイプをロードできます。たとえば、.png ファイル拡張子が データ URL ローダーを使用するように設定した場合、.png ファイルをインポートすると、そのイメージの内容を含むデータ 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)

上記のコードは、次のように build API 呼び出しを使用してバンドルできます。

CLI JS Go
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)
  }
}

このオプションは、stdin からの入力で build API を使用している場合は、stdin にファイル拡張子がないため、異なる方法で指定されます。build API で stdin のローダーを設定すると、次のようになります。

CLI JS Go
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)
  }
}

transform API 呼び出しは、ファイルシステムとの対話を含まないため、単一のローダーのみを受け取ります。したがって、ファイル拡張子を扱いません。transform API のローダー(この場合は ts ローダー)を設定すると、次のようになります。

CLI JS Go
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 呼び出しは、入力として 1 つ以上のファイル名を受け取ります。ただし、このオプションを使用すると、ファイルシステムにモジュールが存在しない状態でビルドを実行できます。これは、コマンドラインでファイルを stdin にパイプすることに対応するため、「stdin」と呼ばれます。

stdin ファイルの内容を指定するだけでなく、オプションで解決ディレクトリ(相対インポートの場所を決定するために使用)、sourcefile(エラーメッセージとソースマップで使用するファイル名)、および ローダー(ファイル内容の解釈方法を決定)を指定することもできます。CLI には解決ディレクトリを指定する方法はありません。代わりに、現在の作業ディレクトリに自動的に設定されます。

この機能の使用方法は次のとおりです。

CLI JS Go
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)
  }
}

出力内容

サポート対象:ビルド変換

生成された JavaScript ファイルおよび CSS ファイルの先頭に任意の文字列を挿入するには、これを使用します。これは通常、コメントを挿入するために使用されます。

CLI JS Go
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 が (@charset ルール以外の) @import ルール以外のルールに続くすべての @import ルールを無視することに注意してください。したがって、バナーを使用して CSS ルールを挿入すると、外部スタイルシートのインポートが誤って無効になる可能性があります。

文字セット

サポート対象:ビルド変換

デフォルトでは、esbuild の出力は ASCII のみです。ASCII 以外の文字は、バックスラッシュエスケープシーケンスを使用してエスケープされます。その理由の 1 つは、ASCII 以外の文字がデフォルトでブラウザによって誤って解釈され、混乱を引き起こすためです。ブラウザがコードを改ざんしないようにするには、HTML に <meta charset="utf-8"> を明示的に追加するか、正しい Content-Type ヘッダーを付けて提供する必要があります。別の理由として、ASCII 以外の文字はブラウザのパーサーを大幅に遅くする可能性があります。ただし、エスケープシーケンスを使用すると、生成される出力がわずかに大きくなり、読み取りも困難になります。

esbuild にエスケープシーケンスを使用せずに元の文字を印刷させ、ブラウザがコードを UTF-8 として解釈することを保証した場合は、文字セットを設定して文字のエスケープを無効にできます。

CLI JS Go
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)
  }
}

いくつかの注意点

サポート対象:ビルド変換

生成された JavaScript ファイルおよび CSS ファイルの末尾に任意の文字列を挿入するには、これを使用します。これは通常、コメントを挿入するために使用されます。

CLI JS Go
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)
  }
}

これは、末尾ではなく先頭に挿入する banner と似ています。

形式

サポート対象:ビルド変換

これは、生成された JavaScript ファイルの出力形式を設定します。現在、設定可能な 3 つの値 iifecjs、および esm があります。出力形式が指定されていない場合、esbuild は バンドルが有効な場合は(以下で説明するように)出力形式を選択し、バンドルが無効な場合は形式変換を行いません。

IIFE

iife 形式は「即時実行関数式」の略で、ブラウザで実行することを目的としています。コードを関数式でラップすると、コード内の変数がグローバルスコープの変数と誤って競合しないようになります。エントリーポイントにブラウザでグローバルとして公開するエクスポートがある場合は、グローバル名設定を使用してそのグローバルの名前を設定できます。出力形式が指定されておらず、バンドルが有効で、プラットフォームbrowser(デフォルト)に設定されている場合、iife 形式は自動的に有効になります。iife 形式の指定は次のようになります。

CLI JS Go
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 での実行を想定しています。この形式は、環境に exportsrequire、および module が存在することを前提としています。ECMAScript モジュール構文でエクスポートを含むエントリポイントは、各エクスポート名に対して exports にゲッターを持つモジュールに変換されます。cjs 形式は、出力形式が指定されておらず、バンドルが有効で、プラットフォームnode に設定されている場合、自動的に有効になります。cjs 形式の指定は次のようになります。

CLI JS Go
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 モジュール構文でエクスポートを含むエントリポイントは、module.exports の値の単一の default エクスポートに変換されます。esm 形式は、出力形式が指定されておらず、バンドルが有効で、プラットフォームneutral に設定されている場合、自動的に有効になります。esm 形式の指定は次のようになります。

CLI JS Go
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 でも使用できますが、明示的にモジュールとしてロードする必要があります。これは、別のモジュールから import すると自動的に行われます。それ以外の場合は

グローバル名

サポート対象:ビルド変換

このオプションは、形式設定が iife(即時実行関数式)の場合にのみ意味があります。エントリポイントからのエクスポートを格納するために使用されるグローバル変数の名前を設定します。

CLI JS Go
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 はそのプロパティを持つグローバル変数を生成します。競合する既存のグローバル変数は上書きされません。これは、複数の独立したスクリプトが同じグローバルオブジェクトにエクスポートを追加する「ネームスペース」を実装するために使用できます。例えば

CLI JS Go
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();
})();

サポート対象:ビルド変換

「法的コメント」は、@license または @preserve を含む、または //! または /*! で始まる JS のステートメントレベルのコメントまたは CSS のルールレベルのコメントであると見なされます。これらのコメントは、コードの元の作者の意図に従っているため、デフォルトで出力ファイルに保持されます。ただし、この動作は、次のいずれかのオプションを使用して構成できます。

デフォルトの動作は、バンドルが有効な場合は eof で、それ以外の場合は inline です。法的コメントモードの設定は次のようになります。

CLI JS Go
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 文字を超えた直後に長い行を折り返します。

CLI JS Go
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 ステートメントに、既知の順序付けの問題があります。この機能の更新については、追跡中の問題を参照してください。

これにより、「コード分割」が有効になり、次の 2 つの目的を果たします。

コード分割を有効にする場合は、outdir 設定を使用して出力ディレクトリも構成する必要があります。

CLI JS Go
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)
  }
}

出力場所

上書きを許可

サポート対象: ビルド

この設定を有効にすると、出力ファイルが入力ファイルを上書きできるようになります。デフォルトでは有効になっていません。これは、ソースコードを上書きすることを意味し、コードがチェックインされていない場合はデータの損失につながる可能性があるためです。しかし、これをサポートすることで、一時ディレクトリの必要性を回避し、特定のワークフローが容易になります。したがって、ソースコードを意図的に上書きする場合は、これを有効にできます。

CLI JS Go
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)
  }
}

アセット名

サポート対象: ビルド

このオプションは、ローダーfile に設定されている場合に追加で生成される出力ファイルの名前を制御します。出力パスが生成されるときにファイルに固有の値で置き換えられるプレースホルダーを使用して、テンプレートで出力パスを構成します。たとえば、assets/[name]-[hash] のアセット名テンプレートを指定すると、すべてのアセットが出力ディレクトリ内の assets というサブディレクトリに配置され、ファイル名にアセットのコンテンツハッシュが含まれます。それを行うと、次のようになります。

CLI JS Go
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)
  }
}

アセットパスのテンプレートで使用できるプレースホルダーは 4 つあります。

アセットパステンプレートにファイル拡張子を含める必要はありません。テンプレート置換後、アセットの元のファイル拡張子は出力パスの末尾に自動的に追加されます。

このオプションは、チャンク名および エントリ名オプションに似ています。

チャンク名

サポート対象: ビルド

このオプションは、コード分割が有効な場合に自動的に生成される共有コードのチャンクのファイル名を制御します。出力パスが生成されるときにチャンクに固有の値で置き換えられるプレースホルダーを使用して、テンプレートで出力パスを構成します。たとえば、chunks/[name]-[hash] のチャンク名テンプレートを指定すると、生成されたすべてのチャンクが出力ディレクトリ内の chunks というサブディレクトリに配置され、ファイル名にチャンクのコンテンツハッシュが含まれます。それを行うと、次のようになります。

CLI JS Go
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)
  }
}

チャンクパステンプレートで使用できるプレースホルダーは 3 つあります。

チャンクパステンプレートにファイル拡張子を含める必要はありません。適切なコンテンツタイプ用に構成された 出力拡張子は、テンプレート置換後、出力パスの末尾に自動的に追加されます。

このオプションは、自動的に生成される共有コードのチャンクの名前のみを制御することに注意してください。エントリポイントに関連する出力ファイルの名前は制御しません。これらの名前は現在、outbase ディレクトリに対する元のエントリポイントファイルのパスから決定され、この動作は変更できません。エントリポイント出力ファイルのファイル名を変更できるようにする追加の API オプションが将来追加されます。

このオプションは、アセット名および エントリ名オプションに似ています。

エントリ名

サポート対象: ビルド

このオプションは、各入力エントリポイントファイルに対応する出力ファイルの名前を制御します。出力パスを生成する際に、ファイルに固有の値で置き換えられるプレースホルダーを使用したテンプレートを使用して出力パスを設定します。たとえば、エントリ名テンプレートを[dir]/[name]-[hash]と指定すると、ファイル名に出力ファイルのハッシュが含まれ、ファイルは出力ディレクトリ(場合によってはサブディレクトリ)に配置されます([dir]の詳細については下記を参照)。この設定は次のようになります。

CLI JS Go
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)
  }
}

エントリパステンプレートで使用できるプレースホルダーは4つあります。

エントリパステンプレートにファイル拡張子を含める必要はありません。ファイルタイプに基づいた適切な出力拡張子が、テンプレート置換後に出力パスの末尾に自動的に追加されます。

このオプションは、アセット名およびチャンク名オプションに似ています。

出力拡張子

サポート対象: ビルド

このオプションを使用すると、esbuildが生成するファイルの拡張子を.jsまたは.css以外のものにカスタマイズできます。特に、.mjsおよび.cjsのファイル拡張子は、nodeで特別な意味を持ちます(それぞれ、ESM形式とCommonJS形式のファイルを示します)。このオプションは、esbuildを使用して複数のファイルを生成する場合で、outfileオプションの代わりにoutdirオプションを使用する必要がある場合に役立ちます。次のように使用できます。

CLI JS Go
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)
  }
}

アウトベース

サポート対象: ビルド

ビルドに別々のディレクトリに複数のエントリポイントが含まれている場合、ディレクトリ構造はアウトベースディレクトリを基準にして出力ディレクトリに複製されます。たとえば、2つのエントリポイントsrc/pages/home/index.tssrc/pages/about/index.tsがあり、アウトベースディレクトリがsrcの場合、出力ディレクトリにはpages/home/index.jspages/about/index.jsが含まれます。使用方法は次のとおりです。

CLI JS Go
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)
  }
}

アウトベースディレクトリが指定されていない場合、すべての入力エントリポイントパスの中で共通の祖先ディレクトリがデフォルトになります。上記の例ではsrc/pagesであり、デフォルトでは出力ディレクトリには代わりにhome/index.jsabout/index.jsが含まれることを意味します。

出力ディレクトリ

サポート対象: ビルド

このオプションは、ビルド操作の出力ディレクトリを設定します。たとえば、このコマンドはoutというディレクトリを生成します。

CLI JS Go
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",
  })

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

出力ディレクトリが存在しない場合は生成されますが、すでにいくつかのファイルが含まれている場合はクリアされません。生成されたファイルは、同じ名前の既存のファイルを上書きします。出力ディレクトリにesbuildの現在の実行からのファイルのみが含まれるようにする場合は、esbuildを実行する前に自分で出力ディレクトリをクリアする必要があります。

ビルドに別々のディレクトリに複数のエントリポイントが含まれている場合、ディレクトリ構造は、すべての入力エントリポイントパスの中で共通の祖先ディレクトリから始まる出力ディレクトリに複製されます。たとえば、2つのエントリポイントsrc/home/index.tssrc/about/index.tsがある場合、出力ディレクトリにはhome/index.jsabout/index.jsが含まれます。この動作をカスタマイズする場合は、アウトベースディレクトリを変更する必要があります。

出力ファイル

サポート対象: ビルド

このオプションは、ビルド操作の出力ファイル名を設定します。これは、エントリポイントが1つしかない場合にのみ適用されます。複数のエントリポイントがある場合は、代わりにoutdirオプションを使用して出力ディレクトリを指定する必要があります。outfileの使用例は次のとおりです。

CLI JS Go
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,
    Outdir:      "out.js",
  })

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

パブリックパス

サポート対象: ビルド

これは、外部ファイルローダーと組み合わせて使用すると便利です。デフォルトでは、そのローダーは、インポートされたファイルの名前をdefaultエクスポートを使用して文字列としてエクスポートします。パブリックパスオプションを使用すると、このローダーによってロードされた各ファイルのエクスポートされた文字列にベースパスを付加できます。

CLI JS Go
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はファイルシステムに書き込み、Go APIは書き込みません。インメモリバッファーを使用するには、次のようにします。

JS 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に置き換えています。

CLI JS Go
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の他のすべてのパス解決ロジックよりも先に実行されます。この機能のユースケースの1つは、制御できないサードパーティコードで、nodeのみのパッケージをブラウザフレンドリーなパッケージに置き換えることです。

エイリアスを使用してインポートパスが置換されると、結果のインポートパスは、インポートパスを含むソースファイル内のディレクトリではなく、作業ディレクトリで解決されることに注意してください。必要に応じて、esbuildが使用する作業ディレクトリは、作業ディレクトリ機能で設定できます。

条件

サポート対象: ビルド

この機能は、package.jsonexportsフィールドがどのように解釈されるかを制御します。カスタム条件は、条件設定を使用して追加できます。これらは必要なだけ指定でき、これらの意味はパッケージ作成者に完全に委ねられます。Nodeは現在、推奨される使用のためにdevelopmentおよびproductionのカスタム条件のみを承認しています。カスタム条件custom1およびcustom2を追加する例を以下に示します。

CLI JS Go
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)
  }
}

条件の仕組み

条件を使用すると、さまざまな状況で同じインポートパスを異なるファイルの場所にリダイレクトできます。条件とパスを含むリダイレクトマップは、パッケージのpackage.jsonファイルのexportsフィールドに保存されます。たとえば、これはrequire('pkg/foo')pkg/required.cjsにリマップし、import 'pkg/foo'importおよびrequire条件を使用してpkg/imported.mjsにリマップします。

{
  "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に組み込まれており、無効にできない特別な動作を持つ5つの条件があります。

platformbrowserまたはnodeのいずれかに設定されており、カスタム条件が構成されていない場合、次の条件も自動的に含まれます。カスタム条件が(空のリストであっても)構成されている場合、この条件は自動的に含まれなくなります。

requireおよびimport条件を使用する場合、パッケージがバンドルに複数回含まれる可能性があります! これは、結果のバンドルを肥大化させるだけでなく、コードの状態の重複コピーによるバグを引き起こす可能性のある微妙な問題です。これは一般にデュアルパッケージハザードとして知られています。

バンドラーとnodeでネイティブに実行する場合の両方で機能するデュアルパッケージハザードを回避する方法の1つは、すべてのコードをCommonJSとしてrequire条件に配置し、import条件がパッケージでrequireを呼び出してESM構文を使用してパッケージを再エクスポートする軽量のESMラッパーになるようにすることです。ただし、esbuildはCommonJSモジュールをツリーシェイクしないため、このアプローチは適切なツリーシェイキングを提供しません。

デュアルパッケージの危険性を回避するもう1つの方法は、バンドラー固有のmodule条件を使用して、バンドラーが常にパッケージのESMバージョンをロードするように指示し、nodeが常にパッケージのCommonJSバージョンにフォールバックするようにすることです。importmoduleはどちらもESMで使用することを目的としていますが、importとは異なり、module条件は、インポートパスがrequire呼び出しを使用してロードされた場合でも常にアクティブです。これは、バンドラーがrequireを使用してESMをロードすることをサポートしているため、バンドラーでうまく機能しますが、nodeは意図的にrequireを使用したESMのロードを実装していないため、nodeでは機能しません。

外部

サポート対象: ビルド

ファイルまたはパッケージを外部としてマークして、ビルドから除外できます。バンドルされる代わりに、インポートは保持され(iifeおよびcjs形式ではrequireを使用し、esm形式ではimportを使用)、代わりに実行時に評価されます。

これにはいくつかの用途があります。まず、実行されないことがわかっているコードパスについて、バンドルから不要なコードを削除するために使用できます。たとえば、パッケージにはnodeでのみ実行されるコードが含まれている場合がありますが、そのパッケージはブラウザでのみ使用することになります。また、バンドルできないパッケージから、実行時にnodeでコードをインポートするためにも使用できます。たとえば、fseventsパッケージには、esbuildがサポートしていないネイティブ拡張機能が含まれています。何かを外部としてマークする方法は次のとおりです。

CLI JS Go
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/*を使用できます。

CLI JS Go
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)
  }
}

外部パスは、パス解決の前と後の両方に適用されるため、ソースコード内のインポートパスと絶対ファイルシステムパスの両方に一致させることができます。外部パスがどちらの場合でも一致した場合、パスは外部と見なされます。具体的な動作は次のとおりです。

メインフィールド

サポート対象: ビルド

nodeでパッケージをインポートすると、そのパッケージのpackage.jsonファイルのmainフィールドによって、どのファイルがインポートされるかが決定されます(その他多くのルールとともに)。esbuildを含む主要なJavaScriptバンドラーを使用すると、パッケージの解決時に試す追加のpackage.jsonフィールドを指定できます。少なくとも3つのそのようなフィールドが一般的に使用されています。

デフォルトのメインフィールドは、現在のプラットフォーム設定によって異なります。これらのデフォルトは、既存のパッケージエコシステムと最も広く互換性があるはずです。ただし、必要に応じて次のようにカスタマイズできます。

CLI JS Go
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)
  }
}

パッケージ作成者向け

moduleフィールドと組み合わせてbrowserフィールドを使用するパッケージを作成する場合は、おそらく、完全なCommonJS対ESMおよびブラウザ対nodeの互換性マトリックスの4つのエントリすべてを入力する必要があります。そのためには、単なる文字列ではなくマップである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固有のバリアントを使用するかどうかに関する決定とは独立しています。これらの4つのエントリのいずれかを省略すると、間違ったバリアントが選択されるリスクがあります。たとえば、CommonJSブラウザビルドのエントリを省略すると、代わりにCommonJS nodeビルドが選択される可能性があります。

mainmodule、およびbrowserを使用するのは古い方法であることに注意してください。代わりに、より新しい方法を使用することもできます。package.jsonexportsフィールドです。これは、異なるトレードオフを提供します。たとえば、パッケージ内のすべてのサブパスのインポートをより正確に制御できます(mainフィールドはエントリポイントのみを制御できます)が、構成方法によっては、パッケージが複数回インポートされる可能性があります。

Nodeパス

サポート対象: ビルド

nodeのモジュール解決アルゴリズムは、インポートパスの解決時に使用するグローバルディレクトリのリストを含むNODE_PATHという環境変数をサポートしています。これらのパスは、すべての親ディレクトリにあるnode_modulesディレクトリに加えて、パッケージを検索するために使用されます。CLIで環境変数を使用し、JSおよびGo APIで配列を使用して、このディレクトリのリストをesbuildに渡すことができます。

CLI 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)
  }
}

CLIを使用しており、NODE_PATHを使用して複数のディレクトリを渡す場合は、Unixでは:、Windowsでは;で区切る必要があります。これは、Node自体が使用するのと同じ形式です。

パッケージ

サポート対象: ビルド

この設定を使用して、パッケージのすべての依存関係をバンドルから除外します。これは、node用にバンドルする場合に役立ちます。これは、多くのnpmパッケージが、バンドル中にesbuildがサポートしないnode固有の機能(__dirnameimport.meta.urlfs.readFileSync、および*.nodeネイティブバイナリモジュールなど)を使用するためです。使用方法は次のとおりです。

CLI JS Go
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)
  }
}

これを有効にすると、npmパッケージのように見える(つまり、.または..パスコンポーネントで始まらず、絶対パスではない)すべてのインポートパスが自動的に外部としてマークされます。これは、各依存関係を外部に手動で渡すのと同じ効果がありますが、より簡潔です。どの依存関係を外部にし、どれを外部にしないかをカスタマイズする場合は、この設定ではなく外部を使用する必要があります。

この設定は、バンドルが有効になっている場合にのみ効果があることに注意してください。また、インポートパスを外部としてマークすることは、構成されたエイリアスによってインポートパスが書き換えられた後に行われるため、この設定を使用した場合でも、エイリアス機能は引き続き有効であることに注意してください。

サポート対象: ビルド

この設定は、nodeの--preserve-symlinks設定をミラーリングします。その設定(またはWebpackの同様のresolve.symlinks設定)を使用する場合は、esbuildでもこの設定を有効にする必要がある可能性があります。次のように有効にできます。

CLI JS Go
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 です。

CLI JS Go
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 オプションを使用すると、ビルドに使用するワーキングディレクトリを指定できます。通常、これは esbuild の API を呼び出すために使用しているプロセスの現在のワーキングディレクトリにデフォルト設定されます。ワーキングディレクトリは、API オプションとして与えられた相対パスを絶対パスに解決したり、ログメッセージで絶対パスを相対パスとして見やすく表示するなど、いくつかの異なる目的で esbuild によって使用されます。esbuild のワーキングディレクトリをカスタマイズする方法は次のとおりです。

CLI JS Go
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

サポート対象:ビルド変換

このオプションは、JSX 構文をどのように処理するかを esbuild に指示します。利用可能なオプションは以下のとおりです。

JSX 変換を preserve に設定する例を次に示します。

CLI JS Go
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 以外のものに設定されている場合、この設定は何も行いません。この設定を有効にする例を次に示します。

CLI JS Go
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 ファクトリ

サポート対象:ビルド変換

これは、各 JSX 要素に対して呼び出される関数を設定します。通常、このような JSX 式は

<div>Example text</div>

次のように、React.createElement への関数呼び出しにコンパイルされます。

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

JSX ファクトリを変更することで、React.createElement 以外のものを呼び出すことができます。たとえば、h 関数(Preactなどの他のライブラリで使用されます)を代わりに呼び出すには、次のようにします。

CLI JS Go
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 を使用している場合は、tsconfig.json ファイルに以下を追加することで、TypeScript 用の JSX を構成するだけで、esbuild は構成しなくても自動的にそれを認識するはずです。

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

ファイルごとにこれを構成したい場合は、// @jsx h コメントを使用することで設定できます。この設定は、JSX 変換が automatic に設定されている場合は適用されないことに注意してください。

JSX フラグメント

サポート対象:ビルド変換

これは、各 JSX フラグメントに対して呼び出される関数を設定します。通常、このような JSX フラグメント式は

<>Stuff</>

次のように、React.Fragment コンポーネントの使用にコンパイルされます。

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

JSX フラグメントを変更することで、React.Fragment 以外のコンポーネントを使用できます。たとえば、代わりに Fragment コンポーネント(Preactなどの他のライブラリで使用されます)を使用するには、次のようにします。

CLI JS Go
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 を使用している場合は、tsconfig.json ファイルに以下を追加することで、TypeScript 用の JSX を構成するだけで、esbuild は構成しなくても自動的にそれを認識するはずです。

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

ファイルごとにこれを構成したい場合は、// @jsxFrag Fragment コメントを使用することで設定できます。この設定は、JSX 変換が automatic に設定されている場合は適用されないことに注意してください。

JSX インポートソース

サポート対象:ビルド変換

JSX 変換が automatic に設定されている場合、これを設定することで、esbuild が JSX ヘルパー関数を自動的にインポートするために使用するライブラリを変更できます。これは、React 17+ に固有の JSX 変換でのみ機能することに注意してください。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 開発モードがオフの場合に使用され、jsxDEV インポートは JSX 開発モードがオンの場合に使用されます。これらの意味は、新しい JSX 変換に関する React のドキュメントで説明されています。createElement インポートは、要素にプロップスプレッドが続き、その後ろに key プロップがある場合に、JSX 開発モードに関係なく使用されます。これは次のように見えます。

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

JSX インポートソースを preact に設定する例を次に示します。

CLI JS Go
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 を使用している場合は、tsconfig.json ファイルに以下を追加することで、TypeScript の JSX インポートソースを構成するだけで、esbuild は構成しなくても自動的にそれを認識するはずです。

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

また、この設定をファイルごとに制御したい場合は、各ファイルで // @jsxImportSource your-pkg コメントを使用できます。JSX 変換が他の方法で設定されていない場合、またはファイルごとに設定したい場合は、// @jsxRuntime automatic コメントも追加する必要があるかもしれません。

JSX の副作用

サポート対象:ビルド変換

デフォルトでは、esbuild は JSX 式に副作用がないと想定しています。つまり、/* @__PURE__ */ コメントで注釈が付けられ、未使用の場合はバンドル中に削除されます。これは仮想 DOM での JSX の一般的な使用法に従っており、JSX ライブラリの大部分に適用されます。ただし、一部の人はこのプロパティを持たない JSX ライブラリを記述しています(具体的には、JSX 式は任意の副作用を持つことができ、未使用の場合は削除できません)。このようなライブラリを使用している場合は、この設定を使用して、JSX 式に副作用があることを esbuild に伝えることができます。

CLI JS Go
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 でサポートされていない構文機能のセットをカスタマイズできます。たとえば、BigIntsがサポートされていないことを esbuild に指示して、それを使用しようとしたときに esbuild がエラーを生成するようにすることができます。通常、これはtarget設定を使用するときに構成されます。これは、通常はこの設定の代わりに利用すべきものです。ターゲットがこの設定に加えて指定されている場合、この設定はターゲットによって指定されたものを上書きします。

ターゲットの設定に加えて、またはその代わりにこの設定を使用する必要がある理由の例を次に示します。

esbuild に特定の構文機能をサポートされていないとみなさせたい場合は、次のように指定できます。

CLI JS Go
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

CSS

ターゲット

サポート対象:ビルド変換

これは、生成された JavaScript および/または CSS コードのターゲット環境を設定します。esbuild に、これらの環境には新しすぎる JavaScript 構文を、これらの環境で動作する古い JavaScript 構文に変換するように指示します。たとえば、?? 演算子は Chrome 80 で導入されたため、esbuild は Chrome 79 以前をターゲットにする場合、それを同等の(ただし冗長な)条件式に変換します。

これは構文機能のみに関係しており、API には関係していないことに注意してください。これらの環境で使用されない新しい API 用の ポリフィル を自動的に追加するわけではありません。必要な API のポリフィルを明示的にインポートする必要があります(例:core-js をインポートするなど)。自動ポリフィル注入は esbuild の範囲外です。

各ターゲット環境は、環境名にバージョン番号が続きます。現在、次の環境名がサポートされています。

さらに、es2020 などの JavaScript 言語バージョンを指定することもできます。デフォルトのターゲットは esnext であり、これは、デフォルトでは、esbuild が最新の JavaScript および CSS 機能がすべてサポートされていると想定することを意味します。以下に、複数のターゲット環境を構成する例を示します。すべてを指定する必要はなく、プロジェクトに関係するターゲット環境のサブセットのみを指定できます。必要に応じて、バージョン番号をより正確に指定することもできます(例:単に node12 ではなく node12.19.0)。

CLI JS Go
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 ローダーを参照してください。es2020 などの JavaScript 言語バージョンは年で識別されますが、それは仕様が承認された年であることに留意してください。それは、すべての主要なブラウザーがその仕様を実装する年とは関係がなく、その年より早くまたは遅く実装されることがよくあります。

esbuild が現在の言語ターゲットに変換するためのサポートをまだ持っていない構文機能を使用すると、esbuild は、サポートされていない構文が使用されている場所でエラーを生成します。たとえば、esbuild はほとんどの新しい JavaScript 構文機能を es6 に変換することのみをサポートしているため、es5 言語バージョンをターゲットにすると、多くの場合、このようになります。

target が提供するものに加えて、またはその代わりに、個々の機能レベルでサポートされている構文機能のセットをカスタマイズする必要がある場合は、supported 設定でそれを行うことができます。

最適化

定義

サポート対象:ビルド変換

この機能は、グローバル識別子を定数式で置き換える方法を提供します。これにより、コード自体を変更せずに、ビルド間で一部のコードの動作を変更することができます。

CLI JS Go
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、ブール値、数値、文字列、配列、またはオブジェクト)または単一の識別子のいずれかである必要があります。配列およびオブジェクト以外の置換式はインラインで置換されます。これは、定数畳み込みに参加できることを意味します。配列およびオブジェクトの置換式は変数に格納され、インラインで置換される代わりに識別子を使用して参照されます。これにより、値の繰り返しコピーを置換することを回避できますが、値が定数畳み込みに参加しないことを意味します。

文字列リテラルで何かを置き換えたい場合は、esbuild に渡される置換値自体に引用符を含める必要があることに注意してください。これは、各 define エントリがコードを含む文字列にマッピングされるためです。引用符を省略すると、置換値は代わりに識別子になります。これは以下の例で示されています。

CLI JS Go
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)
  }
}

CLI を使用している場合、置換値が文字列の場合に必要となる二重引用符文字をエスケープする方法は、シェルによって異なることに注意してください。bash と Windows コマンドプロンプトの両方で機能するため、\" バックスラッシュエスケープを使用してください。単一引用符で囲むなど、bash で機能する二重引用符をエスケープする他の方法は、Windows コマンドプロンプトでは単一引用符が削除されないため、Windows では機能しません。これは、package.json ファイルの npm スクリプトから CLI を使用する場合に関連しており、すべてのプラットフォームで機能することが期待されます。

{
  "scripts": {
    "build": "esbuild --define:process.env.NODE_ENV=\\\"production\\\" app.js"
  }
}

異なるシェルでプラットフォームをまたいだ引用符のエスケープの問題が発生する場合は、代わりに JavaScript API を使用するように切り替えることをお勧めします。ここでは、通常の JavaScript 構文を使用して、プラットフォームをまたいだ違いを解消できます。

式を定数以外のもの(例:グローバル変数をシムで置き換えるなど)で置き換えることができる、より高度な定義機能をお探しの場合は、同様の inject 機能を使用してそれを行うことができる場合があります。

ドロップ

サポート対象:ビルド変換

これは、esbuild に、ビルドする前に特定の構造をドロップするためにソースコードを編集するように指示します。現在、ドロップできるものは 2 つあります。

CLI JS Go
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)
  }
}
CLI JS Go
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();
}

この機能を次のように構成できます(DEVTEST の両方のラベルをドロップします)。

CLI JS Go
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)
  }
}

これは、コードを条件付きで削除する唯一の方法ではないことに注意してください。もう 1 つの一般的な方法は、定義機能を使用して、特定のグローバル変数をブール値で置き換えることです。たとえば、次のコードについて考えてみましょう。

function example() {
  DEV && doAnExpensiveCheck()
  return normalCodePath()
}

DEVfalse と定義すると、esbuild は次のようになります。

function example() {
  return normalCodePath();
}

これは、ラベルを使用するのとほぼ同じです。ただし、グローバル変数ではなくラベルを使用してコードを条件付きで削除する利点は、誰かが esbuild を構成して何かで置き換えるのを忘れたために、グローバル変数が定義されていないことを心配する必要がないことです。ラベルアプローチを使用することのいくつかの欠点は、ラベルがドロップされないときにコードを条件付きで削除することをわずかに読みにくくすることと、ネストされた式に埋め込まれたコードでは機能しないことです。特定のプロジェクトでどのアプローチを使用するかは、個人の好みに帰着します。

注釈の無視

サポート対象:ビルド変換

JavaScript は動的な言語であるため、コンパイラーが未使用のコードを特定することは非常に困難な場合があるため、コミュニティは、どのコードを副作用がなく削除可能とみなすべきかをコンパイラーに伝えるのに役立つ特定の注釈を開発しました。現在、esbuild がサポートする副作用注釈には 2 つの形式があります。

これらの注釈は、コンパイラーが完全に開発者の正確さに依存しているため、開発者が誤った注釈でパッケージを公開することがあるため、問題になる可能性があります。sideEffects フィールドは、インポートが使用されていない場合、パッケージ内のすべてのファイルがデッドコードと見なされるため、開発者にとって特にエラーが発生しやすいです。副作用を含む新しいファイルを追加し、そのフィールドを更新するのを忘れると、パッケージをバンドルしようとしたときにパッケージが壊れる可能性があります。

これが、esbuild に副作用注釈を無視する方法が含まれている理由です。バンドルから必要なコードが予期せず削除されたためにバンドルが壊れているという問題が発生した場合にのみ、これを有効にする必要があります。

CLI JS Go
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())

これは、nodeのprocess.cwd()関数の使用を置き換えて、ブラウザで実行されたときにそれを呼び出すパッケージがクラッシュするのを防ぐことを目的としています。インジェクト機能を使用すると、グローバルプロパティprocess.cwdへのすべての参照をそのファイルからのインポートに置き換えることができます。

CLI JS Go
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機能と似ていると考えることができます。ただし、式を定数で置き換える代わりに、ファイルへのインポートで式を置き換え、置き換える式は、esbuildのAPIでインライン文字列を使用する代わりに、ファイル内のエクスポート名を使用して指定されます。

JSXの自動インポート

React(JSX構文が最初に作成されたライブラリ)には、JSX構文を使用するために何もimportする必要がないautomaticと呼ばれるモードがあります。代わりに、JSXからJSへのトランスフォーマーは、適切なJSXファクトリ関数を自動的にインポートします。esbuildのjsx設定でautomatic JSXモードを有効にできます。JSXの自動インポートが必要で、十分に新しいバージョンのReactを使用している場合は、automatic JSXモードを使用する必要があります。

ただし、jsxautomaticに設定すると、残念ながら、デフォルトの汎用JSXトランスフォームではなく、Reactに特化したJSXトランスフォームを使用することにもなります。これは、JSXファクトリ関数を記述するのがより複雑になることを意味し、また、automaticモードが標準のJSXトランスフォームで使用されることを期待するライブラリ(古いバージョンのReactを含む)では機能しないことを意味します。

esbuildのインジェクト機能を使用すると、JSXトランスフォームがautomaticに設定されていない場合に、JSX式のファクトリフラグメントを自動的にインポートできます。これを実行するためにインジェクトできるファイルの例を次に示します。

const { createElement, Fragment } = require('react')
export {
  createElement as 'React.createElement',
  Fragment as 'React.Fragment',
}

このコードではReactライブラリを例として使用していますが、適切な変更を加えることで、このアプローチを他のJSXライブラリでも使用できます。

インポートなしでファイルをインジェクトする

この機能を、エクスポートがないファイルでも使用できます。その場合、インジェクトされたファイルは、すべての入力ファイルにimport "./file.js"が含まれているかのように、出力の残りの部分の前に最初に配置されます。ECMAScriptモジュールの仕組みにより、このインジェクションは、異なるファイルで同じ名前のシンボルが互いに衝突しないように名前が変更される点で、依然として「衛生的」です。

条件付きでファイルをインジェクトする

エクスポートが実際に使用されている場合にのみ、ファイルを条件付きでインポートする場合は、パッケージに入れて、そのパッケージのpackage.jsonファイルに"sideEffects": falseを追加することにより、インジェクトされたファイルに副作用がないとマークする必要があります。この設定は、Webpackからの慣習であり、esbuildはインジェクトで使用されるファイルだけでなく、インポートされたすべてのファイルに対して尊重します。

名前を保持する

サポート対象:ビルド変換

JavaScriptでは、関数とクラスのnameプロパティは、ソースコード内の近くの識別子にデフォルトで設定されます。これらの構文形式はすべて、関数のnameプロパティを"fn"に設定します。

function fn() {}
let fn = function() {};
fn = function() {};
let [fn = function() {}] = [];
let {fn = function() {}} = {};
[fn = function() {}] = [];
({fn = function() {}} = {});

ただし、minifyはコードサイズを小さくするためにシンボルを名前変更し、バンドルは衝突を避けるためにシンボルを名前変更する必要がある場合があります。これにより、これらの多くの場合にnameプロパティの値が変更されます。通常、nameプロパティは通常デバッグにのみ使用されるため、これは問題ありません。ただし、一部のフレームワークは、登録およびバインドの目的でnameプロパティに依存しています。この場合は、このオプションを有効にして、minifyされたコードでも元のname値を保持できます。

CLI JS Go
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)
  }
}

この機能は、ターゲットが、関数とクラスのnameプロパティをesbuildが変更することを許可しない古い環境に設定されている場合は利用できないことに注意してください。これは、ES6をサポートしていない環境の場合です。

プロパティをマングルする

サポート対象:ビルド変換

この機能を使用すると、コードが微妙な方法で破損する可能性があります。自分が何をしているかを理解し、コードとすべての依存関係にどのように影響するかを正確に理解している場合を除き、この機能を使用しないでください。

この設定では、正規表現をesbuildに渡して、この正規表現に一致するすべてのプロパティを自動的に名前変更するように指示できます。これは、生成されたコードを小さくしたり、コードの意図をある程度難読化したりするために、コード内の一部のプロパティ名をminifyしたい場合に役立ちます。

以下に、foo_のようにアンダースコアで終わるすべてのプロパティをマングルするために、正規表現_$を使用する例を示します。これにより、print({ foo_: 0 }.foo_)print({ a: 0 }.a)にマングルされます。

CLI JS Go
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コードでは機能しない安全でない変換だからです。これは、指定された正規表現がマングルしたいすべてのプロパティと一致し、マングルしたくないプロパティと一致しない場合にのみ機能します。また、マングルされたプロパティを間接的に参照しない場合にのみ機能します。たとえば、propがプロパティ名を含む文字列である場合に、obj[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_) {} }

この機能を使用する場合は、プロパティ名は単一のesbuild API呼び出し内でのみ一貫してマングルされますが、esbuild API呼び出し間ではマングルされないことに注意してください。各esbuild API呼び出しは、独立したプロパティマングリング操作を実行するため、2つの異なるAPI呼び出しによって生成された出力ファイルは、同じプロパティを2つの異なる名前にマングルする可能性があり、結果としてコードが正しく動作しない可能性があります。

引用符付きプロパティ

デフォルトでは、esbuildは文字列リテラルの内容を変更しません。つまり、文字列として引用することにより、個々のプロパティのプロパティマングルを回避できます。ただし、これが機能するには、特定のプロパティに対して、常に引用符を使用するか、引用符を使用しない必要があります。たとえば、print({ foo_: 0 }.foo_)print({ a: 0 }.a)にマングルされますが、print({ 'foo_': 0 }['foo_'])はマングルされません。

esbuildにも文字列リテラルの内容をマングルさせたい場合は、次のように明示的にその動作を有効にすることができます。

CLI JS Go
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_'の内容がプロパティ名としてマングルされます(プロパティマングルが有効になっており、foo_が名前変更の対象であると仮定します)。/* @__KEY__ */コメントは、同様のプロパティマングル機能を備えた人気のあるJavaScript minifyツールであるTerserからの慣習です。

名前変更の防止

特定のプロパティをマングルから除外する場合は、追加の設定でそれらを予約できます。たとえば、これは、__foo__のように、2つのアンダースコアで始まり、2つのアンダースコアで終わるすべてのプロパティを予約するために、正規表現^__.*__$を使用します。

CLI JS Go
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)
  }
}

名前変更の決定の永続化

プロパティマングリング機能の高度な使用法には、元の名前からマングルされた名前へのマッピングを永続的なキャッシュに格納することが含まれます。有効にすると、初期ビルド中にすべてのマングルされたプロパティの名前変更がキャッシュに記録されます。後続のビルドでは、キャッシュに格納された名前変更が再利用され、新しく追加されたプロパティの名前変更が追加されます。これにはいくつかの結果があります。

例えば、次の入力ファイルを考えてみましょう。

console.log({
  someProp_: 1,
  customRenaming_: 2,
  disabledRenaming_: 3
});

customRenaming_cR_ にリネームし、disabledRenaming_ をリネームしないようにしたい場合、次のマングルキャッシュJSONをesbuildに渡すことができます。

{
  "customRenaming_": "cR_",
  "disabledRenaming_": false
}

マングルキャッシュJSONは、以下のようにesbuildに渡すことができます。

CLI JS Go
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"
}

minify

サポート対象:ビルド変換

有効にすると、生成されたコードはプリティプリントではなく、minifyされます。minifyされたコードは、一般的にminifyされていないコードと同等ですが、サイズが小さいため、ダウンロードは速くなりますが、デバッグは難しくなります。通常、本番環境ではコードをminifyしますが、開発環境ではminifyしません。

esbuildでminifyを有効にするには、次のようになります。

CLI JS Go
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)
  }
}

このオプションは、空白の削除、構文をよりコンパクトに書き換える、ローカル変数をより短い名前にリネームするという3つの異なることを組み合わせて実行します。通常、これらすべてを実行したいのですが、必要に応じてこれらのオプションを個別に有効にすることもできます。

CLI JS Go
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)
  }
}

これらの同じ概念は、JavaScriptだけでなく、CSSにも適用されます。

CLI JS Go
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)
  }
}

esbuildのJavaScript minifyアルゴリズムは通常、業界標準のJavaScript minifyツールのminifyされた出力サイズに非常に近い出力を生成します。このベンチマークには、異なるminifyツール間の出力サイズの比較例があります。esbuildは、すべてのケースで最適なJavaScript minifyツールではありません(また、そうしようともしていません)が、ほとんどのコードで専用のminifyツールのサイズから数パーセント以内のminifyされた出力を生成し、もちろん他のツールよりもはるかに高速に実行することを目指しています。

考慮事項

esbuildをminifyツールとして使用する際に留意すべき点を以下に示します。

pure

サポート対象:ビルド変換

さまざまなJavaScriptツールで使用されている慣例として、新規または呼び出し式の前に /* @__PURE__ */ または /* #__PURE__ */ を含む特別なコメントは、結果の値が未使用の場合、その式を削除できることを意味します。それはこのような感じです。

let button = /* @__PURE__ */ React.createElement(Button, null);

この情報は、ツリーシェイキング(別名、デッドコード削除)中にesbuildなどのバンドラーによって使用され、JavaScriptコードの動的な性質のために、バンドラー自身が削除が安全であることを証明できない状況で、モジュール境界を越えて未使用のインポートを詳細に削除するために使用されます。

コメントは「pure」と表示されていますが、混乱を招くことに、呼び出されている関数がpureであることを示すものではありません。例えば、その関数への繰り返し呼び出しをキャッシュしても良いとは示していません。この名前は、本質的に「未使用の場合、削除しても良い」の抽象的な略語にすぎません。

JSXや特定の組み込みグローバルなどの一部の式は、esbuildで自動的に /* @__PURE__ */ として注釈が付けられます。追加のグローバルを /* @__PURE__ */ としてマークすることもできます。例えば、グローバルな document.createElement 関数をそのようにマークすると、結果が使用されない限り、バンドルがminifyされたときに自動的にバンドルから削除されます。

注釈の効果は呼び出し自体にのみ拡張され、引数には拡張されないことは注目に値します。副作用のある引数は、minifyが有効になっている場合でも保持されます。

CLI JS Go
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.logなどのconsole APIメソッドへのすべての呼び出しを削除しようとしており、副作用のある引数の評価も削除したい場合、これには特別なケースがあります。console API呼び出しをpureとしてマークする代わりに、drop機能を使用できます。ただし、このメカニズムはconsole APIに固有のものであり、他の呼び出し式では機能しません。

ツリーシェイキング

サポート対象:ビルド変換

ツリーシェイキングは、JavaScriptコミュニティがデッドコード削除に使用する用語であり、到達不能なコードを自動的に削除する一般的なコンパイラー最適化です。esbuild内では、この用語は特に宣言レベルのデッドコード削除を指します。

ツリーシェイキングは、例で説明するのが最も簡単です。次のファイルを考えてみましょう。1つの使用された関数と1つの未使用の関数があります。

// 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はデフォルトでツリーシェイキングで機能する形式を選択しようとします。パッケージに応じて、main fieldsconditionsオプションを使用して、esbuildが選択する形式をカスタマイズできます。

デフォルトでは、ツリーシェイキングは、バンドルが有効になっているか、出力formatiifeに設定されている場合にのみ有効になり、それ以外の場合はツリーシェイキングは無効になります。trueに設定することで、ツリーシェイキングを強制的に有効にすることができます。

CLI JS Go
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に設定することで、ツリーシェイキングを強制的に無効にすることもできます。

CLI JS Go
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" + cdfoo.barなどの式は副作用がないとは言えません(文字列の結合はtoString()を呼び出し、副作用を持つ可能性があり、メンバーアクセスはgetterを呼び出して副作用を持つ可能性があります)。グローバル識別子を参照することさえ、その名前のグローバルが存在しない場合は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__ */がcall式でのみ機能するという事実は、コードを冗長にする場合がありますが、この構文の大きな利点は、人気のあるUglifyJSTerser JavaScriptミニファイア(WebpackParcelなどの他の主要ツールで使用されています)を含む、JavaScriptエコシステムの他の多くのツールで移植可能であることです。

アノテーションにより、esbuildは、注釈されたコードが副作用がないと仮定します。アノテーションが間違っていて、コードに実際には重要な副作用がある場合、これらのアノテーションはコードの破損につながる可能性があります。誤って作成されたアノテーションを含むサードパーティのコードをバンドルしている場合は、バンドルされたコードが正しいことを確認するためにアノテーションの無視を有効にする必要があるかもしれません。

ソースマップ

ソースルート

サポート対象:ビルド変換

この機能は、ソースマップが有効になっている場合にのみ関連します。これにより、ソースマップ内のすべてのパスが相対パスになるパスを指定する、ソースマップ内のsourceRootフィールドの値を設定できます。このフィールドが存在しない場合、ソースマップ内のすべてのパスは、ソースマップを含むディレクトリに対する相対パスとして解釈されます。

sourceRootは次のように構成できます。

CLI JS Go
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)
  }
}

ソースファイル

サポート対象:ビルド変換

このオプションは、ファイル名がない入力を使用する場合にファイル名を設定します。これは、transform APIを使用する場合や、stdinでbuild APIを使用する場合に発生します。設定されたファイル名は、エラーメッセージとソースマップに反映されます。構成されていない場合、ファイル名はデフォルトで<stdin>になります。これは次のように構成できます。

CLI JS Go
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であるか、minifyを有効にした場合)に役立ちます。また、1つの大きなバンドルされたファイルではなく、ブラウザの開発者ツールで個々のファイルを見たい場合にも役立ちます。

ソースマップの出力はJavaScriptとCSSの両方でサポートされており、同じオプションが両方に適用されることに注意してください。以下で.jsファイルについて説明することは、同様に.cssファイルにも適用されます。

ソースマップの生成には、4つの異なるモードがあります。

  1. linked

    このモードでは、ソースマップは.js出力ファイルとともに別の.js.map出力ファイルに生成され、.js出力ファイルには、.js.map出力ファイルを指す特別な//# sourceMappingURL=コメントが含まれます。これにより、ブラウザはデバッガーを開いたときに、特定のファイルのソースマップをどこで検索すればよいかを知ることができます。次のようにlinkedソースマップモードを使用します。

CLI JS Go
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出力ファイルとともに別の.js.map出力ファイルに生成されますが、linkedモードとは異なり、.js出力ファイルには//# sourceMappingURL=コメントが含まれません。次のようにexternalソースマップモードを使用します。

CLI JS Go
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

    このモードでは、ソースマップは//# sourceMappingURL=コメント内のbase64ペイロードとして.js出力ファイルの末尾に追加されます。追加の.js.map出力ファイルは生成されません。ソースマップには通常、元のソースコードがすべて含まれているため、非常に大きくなることに注意してください。そのため、通常はinlineソースマップを含むコードを出荷したくありません。ソースマップからソースコードを削除する(ファイル名と行/列のマッピングのみを保持する)には、ソースコンテンツオプションを使用します。次のようにinlineソースマップモードを使用します。

CLI JS Go
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

    このモードは、inlineexternalの組み合わせです。ソースマップは、.js出力ファイルの末尾にインラインで追加され、同じソースマップの別のコピーが、.js出力ファイルとともに別の.js.map出力ファイルに書き込まれます。次のようにbothソースマップモードを使用します。

CLI JS Go
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)
  }
}

build APIは、上記でリストされた4つのソースマップモードをすべてサポートしていますが、transform APIはlinkedモードをサポートしていません。これは、transform APIから返される出力には関連付けられたファイル名がないためです。transform APIの出力にソースマップコメントを付けたい場合は、自分で追加できます。さらに、transform APIのCLI形式は、出力がstdoutに書き込まれるため、複数の出力ファイルを生成することができないため、inlineモードのみをサポートしています。

ソースマップが何をするのかを「詳しく見て」みたい場合(または、ソースマップに関する問題をデバッグする場合)は、関連する出力ファイルと関連付けられたソースマップをここにアップロードできます:ソースマップの可視化

ソースマップの使用

ブラウザでは、ソースマップ設定が有効になっていれば、ブラウザの開発者ツールによってソースマップが自動的に選択されるはずです。ブラウザは、ソースマップをコンソールにログされたスタックトレースの表示を変更するためにのみ使用することに注意してください。スタックトレース自体は変更されないため、コード内のerror.stackを調べると、コンパイルされたコードを含むマップされていないスタックトレースが引き続き表示されます。ブラウザの開発者ツールでこの設定を有効にする方法は次のとおりです。

nodeでは、ソースマップはバージョンv12.12.0以降からネイティブでサポートされています。この機能はデフォルトで無効になっていますが、フラグで有効にできます。ブラウザとは異なり、実際のスタックトレースもnodeで変更されるため、コード内のerror.stackを調べると、元のソースコードを含むマップされたスタックトレースが表示されます。nodeでこの設定を有効にする方法は次のとおりです(--enable-source-mapsフラグは、スクリプトファイル名の前に来る必要があります)。

node --enable-source-maps app.js

ソースコンテンツ

サポート対象:ビルド変換

ソースマップは、最も広くサポートされているバリアントであるソースマップ形式のバージョン3を使用して生成されます。各ソースマップは、次のようになります。

{
  "version": 3,
  "sources": ["bar.js", "foo.js"],
  "sourcesContent": ["bar()", "foo()\nimport './bar'"],
  "mappings": ";AAAA;;;ACAA;",
  "names": []
}

sourcesContentフィールドは、元のソースコードをすべて含むオプションのフィールドです。これにより、デバッガーで元のソースコードが利用できるようになるため、デバッグに役立ちます。

ただし、一部のシナリオでは必要ありません。たとえば、本番環境でソースマップを使用して、元のファイル名を含むスタックトレースを生成するだけであれば、デバッガーが関与しないため、元のソースコードは必要ありません。その場合、ソースマップを小さくするためにsourcesContentフィールドを省略することが望ましい場合があります。

CLI JS Go
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のメタファイルをアップロードして、バンドルサイズの分析結果を確認できます。

分析機能を使用すると、バンドルの内容に関する読みやすいレポートが生成されます。

CLI JS Go
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{}))
}

この情報は、各出力ファイルにどの入力ファイルが入ったか、および入力ファイルが出力ファイルの何パーセントを占めているかを示します。追加情報が必要な場合は、「詳細」モードを有効にできます。現在、これは、エントリーポイントから各入力ファイルへのインポートパスを表示します。これにより、特定の入力ファイルがバンドルに含まれている理由がわかります。

CLI JS Go
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というファイルに入れます。

CLI JS Go
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独自のバンドルサイズアナライザーを使用できます。テキスト分析をすばやく行う場合は、esbuildの組み込みの分析機能を使用できます。または、この情報を使用する独自の分析を作成することもできます。

メタデータ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
    }
  }
}

ロギング

サポート対象:ビルド変換

このオプションは、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に設定すると、カラー出力を強制的に有効にできます。これは、esbuildのstderr出力を自分でTTYにパイプしている場合に役立ちます。

CLI JS Go
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に設定することもできます。

メッセージの書式設定

サポート対象:ビルド変換

このAPI呼び出しは、build APIとtransform APIによって返されるログのエラーと警告を、esbuild自体が使用するのと同じ書式で文字列としてフォーマットするために使用できます。これは、esbuildのロギング方法をカスタマイズしたい場合に役立ちます。たとえば、ログメッセージを印刷前に処理したり、コンソール以外の場所に印刷したりする場合などです。以下に例を示します。

JS Go
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"))
}

オプション

以下のオプションを指定して、フォーマットを制御できます。

JS Go
interface FormatMessagesOptions {
  kind: 'error' | 'warning';
  color?: boolean;
  terminalWidth?: number;
}
type FormatMessagesOptions struct {
  Kind          MessageKind
  Color         bool
  TerminalWidth int
}

ログレベル

サポート対象:ビルド変換

ログレベルを変更して、esbuildが警告やエラーメッセージを端末に出力しないようにできます。6つのログレベルがあります。

ログレベルは次のように設定できます。

CLI JS Go
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)
  }
}

ログ制限

サポート対象:ビルド変換

デフォルトでは、esbuildは10個のメッセージが報告された後、ログメッセージの報告を停止します。これにより、膨大な数のログメッセージが誤って生成されるのを防ぎます。これは、Windowsコマンドプロンプトなどの低速な端末エミュレータを簡単にロックアップさせる可能性があります。また、スクロールバッファが限られている端末エミュレータでスクロールバッファ全体を誤って使い切ってしまうのを防ぎます。

ログ制限は別の値に変更でき、ゼロに設定することで完全に無効にすることもできます。これにより、すべてのログメッセージが表示されます。

CLI JS Go
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)
  }
}

ログのオーバーライド

サポート対象:ビルド変換

この機能を使用すると、ログメッセージの個々のタイプのログレベルを変更できます。特定のタイプの警告を抑制したり、デフォルトで有効になっていない追加の警告を有効にしたり、警告をエラーに変換したりすることもできます。

たとえば、古いブラウザーをターゲットにする場合、esbuildは、それらのブラウザーでは新しすぎる機能を使用する正規表現リテラルを自動的にnew RegExp()呼び出しに変換し、生成されたコードがブラウザーによって構文エラーと見なされずに実行できるようにします。ただし、その正規表現構文は依然としてサポートされていないため、RegExpのポリフィルを追加しない場合、これらの呼び出しは実行時に例外をスローします。新しいサポートされていない正規表現構文を使用するときにesbuildに警告を生成させたい場合は、次のように行うことができます。

CLI JS Go
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)
  }
}

各メッセージタイプのログレベルは、ログレベルの設定でサポートされている任意の値にオーバーライドできます。現在利用可能なすべてのメッセージタイプを以下に示します(各メッセージの例については、クリックしてください)。

これらのメッセージタイプは合理的に安定している必要がありますが、新しいものが追加されたり、古いものが将来的に削除されたりする可能性があります。メッセージタイプが削除された場合、そのメッセージタイプに対するオーバーライドは、単にサイレントに無視されます。