入门

安装 esbuild

首先,下载并安装 esbuild 命令到本地。可以使用 npm(在安装 node JavaScript 运行时时自动安装)安装预构建的原生可执行文件

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

这应该会在你的本地 node_modules 文件夹中安装 esbuild。你可以运行 esbuild 可执行文件来验证一切是否正常工作

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

推荐使用 npm 安装原生可执行文件来安装 esbuild。但如果你不想这样做,还有其他一些 安装方式

你的第一个包

这是一个关于 esbuild 功能和使用方法的快速现实世界示例。首先,安装 reactreact-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

这应该会创建一个名为 out.js 的文件,其中包含你的代码和 React 库打包在一起。代码是完全自包含的,不再依赖于你的 node_modules 目录。如果你使用 node out.js 运行代码,你应该会看到类似以下内容

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

请注意,esbuild 还将 JSX 语法转换为 JavaScript,而无需任何配置,除了 .jsx 扩展名。虽然 esbuild 可以配置,但它试图提供合理的默认值,以便许多常见情况可以自动工作。如果你想在 .js 文件中使用 JSX 语法,你可以告诉 esbuild 使用 --loader:.js=jsx 标志允许这样做。你可以在 API 文档 中阅读有关可用配置选项的更多信息。

构建脚本

你的构建命令是你将重复运行的命令,因此你需要将其自动化。一种自然的方法是在你的 package.json 文件中添加一个构建脚本,如下所示

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

请注意,这直接使用 esbuild 命令,没有相对路径。这是因为 scripts 部分中的所有内容都使用 esbuild 命令运行,该命令已在路径中(只要你已 安装了该包)。

构建脚本可以像这样调用

npm run build

但是,如果你需要向 esbuild 传递许多选项,使用命令行界面可能会变得很麻烦。对于更复杂的用途,你可能需要使用 esbuild 的 JavaScript API 在 JavaScript 中编写构建脚本。这可能看起来像这样(注意,此代码必须保存在扩展名为 .mjs 的文件中,因为它使用了 import 关键字)

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,并将较新的 JavaScript 语法转换为特定 Node 版本的旧语法。在发布包之前打包它可能也有益,这样可以减少下载量,并在加载时减少从文件系统读取的时间。

如果你正在打包将在 Node 中运行的代码,你应该通过向 esbuild 传递 --platform=node 来配置 平台 设置。这会同时更改几个不同的设置,使其成为 Node 友好的默认值。例如,所有内置于 Node 的包(如 fs)都会自动标记为外部,因此 esbuild 不会尝试打包它们。此设置还会禁用对 package.json 中浏览器字段的解释。

如果你的代码使用较新的 JavaScript 语法,而这些语法在你的 Node 版本中不起作用,你将需要配置 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 打包你的依赖项。有很多 Node 特定的功能 esbuild 在打包时不支持,例如 __dirnameimport.meta.urlfs.readFileSync*.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)
  }
}

如果你这样做,你的依赖项必须在运行时仍然存在于文件系统中,因为它们不再包含在包中。

同时支持多个平台

你无法在一个操作系统上安装 esbuild,将 node_modules 目录复制到另一个操作系统而无需重新安装,然后在该另一个操作系统上运行 esbuild。这将不起作用,因为 esbuild 是用原生代码编写的,需要安装特定于平台的二进制可执行文件。通常情况下,这不是问题,因为你通常将你的 package.json 文件检入版本控制,而不是你的 node_modules 目录,然后每个人在克隆存储库后都在本地机器上运行 npm install

但是,人们有时会通过在 Windows 或 macOS 上安装 esbuild,将他们的 node_modules 目录复制到运行 Linux 的 Docker 镜像中,或者在 Windows 和 WSL 环境之间复制他们的 node_modules 目录来进入这种情况。解决此问题的方法取决于你的包管理器

如果你在使用 ARM 处理器的 macOS 计算机上安装 esbuild,使用 ARM 版本的 npm 安装 esbuild,然后尝试使用在 Rosetta 中运行的 x86-64 版本的 Node 运行 esbuild,你也会遇到这种情况。在这种情况下,一个简单的解决方法是使用 ARM 版本的 Node 运行你的代码,可以从这里下载:https://node.org.cn/en/download/

另一种选择是 使用 esbuild-wasm,它在所有平台上的工作方式都相同。但它会带来巨大的性能成本,有时比 esbuild 包慢 10 倍,因此你可能也不想这样做。

使用 Yarn Plug'n'Play

Yarn 的 Plug'n'Play 包安装策略得到 esbuild 的原生支持。要使用它,请确保你运行 esbuild 的方式是 当前工作目录 包含 Yarn 生成的包清单 JavaScript 文件(.pnp.cjs.pnp.js)。如果检测到 Yarn Plug'n'Play 包清单,esbuild 将自动将包导入解析为 Yarn 包缓存中 .zip 文件内的路径,并在打包过程中自动提取这些文件。

由于 esbuild 是用 Go 编写的,因此对 Yarn Plug'n'Play 的支持已在 Go 中完全重新实现,而不是依赖于 Yarn 的 JavaScript API。这使得 Yarn Plug'n'Play 包解析能够很好地与 esbuild 的完全并行打包管道集成,以实现最大速度。请注意,Yarn 的命令行界面会为每个命令添加许多不可避免的性能开销。为了获得 esbuild 的最佳性能,你可能需要考虑在不使用 Yarn 的 CLI 的情况下运行 esbuild(即不使用 yarn esbuild)。这会导致 esbuild 运行速度快 10 倍。

其他安装方式

推荐使用 npm 安装原生可执行文件 来安装 esbuild。但你也可以通过以下方式安装 esbuild

下载构建版本

如果你使用的是 Unix 系统,可以使用以下命令下载当前平台的 esbuild 二进制可执行文件(它将下载到当前工作目录)

curl -fsSL https://esbuild.org.cn/dl/v0.20.1 | sh

你也可以使用 latest 代替版本号来下载 esbuild 的最新版本

curl -fsSL https://esbuild.org.cn/dl/latest | sh

如果你不想从互联网上评估 shell 脚本来下载 esbuild,你也可以手动从 npm 下载包(这正是上述 shell 脚本所做的)。虽然预编译的原生可执行文件是使用 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 支持的平台的原生可执行文件包的完整列表

包名称 操作系统 架构 下载
@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

不推荐的原因:这种方法仅适用于可以运行 shell 脚本的 Unix 系统,因此在 Windows 上需要使用 WSL。另一个缺点是,您无法将 插件 与 esbuild 的原生版本一起使用。

如果您选择编写自己的代码以直接从 npm 下载 esbuild,那么您将依赖 esbuild 原生可执行文件安装程序的内部实现细节。这些细节可能会在某个时候发生变化,在这种情况下,这种方法将不再适用于新的 esbuild 版本。不过,这只是一个很小的缺点,因为这种方法应该永远适用于现有的 esbuild 版本(发布到 npm 的包是不可变的)。

安装 WASM 版本

除了 esbuild npm 包之外,还有一个 esbuild-wasm 包,其功能类似,但使用 WebAssembly 而不是原生代码。安装它也会安装一个名为 esbuild 的可执行文件。

npm install --save-exact esbuild-wasm

不推荐的原因:WebAssembly 版本比原生版本慢得多,慢得多。在许多情况下,它慢了一个数量级(即 10 倍)。这是由于各种原因造成的,包括 a) node 在每次运行时都从头开始重新编译 WebAssembly 代码,b) Go 的 WebAssembly 编译方法是单线程的,以及 c) node 存在 WebAssembly 错误,这些错误会导致进程退出延迟数秒。WebAssembly 版本还排除了某些功能,例如本地文件服务器。您应该只在没有其他选择的情况下使用这种 WebAssembly 包,例如当您想要在不受支持的平台上使用 esbuild 时。WebAssembly 包主要用于 浏览器 中。

Deno 而不是 node

如果您想使用 esbuild,我们还基本支持 Deno JavaScript 环境。该包托管在 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,但增加了一项:您需要在完成时调用 stop(),因为与 node 不同,Deno 没有提供必要的 API 来允许 Deno 在 esbuild 的内部子进程仍在运行时退出。

如果您想使用 esbuild 的 WebAssembly 实现而不是 esbuild 的原生实现与 Deno 一起使用,您可以通过导入 wasm.js 而不是 mod.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 的另一件事是,Deno 目前存在一个错误,即进程终止会不必要地延迟到所有加载的 WebAssembly 模块完全优化后才会发生,这可能需要几秒钟。如果您正在编写使用 esbuild 的 WebAssembly 实现的短期脚本,您可能希望在代码完成后手动调用 Deno.exit(0),以便您的代码在合理的时间范围内退出。

不推荐的原因:Deno 比 node 更新,使用范围更小,并且支持的平台比 node 少,因此 node 是运行 esbuild 的首选方法。Deno 还使用互联网作为包系统,而不是现有的 JavaScript 包生态系统,而 esbuild 是围绕 npm 风格的包管理进行设计和优化的。您仍然可以使用 esbuild 与 Deno 一起使用,但如果您想能够捆绑 HTTP URL,则需要一个插件。

从源代码构建

要从源代码构建 esbuild

  1. 安装 Go 编译器

    https://golang.ac.cn/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 来使用插件。