快速入门

安装 esbuild

首先,下载并本地安装 esbuild。A prebuilt native executable can be installed using npm (which is automatically installed when you install the node JavaScript runtime):

npm install esbuild

此命令应该会将 esbuild 安装到你本地的 node_modules 中。 你可以运行如下命令,来检测 esbuild 的原生可执行文件 是否正常:

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

推荐安装 esbuild 的做法是通过 npm 安装原生可执行文件。 但是如果你不想这么做, 这里也有一些其他的安装方式

首次构建

这是一个简易的真实案例,用于说明 esbuild 的能力以及如何使用它。 首先,请安装 reactreact-dom 的 package:

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

不出意外的话,上述命令执行后会创建一个名为 out.js 的文件, 其中包含你的代码以及 React 库的代码。 代码完全独立,无需再依赖你的 node_modules。 如果你使用 node out.js 运行代码,你应该能看到如下内容:

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

注意,esbuild 除了 jsx 扩展名, 无需任何配置就能够将 JSX 语法转换为 JavaScript。 尽管 esbuild 可以进行配置, 但它试图提供合理的默认值,以便在常见的情况下自动完成构建。 如果你想在 .js 文件中使用 JSX 语法,你可以通过设置 --loader:.js=jsx 选项, 来告诉 esbuild 对 js 文件进行 JSX 的编译。 更多关于可用配置项的说明,请参阅 API 文档

构建脚本

构建命令会反复执行,因此,通常我们会简化它。 常见的做法是在 package.json 中添加构建脚本, 具体代码如下:

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

注意,这里直接使用 esbuild,没用相对路径。 这能够生效的原因是 "scripts" 中执行的内容 会去寻找当前环境下的 esbuild 命令并执行。 (前提是,你已经安装了 esbuild

使用方式如下:

npm run build

但是,如果需要向 esbuild 传递许多选项, 这会使得命令看起来非常笨重。如果将 esbuild 用于较为复杂的情况, 你可能会用到 esbuild 的 JavaScript API, 即在 JavaScript 中编写构建脚本。具体代码如下:

require('esbuild').build({
  entryPoints: ['app.jsx'],
  bundle: true,
  outfile: 'out.js',
}).catch(() => process.exit(1))

build 函数会在子进程中运行 esbuild 的可执行文件,并返回一个 Promise, 当构建完成后,该 Promise 将被 resolve。 上述代码并未打印捕获的异常, 因为异常中的任何错误信息默认会被打印到控制台(你 可以通过修改 日志级别 来按需关闭此功能)。

尽管有个同步的 buildSync API, 但异步 API 对于构建脚本来说更为合适, 因为插件只与异步 API 协同工作。 你可以在 API 文档 中了解更多关于构建 API 的配置项。

针对浏览器环境的构建

构建工具默认为浏览器输出代码, 所以无需额外配置就可以完成构建。 对于开发版本,你可能需要使用 --sourcemap 以启用 source map, 对于生产版本,你可能需要使用 --minify 启用压缩。 有时,你可能还需要为你支持的浏览器配置目标环境。 所以具体命令会像如下所示:

CLI JS Go
esbuild app.jsx --bundle --minify --sourcemap --target=chrome58,firefox57,safari11,edge16
require('esbuild').buildSync({
  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)
  }
}

有时,你使用的包可能会引入另一个只能在 node 上运行的包, 例如 node 内置的 path 包。 当发生这种情况时,你可以通过在 package.json 中使用 browser 字段 来将此包替换成对浏览器友好的包,具体如下:

{
  "browser": {
    "path": "path-browserify"
  }
}

有些你想使用的 npm 包可能并不是为在浏览器中运行设计的。 有时你可以使用 esbuild 的配置项来解决这些问题, 并成功打包。 未定义的全局变量在简单情况下可以用 define 功能代替, 如遇到更复杂的情况,可以用 inject 功能代替。

针对 Node 环境的打包

尽管在使用 Node 时,无需打包,但有时在 Node 代码运行前, 用 esbuild 处理下代码还是有好处的。 通过打包可以自动剥离 TypeScript 的类型, 将 ECMAScript 模块语法转换为 CommonJS 语法, 同时将 JavaScript 语法转换为特定版本 Node 的旧语法。 在包发布前打包也是有好处的, 它可以让包的下载体积更小,从而保证加载时文件系统读取它的时间更少。

如果你要打包运行在 node 环境的代码, 你需要配置 platform 设置,将 --platform=node 传递给 esbuild。 这会将几个配置同时改为对 node 友好的默认值。 例如,所有 node 的内置包,如 fs,都会自动标记为外部(external)包,这样 esbuild 就不会尝试对它们打包。 此设置也会禁用 package.json 中的 browser 字段。

如果你在代码中使用了较新的 JavaScript 语法,而这些语法在你的 node 版本中无法运行, 你会需要配置 node 的目标版本:

CLI JS Go
esbuild app.js --bundle --platform=node --target=node10.4
require('esbuild').buildSync({
  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)
  }
}

You also may not want to bundle your dependencies with esbuild. There are many node-specific features that esbuild doesn't support while bundling such as __dirname, import.meta.url, fs.readFileSync, and *.node native binary modules. You can exclude all of your dependencies from the bundle by marking them as external:

CLI JS Go
esbuild app.jsx --bundle --platform=node --external:./node_modules/*
require('esbuild').buildSync({
  entryPoints: ['app.jsx'],
  bundle: true,
  platform: 'node',
  external: ['./node_modules/*'],
  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,
    External:    []string{"./node_modules/*"},
    Write:       true,
  })

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

If you do this, your dependencies must still be present on the file system at run-time since they are no longer included in the bundle.

Simultaneous platforms

You cannot install esbuild on one OS, copy the node_modules directory to another OS without reinstalling, and then run esbuild on that other OS. This won't work because esbuild is written with native code and needs to install a platform-specific binary executable. Normally this isn't an issue because you typically check your package.json file into version control, not your node_modules directory, and then everyone runs npm install on their local machine after cloning the repository.

However, people sometimes get into this situation by installing esbuild on Windows or macOS and copying their node_modules directory into a Docker image that runs Linux, or by copying their node_modules directory between Windows and WSL environments. The way to get this to work depends on your package manager:

Another alternative is to use the esbuild-wasm package instead, which works the same way on all platforms. But it comes with a heavy performance cost and can sometimes be 10x slower than the esbuild package, so you may also not want to do that.

其他安装方式

推荐安装 esbuild 的方式是使用 npm 安装原生可执行文件。 但你也可以通过如下几种方式安装 esbuild:

安装 WASM 版本

除了 esbuild 的 npm 包,还有一个 esbuild-wasm 包, 这个包与 esbuild 功能相同,但是使用了 WebAssembly 代替原生代码。 安装它也会安装一个叫做 esbuild 的可执行文件:

npm install esbuild-wasm

为什么不推荐这种方式: WebAssembly 的版本比原生的版本慢很多。 在很多情况下会慢一个数量级(10 倍)。 这有很多原因,包括: a) Node 会在每次运行事从头开始编译 WebAssembly 代码, b) Go 的 WebAssembly 编译方式是单线程的, c) Node 的 WebAssembly 存在缺陷,该缺陷会导致延迟数秒退出进程。 WebAssembly 版本还移除了一些功能,如本地文件服务器。 除非别无选择,请不要使用 WebAssembly 版本, 比如当你想要在一个不支持的平台使用 esbuild 时。 WebAssembly 包应该仅使在浏览器里

Use Deno instead of node

There is also basic support for the Deno JavaScript environment if you'd like to use esbuild with that instead. The package is hosted at https://deno.land/x/esbuild and uses the native esbuild executable. The executable will be downloaded and cached from npm at run-time so your computer will need network access to registry.npmjs.org to make use of this package. Using the package looks like this:

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

It has basically the same API as esbuild's npm package with one addition: you need to call stop() when you're done because unlike node, Deno doesn't provide the necessary APIs to allow Deno to exit while esbuild's internal child process is still running.

Why this is not recommended: Deno is newer than node, less widely used, and supports fewer platforms than node, so node is recommended as the primary way to run esbuild. Deno also uses the internet as a package system instead of existing JavaScript package ecosystems, and esbuild is designed around and optimized for npm-style package management. You should still be able to use esbuild with Deno, but you will need a plugin if you would like to be able to bundle HTTP URLs.

从源码构建

从源码构建 esbuild:

  1. 安装 Go 编译器:

    https://go.dev/dl/

  2. 下载源码:
    git clone --depth 1 --branch v0.14.27 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 来使用插件。

下载构建版本

尽管预编译的原生可执行文件托管在 npm 上, 你实际可以不需要安装 npm 也能下载它们。 Npm 包管理器(registry)实际上就是一个普通的 HTTP 服务器,而包就是普通的 tar 压缩文件。

此处是可直接下载二进制可执行文件的示例:

curl -O https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.27.tgztar xf ./esbuild-darwin-64-0.14.27.tgz./package/bin/esbuildUsage:
  esbuild [options] [entry points]

...

esbuild-darwin-64 包里的原生可执行文件可以用在 macOS 操作系统和 x86-64 架构上。 在编写此文章时,我将原生可执行文件包 esbuild 所支持的平台整理成了表格, 完整表格如下:

包名 操作系统 架构 下载地址
esbuild-android-arm64 android arm64
esbuild-darwin-64 darwin x64
esbuild-darwin-arm64 darwin arm64
esbuild-freebsd-64 freebsd x64
esbuild-freebsd-arm64 freebsd arm64
esbuild-linux-32 linux ia32
esbuild-linux-64 linux x64
esbuild-linux-arm linux arm
esbuild-linux-arm64 linux arm64
esbuild-linux-mips64le linux mips64el2
esbuild-linux-ppc64le linux ppc64
esbuild-linux-riscv64 linux riscv642
esbuild-linux-s390x linux s390x
esbuild-netbsd-64 netbsd1 x64
esbuild-openbsd-64 openbsd x64
esbuild-sunos-64 sunos x64
esbuild-windows-32 win32 ia32
esbuild-windows-64 win32 x64
esbuild-windows-arm64 win32 arm64

为什么不推荐这种方式: 这个方式依赖 esbuild 的原生可执行文件安装器的内部实现细节。 这些细节可能会在某些情况改变, 这些情况会导致新版本 esbuild 无法运行。 这是个小缺点, 因为这种方式对现有的 esbuild 版本永久有效, 因为发布到 npm 的包是不可变的。 另一个缺点是你没法使用原生版本的插件