常见问题

这是关于esbuild的常见问题集合。你也可以在 GitHub问题跟踪器上提问。

为什么esbuild这么快?

有几个原因:

这些因素中的每一个都只是一个相当显著的加速,但 一起,它们可以产生一个比当今常用的其他打包工具快几个数量级的打包工具。

基准测试详情

以下是每个基准测试的详细信息:

JavaScript基准测试
esbuild
0.39s
parcel 2
14.91s
rollup 4 + terser
34.10s
webpack 5
41.21s
0s
10s
20s
30s
40s

这个基准测试通过复制three.js库10次 并从头开始构建一个单一的包(没有任何缓存)来近似一个大型JavaScript代码库。 可以在esbuild仓库中使用make bench-three运行此基准测试。

Bundler Time Relative slowdown Absolute speed Output size
esbuild 0.39s 1x 1403.7 kloc/s 5.80mb
parcel 2 14.91s 38x 36.7 kloc/s 5.78mb
rollup 4 + terser 34.10s 87x 16.1 kloc/s 5.82mb
webpack 5 41.21s 106x 13.3 kloc/s 5.84mb

报告的每个时间是三次运行中的最佳结果。我运行esbuild时使用 --bundle --minify --sourcemap。我使用了 @rollup/plugin-terser 插件,因为Rollup本身不支持压缩。Webpack 5使用 --mode=production --devtool=sourcemap。 Parcel 2使用默认选项。绝对速度基于总行数,包括注释和空行,目前为547,441行。 测试是在一台6核2019 MacBook Pro上进行的,配备16GB RAM,并禁用了 macOS Spotlight

TypeScript基准测试
esbuild
0.10s
parcel 2
6.91s
webpack 5
16.69s
0s
5s
10s
15s

这个基准测试使用旧的Rome代码库 (在他们的Rust重写之前)来近似一个大型TypeScript代码库。所有代码 必须合并成一个带有源映射的压缩包,并且生成的包必须正确工作。 可以在esbuild仓库中使用 make bench-rome运行此基准测试。

Bundler Time Relative slowdown Absolute speed Output size
esbuild 0.10s 1x 1318.4 kloc/s 0.97mb
parcel 2 6.91ѕ 69x 16.1 kloc/s 0.96mb
webpack 5 16.69ѕ 167x 8.3 kloc/s 1.27mb

报告的每个时间是三次运行中的最佳结果。我运行esbuild时使用 --bundle --minify --sourcemap --platform=node。 Webpack 5使用ts-loader,配置为transpileOnly: true--mode=production --devtool=sourcemap。Parcel 2使用 "engines": "node"package.json中。绝对 速度基于总行数,包括注释和空行,目前为131,836行。测试是在一台6核2019 MacBook Pro上 进行的,配备16GB RAM,并禁用了macOS Spotlight

结果不包括Rollup,因为我无法让它工作,原因与TypeScript编译有关。 我尝试了@rollup/plugin-typescript, 但你不能禁用类型检查,我也尝试了 @rollup/plugin-sucrase, 但没有办法提供tsconfig.json文件(这对于正确的路径解析是必需的)。

即将到来的路线图

这些功能已经处于开发中,并且是优先级最高的:

这些是潜在的未来功能,但可能不会发生或可能只会以更有限的方式发生:

在那之后,我会认为esbuild相对完整。 我计划让esbuild达到大部分稳定的状态,然后停止积累更多的功能。 这将涉及对esbuild本身添加主要功能的请求说"不"。 我不认为esbuild应该成为一个所有前端需求的"一站式"解决方案。 特别是,我不想避免"webpack config"模型带来的痛苦和问题, 其中底层工具过于灵活,可用性受到影响。

例如,我_不_计划在esbuild的核心中包含这些功能:

我希望我正在为esbuild添加的可扩展性点 (插件API)将使esbuild对更多定制构建工作流有用, 但我不是有意或期望这些可扩展性点来覆盖所有用例。如果你有非常特殊的需要,你应该使用其他工具。 我也希望esbuild能激励其他构建工具 通过重写其实现来显著提高性能,以便每个人都能受益,而不仅仅是使用esbuild的人。

我计划继续维护esbuild的所有现有范围,即使esbuild达到稳定状态。 这意味着为新发布的JavaScript和TypeScript语法功能实现支持,例如。

生产准备

这个项目还没有达到1.0.0版本,仍在积极开发中。 也就是说,它远远超出了alpha阶段,并且相当稳定。 我认为它是一个后期阶段的beta。对于一些早期采用者来说,这意味着它足够好,可以用于真实的事情。 有些人认为这意味着esbuild还没有准备好。 本节不试图说服你任何一方。它只是试图给你足够的信息,以便你自己决定是否要将esbuild作为你的打包工具。

一些数据点:

反病毒软件

由于esbuild是用原生代码编写的,反病毒软件有时会错误地将其标记为病毒。 这并不意味着esbuild是病毒。 我不发布恶意代码,并且我非常重视供应链安全。

实际上,esbuild的所有代码都是第一方代码,除了对Google的补充Go包集的 一个依赖。 我的开发工作是在与发布构建时使用的机器隔离的不同机器上完成的。 我已经做了额外的工作,以确保esbuild的已发布构建是完全可重现的, 并且在每次发布后,已发布的构建会自动与在无关环境中本地构建的构建进行比较, 以确保它们在位级别上完全相同(即Go编译器本身没有被破坏)。 你也可以自己从源代码构建esbuild,并将你的构建产物与已发布的构建产物进行比较, 以独立验证这一点。

不得不处理误报是使用反病毒软件的一个不幸现实。 如果你的反病毒软件不让你使用esbuild,以下是一些可能的解决方法:

过时的Go版本

如果你使用自动化依赖漏洞扫描器,你可能会收到报告, 指出esbuild使用的Go编译器版本和/或golang.org/x/sys (esbuild的唯一依赖)的版本已过时。 这些报告是良性的,应该被忽略。

这是因为esbuild的代码有意设计为可以用Go 1.13编译。 Go的更新版本已经放弃了对某些我希望esbuild能够运行的旧平台的支持 (例如,旧版本的macOS)。虽然esbuild的已发布二进制文件是用更新版本的Go编译的 (因此不适用于旧版本的macOS),但你目前仍然可以使用Go 1.13自己编译 最新版本的esbuild,并在旧版本的macOS上使用它,因为esbuild的代码 仍然可以用Go 1.13编译。

人们和/或自动化工具有时会看到go.mod中的go 1.13行, 并抱怨esbuild的已发布二进制文件是用Go 1.13构建的,这是一个非常旧的Go版本。 然而,这不是真的。go.mod中的那一行只指定了最低编译器版本。 它与esbuild的已发布二进制文件是用哪个版本的Go构建的无关, 后者是一个更新的Go版本。请阅读文档。

人们有时也希望esbuild更新golang.org/x/sys依赖, 因为esbuild使用的版本中存在已知漏洞 (具体是关于Faccessat函数的GO-2022-0493)。 阻止esbuild更新到更新版本的golang.org/x/sys依赖的问题是, 更新版本已经开始使用unsafe.Slice函数,该函数是在Go 1.17中首次引入的 (因此在旧版本的Go中无法编译)。然而,这个漏洞报告无关紧要,因为 a)esbuild从不调用那个函数,b)esbuild是一个构建工具,而不是沙盒, esbuild的文件系统访问对安全性不敏感。

我不会放弃与旧平台的兼容性,阻止一些人使用esbuild, 只是为了解决无关的漏洞报告。请忽略关于上述问题的任何报告。

压缩后的换行符

人们有时会惊讶地发现,esbuild的压缩器通常会将JavaScript字符串中的字符转义序列\n 更改为模板字面量中的换行符。但这是有意的。 这不是esbuild的错误。压缩器的工作是生成尽可能紧凑且与输入等效的输出。 字符转义序列\n长度为两个字节,而换行符长度为一个字节。

例如,这段代码长度为21字节:

var text="a\nb\nc\n";

而这段代码长度为18字节:

var text=`a
b
c
`;

所以第二段代码是完全压缩的,而第一段不是。压缩代码并不意味着将所有内容放在一行上。 相反,压缩代码意味着生成等效的代码,但使用尽可能少的字节。 在JavaScript中,未标记的模板字面量等同于字符串字面量, 所以esbuild在这里做的是正确的事情。

避免名称冲突

当在浏览器中运行esbuild的输出时,入口点模块中的顶级变量永远不应该出现在全局作用域中。 如果发生这种情况,这意味着你没有遵循esbuild关于输出格式的文档, 并且使用esbuild不正确。这不是esbuild的错误。

具体来说,在浏览器中运行esbuild的输出时,你必须执行以下操作之一:

  1. --format=iife<script src="...">

    如果你在全局作用域中运行代码,那么你应该使用--format=iife。 这会导致esbuild的输出将你的代码包装起来,使顶级变量在嵌套作用域中声明。

  2. --format=esm<script src="..." type="module">

    如果你使用--format=esm,那么你必须将代码作为模块运行。 这会导致浏览器将你的代码包装起来,使顶级变量在嵌套作用域中声明。

使用--format=esm<script src="..."> 将以微妙而令人困惑的方式破坏你的代码(省略type="module" 意味着所有顶级变量都将进入全局作用域,然后与其他JavaScript文件中具有相同名称的顶级变量发生冲突)。

顶级var

人们有时会惊讶地发现,esbuild有时会将顶级的letconstclass声明重写为var声明。 这样做有几个原因:

请注意,esbuild不保留顶级TDZ副作用,因为模块可能需要延迟初始化(如上所述), 这意味着将声明与初始化分离。顶级符号的TDZ检查理论上仍然可以通过生成额外的代码来支持, 该代码在每次使用顶级符号之前进行检查,如果该符号尚未初始化则抛出错误 (有效地手动实现真正的JavaScript VM会做的事情)。 然而,这对代码大小和运行时间来说似乎是一个过度的开销, 并且似乎不是一个面向生产的打包工具应该做的事情。