はじめに

esbuild のインストール

まず、esbuild コマンドをローカルにダウンロードしてインストールします。プリビルド済みのネイティブ実行可能ファイルは、npm (これは、node JavaScript ランタイムをインストールすると自動的にインストールされます) を使用してインストールできます。

npm install --save-exact --save-dev esbuild

これで、esbuild がローカルの node_modules フォルダーにインストールされたはずです。esbuild の実行可能ファイルを実行して、すべてが正常に動作していることを確認できます。

Unix Windows
./node_modules/.bin/esbuild --version
.\node_modules\.bin\esbuild --version

esbuild をインストールする推奨される方法は、npm を使用してネイティブ実行可能ファイルをインストールすることです。ただし、そうしたくない場合は、その他のインストール方法もいくつかあります。

最初のバンドル

これは、esbuild が何ができるか、およびその使用方法を示す簡単な現実世界の例です。まず、react および react-dom パッケージをインストールします。

npm install react react-dom

次に、次のコードを含む app.jsx というファイルを作成します。

import * as React from 'react'
import * as Server from 'react-dom/server'

let Greet = () => <h1>Hello, world!</h1>
console.log(Server.renderToString(<Greet />))

最後に、esbuild にファイルをバンドルするように指示します。

Unix Windows
./node_modules/.bin/esbuild app.jsx --bundle --outfile=out.js
.\node_modules\.bin\esbuild app.jsx --bundle --outfile=out.js

これにより、コードと React ライブラリをまとめた out.js というファイルが作成されたはずです。コードは完全に自己完結型であり、node_modules ディレクトリに依存しなくなりました。node out.js を使用してコードを実行すると、次のようなものが表示されるはずです。

<h1 data-reactroot="">Hello, world!</h1>

esbuild は、.jsx 拡張子以外に設定なしで JSX 構文も JavaScript に変換したことに注意してください。esbuild は構成できますが、多くの一般的な状況が自動的に動作するように、妥当なデフォルトを設定しようとします。.js ファイルで JSX 構文を使用する場合は、--loader:.js=jsx フラグを使用してこれを許可するように esbuild に指示できます。利用可能な構成オプションの詳細については、API ドキュメントを参照してください。

ビルドスクリプト

ビルドコマンドは繰り返し実行するものであるため、自動化する必要があります。これを行う自然な方法は、次のように package.json ファイルにビルドスクリプトを追加することです。

{
  "scripts": {
    "build": "esbuild app.jsx --bundle --outfile=out.js"
  }
}

これは、相対パスなしで esbuild コマンドを直接使用していることに注意してください。これは、scripts セクションのすべてが、パスに esbuild コマンドがすでにある状態で実行されるためです (前提として パッケージをインストールしている必要があります)。

ビルドスクリプトは、次のように呼び出すことができます。

npm run build

ただし、多くのオプションを esbuild に渡す必要がある場合、コマンドラインインターフェースを使用すると扱いにくくなる可能性があります。より複雑な使用法では、esbuild の JavaScript API を使用して JavaScript でビルドスクリプトを作成することが望ましいでしょう。これは次のようになる可能性があります (このコードは、import キーワードを使用しているため、.mjs 拡張子の付いたファイルに保存する必要があることに注意してください)。

import * as esbuild from 'esbuild'

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

build 関数は、子プロセスで esbuild 実行可能ファイルを実行し、ビルドが完了すると解決される Promise を返します。非同期ではない buildSync API もありますが、プラグインは非同期 API でのみ機能するため、非同期 API の方がビルドスクリプトに適しています。ビルド API の構成オプションの詳細については、API ドキュメントを参照してください。

ブラウザ向けのバンドル

バンドラーはデフォルトでブラウザ用のコードを出力するため、開始するのに追加の設定は必要ありません。開発ビルドでは、おそらく --sourcemapソースマップを有効にし、本番ビルドでは、おそらく --minify圧縮を有効にしたいでしょう。また、サポートするブラウザの ターゲット環境を構成して、新しすぎる JavaScript 構文が古い JavaScript 構文に変換されるようにする必要もあるでしょう。すべてをまとめると、次のようになるかもしれません。

CLI JS Go
esbuild app.jsx --bundle --minify --sourcemap --target=chrome58,firefox57,safari11,edge16
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.jsx'],
  bundle: true,
  minify: true,
  sourcemap: true,
  target: ['chrome58', 'firefox57', 'safari11', 'edge16'],
  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"},
    Bundle:            true,
    MinifyWhitespace:  true,
    MinifyIdentifiers: true,
    MinifySyntax:      true,
    Engines: []api.Engine{
      {api.EngineChrome, "58"},
      {api.EngineFirefox, "57"},
      {api.EngineSafari, "11"},
      {api.EngineEdge, "16"},
    },
    Write: true,
  })

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

使用したい npm パッケージの中には、ブラウザで実行するように設計されていないものがあるかもしれません。場合によっては、esbuild の構成オプションを使用して特定の問題を回避し、パッケージを正常にバンドルできることがあります。単純な場合は define 機能で、より複雑な場合は inject 機能で未定義のグローバルを置き換えることができます。

Node 向けのバンドル

Node を使用する場合、バンドラーは必要ありませんが、Node で実行する前に esbuild でコードを処理することが有益な場合があります。バンドルは、TypeScript 型を自動的に削除し、ECMAScript モジュール構文を CommonJS に変換し、Node の特定のバージョンの新しい JavaScript 構文を古い構文に変換できます。また、パッケージを公開する前にバンドルすることで、ダウンロードサイズを小さくし、ロード時にファイルシステムからの読み取り時間を短縮できるという利点があります。

Node で実行されるコードをバンドルする場合は、--platform=node を esbuild に渡して platform 設定を構成する必要があります。これにより、いくつかの異なる設定が同時に Node に適したデフォルト値に変更されます。たとえば、fs など Node に組み込まれているすべてのパッケージは、esbuild がバンドルしようとしないように自動的に外部としてマークされます。この設定では、package.json の browser フィールドの解釈も無効になります。

コードで、使用している Node のバージョンで動作しない新しい JavaScript 構文を使用している場合は、ターゲットの Node バージョンを構成する必要があります。

CLI JS Go
esbuild app.js --bundle --platform=node --target=node10.4
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  platform: 'node',
  target: ['node10.4'],
  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,
    Engines: []api.Engine{
      {api.EngineNode, "10.4"},
    },
    Write: true,
  })

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

また、依存関係を esbuild でバンドルしたくない場合もあります。__dirnameimport.meta.urlfs.readFileSync*.node のネイティブバイナリモジュールなど、esbuild がバンドル中にサポートしない Node 固有の機能はたくさんあります。packages を外部に設定することにより、すべての依存関係をバンドルから除外できます。

CLI JS Go
esbuild app.jsx --bundle --platform=node --packages=external
require('esbuild').buildSync({
  entryPoints: ['app.jsx'],
  bundle: true,
  platform: 'node',
  packages: '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.jsx"},
    Bundle:      true,
    Platform:    api.PlatformNode,
    Packages:    api.PackagesExternal,
    Write:       true,
  })

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

これを行う場合、依存関係はバンドルに含まれなくなったため、実行時にファイルシステム上に存在している必要があります。

複数のプラットフォームを同時に使用する

ある OS に esbuild をインストールし、node_modules ディレクトリを再インストールせずに別の OS にコピーしてから、その別の OS で esbuild を実行することはできません。これは、esbuild がネイティブコードで記述されており、プラットフォーム固有のバイナリ実行可能ファイルをインストールする必要があるため、機能しません。通常、node_modules ディレクトリではなく package.json ファイルをバージョン管理にチェックインし、リポジトリをクローンした後、すべてのユーザーがローカルマシンで npm install を実行するため、これは問題になりません。

ただし、Windows または macOS に esbuild をインストールし、node_modules ディレクトリを Linux を実行する Docker イメージにコピーしたり、node_modules ディレクトリを Windows と WSL 環境間でコピーしたりすることで、この状況に陥ることがあります。これを機能させる方法は、パッケージマネージャーによって異なります。

また、npm の ARM バージョンを使用して esbuild をインストールしたが、Rosetta の内部で実行されている x86-64 バージョンの Node で esbuild を実行しようとすると、ARM プロセッサを搭載した macOS コンピューターでこの状況に陥る可能性があります。その場合、簡単な修正方法は、代わりに ARM バージョンの Node を使用してコードを実行することです。これは、https://node.dokyumento.jp/en/download/ からダウンロードできます。

別の方法として、代わりに esbuild-wasm パッケージを使用することもできます。これはすべてのプラットフォームで同じように機能します。ただし、パフォーマンスコストが高く、esbuild パッケージよりも 10 倍遅くなることがあるため、そうしない方がよい場合もあります。

Yarn Plug'n'Play の使用

Yarn の Plug'n'Play パッケージインストール戦略は、esbuild によってネイティブにサポートされています。これを使用するには、現在の作業ディレクトリに Yarn によって生成されたパッケージマニフェスト JavaScript ファイル (.pnp.cjs または .pnp.js) が含まれるように esbuild を実行していることを確認してください。Yarn Plug'n'Play パッケージマニフェストが検出されると、esbuild は自動的にパッケージのインポートを Yarn のパッケージキャッシュ内の .zip ファイル内のパスに解決し、バンドル中にこれらのファイルをオンザフライで自動的に抽出します。

esbuildはGoで記述されているため、Yarn Plug'n'Playのサポートは、YarnのJavaScript APIに依存するのではなく、Goで完全に再実装されています。これにより、Yarn Plug'n'Playのパッケージ解決が、esbuildの完全に並列化されたバンドリングパイプラインと適切に統合され、最大の速度を実現します。Yarnのコマンドラインインターフェースは、すべてのコマンドに避けられないパフォーマンスオーバーヘッドを追加することに注意してください。esbuildのパフォーマンスを最大限に引き出すには、YarnのCLIを使用せずに(つまり、yarn esbuildを使用せずに)esbuildを実行することを検討するとよいでしょう。これにより、esbuildの実行速度が10倍速くなる可能性があります。

その他のインストール方法

esbuildをインストールする推奨方法は、npmを使用してネイティブ実行可能ファイルをインストールすることです。ただし、次の方法でもesbuildをインストールできます。

ビルドをダウンロードする

Unixシステムをお持ちの場合、次のコマンドを使用して、現在のプラットフォーム用のesbuildバイナリ実行可能ファイルをダウンロードできます(現在の作業ディレクトリにダウンロードされます)。

curl -fsSL https://esbuild.dokyumento.jp/dl/v0.20.1 | sh

バージョン番号の代わりにlatestを使用して、最新バージョンのesbuildをダウンロードすることもできます。

curl -fsSL https://esbuild.dokyumento.jp/dl/latest | sh

インターネットからシェルスクリプトを評価してesbuildをダウンロードしたくない場合は、npmから手動でパッケージをダウンロードすることもできます(上記のシェルスクリプトが実行していることと全く同じです)。プリコンパイル済みのネイティブ実行可能ファイルはnpmを使用してホストされていますが、ダウンロードするためにnpmをインストールする必要はありません。npmパッケージレジストリは通常のHTTPサーバーであり、パッケージは通常のgzip圧縮されたtarファイルです。

バイナリ実行可能ファイルを直接ダウンロードする例を次に示します。

curl -O https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz
tar xzf ./darwin-x64-0.20.1.tgz
./package/bin/esbuild
Usage:
  esbuild [options] [entry points]

...

@esbuild/darwin-x64パッケージのネイティブ実行可能ファイルは、macOSオペレーティングシステムと64ビットIntelアーキテクチャ用です。執筆時点では、これがesbuildがサポートするプラットフォーム用のネイティブ実行可能ファイルパッケージの完全なリストです。

パッケージ名 OS アーキテクチャ ダウンロード
@esbuild/aix-ppc64 aix ppc64
@esbuild/android-arm android arm
@esbuild/android-arm64 android arm64
@esbuild/android-x64 android x64
@esbuild/darwin-arm64 darwin arm64
@esbuild/darwin-x64 darwin x64
@esbuild/freebsd-arm64 freebsd arm64
@esbuild/freebsd-x64 freebsd x64
@esbuild/linux-arm linux arm
@esbuild/linux-arm64 linux arm64
@esbuild/linux-ia32 linux ia32
@esbuild/linux-loong64 linux loong642
@esbuild/linux-mips64el linux mips64el2
@esbuild/linux-ppc64 linux ppc64
@esbuild/linux-riscv64 linux riscv642
@esbuild/linux-s390x linux s390x
@esbuild/linux-x64 linux x64
@esbuild/netbsd-x64 netbsd1 x64
@esbuild/openbsd-x64 openbsd x64
@esbuild/sunos-x64 sunos x64
@esbuild/win32-arm64 win32 arm64
@esbuild/win32-ia32 win32 ia32
@esbuild/win32-x64 win32 x64

これが推奨されない理由: このアプローチは、シェルスクリプトを実行できるUnixシステムでのみ機能するため、WindowsではWSLが必要になります。追加の欠点は、esbuildのネイティブバージョンではプラグインを使用できないことです。

npmからesbuildを直接ダウンロードする独自のコードを作成する場合、esbuildのネイティブ実行可能ファイルインストーラーの内部実装詳細に依存することになります。これらの詳細はある時点で変更される可能性があり、その場合、このアプローチは新しいesbuildバージョンでは機能しなくなります。ただし、このアプローチは既存のesbuildバージョンでは引き続き永続的に機能するはずであるため(npmに公開されたパッケージは不変です)、これは小さな欠点にすぎません。

WASMバージョンのインストール

esbuild npmパッケージに加えて、同様に機能するが、ネイティブコードの代わりにWebAssemblyを使用するesbuild-wasmパッケージもあります。インストールすると、esbuildという名前の実行可能ファイルもインストールされます。

npm install --save-exact esbuild-wasm

これが推奨されない理由: WebAssemblyバージョンはネイティブバージョンよりもはるかに遅いです。多くの場合、1桁(つまり、10倍)遅いです。これは、a) nodeが実行ごとにWebAssemblyコードをゼロから再コンパイルする、b) GoのWebAssemblyコンパイルアプローチがシングルスレッドである、c) nodeにプロセスの終了を数秒遅らせる可能性のあるWebAssemblyのバグがある、などのさまざまな理由によります。WebAssemblyバージョンには、ローカルファイルサーバーなどの一部の機能も含まれていません。サポートされていないプラットフォームでesbuildを使用したい場合など、他に選択肢がない場合にのみ、このような方法でWebAssemblyパッケージを使用する必要があります。WebAssemblyパッケージは、主にブラウザで使用することのみを目的としています。

nodeの代わりにDeno

Deno JavaScript環境を代わりにesbuildで使用したい場合に備えて、基本的なサポートも用意されています。パッケージはhttps://deno.land/x/esbuildでホストされており、ネイティブのesbuild実行可能ファイルを使用します。実行可能ファイルは、実行時にnpmからダウンロードされてキャッシュされるため、このパッケージを使用するには、コンピューターがregistry.npmjs.orgへのネットワークアクセスを必要とします。パッケージを使用すると、次のようになります。

import * as esbuild from 'https://deno.land/x/esbuild@v0.20.1/mod.js'
let ts = 'let test: boolean = true'
let result = await esbuild.transform(ts, { loader: 'ts' })
console.log('result:', result)
await esbuild.stop()

基本的にはesbuildのnpmパッケージと同じAPIを持っていますが、1つ追加があります。nodeとは異なり、Denoにはesbuildの内部子プロセスが実行中にDenoを終了させるための必要なAPIがないため、終了したらstop()を呼び出す必要があります。

Denoでesbuildのネイティブ実装ではなく、esbuildのWebAssembly実装を使用したい場合は、次のようにmod.jsの代わりにwasm.jsをインポートすることで実現できます。

import * as esbuild from 'https://deno.land/x/esbuild@v0.20.1/wasm.js'
let ts = 'let test: boolean = true'
let result = await esbuild.transform(ts, { loader: 'ts' })
console.log('result:', result)
await esbuild.stop()

ネイティブの代わりにWebAssemblyを使用するということは、Denoの--allow-run権限を指定する必要がないことを意味し、WebAssemblyはDeno Deployのようにファイルシステムが利用できない状況での唯一のオプションです。ただし、esbuildのWebAssemblyバージョンはネイティブバージョンよりもはるかに遅いことに注意してください。WebAssemblyについてもう1つ知っておくべきことは、Denoには、ロードされたすべてのWebAssemblyモジュールが完全に最適化されるまで、プロセスの終了が不必要に遅延するというバグが現在あり、これには数秒かかる場合があります。esbuildのWebAssembly実装を使用する短寿命のスクリプトを作成する場合は、コードが合理的な時間内に終了するように、コードが完了した後でDeno.exit(0)を手動で呼び出すとよいでしょう。

これが推奨されない理由: Denoはnodeよりも新しく、使用されている範囲も狭く、nodeよりもサポートするプラットフォームが少ないため、esbuildを実行する主な方法としてnodeが推奨されます。Denoは、既存のJavaScriptパッケージエコシステムではなく、インターネットをパッケージシステムとしても使用しており、esbuildはnpmスタイルのパッケージ管理を中心に設計および最適化されています。Denoでもesbuildを使用できるはずですが、HTTP URLをバンドルできるようにする場合は、プラグインが必要になります。

ソースからビルドする

ソースからesbuildをビルドするには

  1. Goコンパイラをインストールします

    https://go.dokyumento.jp/dl/

  2. esbuildのソースコードをダウンロードします
    git clone --depth 1 --branch v0.20.1 https://github.com/evanw/esbuild.git
    cd esbuild
    
  3. esbuild実行可能ファイルをビルドします(Windowsではesbuild.exeになります)。
    go build ./cmd/esbuild

他のプラットフォーム用にビルドする場合は、ビルドコマンドにプラットフォーム情報を先頭に追加するだけでかまいません。たとえば、次のコマンドを使用して32ビットLinuxバージョンをビルドできます。

GOOS=linux GOARCH=386 go build ./cmd/esbuild

これが推奨されない理由: ネイティブバージョンは、コマンドラインインターフェース経由でのみ使用でき、複雑なユースケースでは使いにくく、プラグインをサポートしていません。プラグインを使用するには、JavaScriptまたはGoコードを記述し、esbuildのAPIを使用する必要があります。