Bun 快速的原生打包器现在处于 beta 测试阶段。它可以通过 bun build
CLI 命令或新的 Bun.build()
JavaScript API 使用。

使用打包器通过内置的 Bun.build()
函数或 bun build
CLI 命令来构建前端应用程序。
Bun.build({
entrypoints: ['./src/index.tsx'],
outdir: './build',
minify: true,
// additional config
});
bun build ./src/index.tsx --outdir ./build --minify
降低 JavaScript 的复杂性
JavaScript 最初是表单字段的自动填充,而今天它为将火箭发射到太空的仪器提供动力。
毫不奇怪,JavaScript 生态系统的复杂性呈爆炸式增长。如何运行 TypeScript 文件?如何构建/打包你的代码以用于生产环境?该软件包是否与 ESM 兼容?如何加载本地专用配置?我需要安装对等依赖项吗?我如何让 sourcemap 工作?
复杂性会耗费时间,通常花费在粘合工具或等待事情完成上。安装 npm 包花费的时间太长。运行测试应该只需要几秒钟(或更短)。当 2003 年将文件上传到 FTP 服务器只需几毫秒时,为什么 2023 年部署软件却需要几分钟?
多年来,我对 JavaScript 周围的一切都如此缓慢感到沮丧。当从保存文件到测试更改的迭代周期时间变得足够长,以至于本能地查看 Hacker News 时,就说明有问题了。
复杂性是有充分理由的。打包器和压缩器使网站加载速度更快。TypeScript 的编辑器内交互式文档使开发人员更有效率。类型安全有助于在错误发布给用户之前捕获它们。依赖项作为版本控制的软件包通常比复制文件更容易维护。
当“一件事情”被拆分到如此多孤立的工具之间时,“做好一件事”的 Unix 哲学就崩溃了。
这就是我们构建 Bun 的原因,也是为什么今天我们很高兴推出 Bun 打包器的原因。
是的,一个新的打包器
借助新的打包器,打包现在成为 Bun 生态系统的首要元素,完整配备了 bun build
CLI 命令、新的顶级 Bun.build
函数和稳定的插件系统。
我们决定 Bun 需要自己的打包器有几个原因。
内聚性
打包器是编排和启用所有其他工具的元工具,例如 JSX、TypeScript、CSS 模块和服务器组件——所有这些都需要打包器集成才能工作。
如今,打包器是 JavaScript 生态系统中巨大复杂性的来源。通过将打包引入 JavaScript 运行时,我们认为我们可以使交付前端和全栈代码更简单、更快速。
- 快速插件。 插件在轻量级的 Bun 进程中执行,该进程启动速度很快。
- 没有冗余的转译。使用
target: "bun"
,打包器生成针对 Bun 运行时优化的预编译文件,从而提高运行性能并避免不必要的重新转译。 - 统一的插件 API。Bun 提供了一个统一的插件 API,该 API 既适用于打包器也适用于运行时。任何扩展 Bun 打包功能的插件也可以用于扩展 Bun 的运行时功能。
- 运行时集成。构建返回
BuildArtifact
对象数组,这些对象实现了Blob
接口,可以直接传递到 HTTP API 中,例如new Response()
。运行时为BuildArtifact
实现了特殊的漂亮打印。 - 独立可执行文件。打包器可以通过
--compile
标志从 TypeScript 和 JavaScript 脚本生成独立的可执行文件。这些可执行文件完全是独立的,并包含 Bun 运行时的副本。
很快,打包器将与 Bun 的 HTTP 服务器 API (Bun.serve
) 集成,从而可以使用简单的声明式 API 来表示当前复杂的构建管道。稍后会详细介绍。
性能
这一点不会让任何人感到惊讶。作为运行时,Bun 的代码库已经包含了快速解析和转换源代码的基础(用 Zig 实现)。虽然有可能,但很难与现有的原生打包器集成,并且进程间通信中涉及的开销会损害性能。
最终结果不言自明。在 我们的基准测试(源自 esbuild 的 three.js 基准测试)中,Bun 比 esbuild 快 1.75 倍,比 Parcel 2 快 150 倍,比 Rollup + Terser 快 180 倍,比 Webpack 快 220 倍。
开发者体验
查看现有打包器的 API,我们看到了很大的改进空间。没有人喜欢与打包器配置作斗争。Bun 的打包器 API 旨在明确且不令人意外。说到这...
API
API 目前在设计上是最小化的。我们此初始版本的目的是实现一个最小的功能集,该功能集快速、稳定,并且在不牺牲性能的情况下适应大多数现代用例。
以下是 API 的当前存在形式
interface Bun {
build(options: BuildOptions): Promise<BuildOutput>;
}
interface BuildOptions {
entrypoints: string[]; // required
outdir?: string; // default: no write (in-memory only)
target?: "browser" | "bun" | "node"; // "browser"
format?: "esm"; // later: "cjs" | "iife"
splitting?: boolean; // default false
plugins?: BunPlugin[]; // [] // see https://bun.net.cn/docs/bundler/plugins
loader?: { [k in string]: string }; // see https://bun.net.cn/docs/bundler/loaders
external?: string[]; // default []
sourcemap?: "none" | "inline" | "external"; // default "none"
root?: string; // default: computed from entrypoints
publicPath?: string; // e.g. http://mydomain.com/
naming?:
| string // equivalent to naming.entry
| { entry?: string; chunk?: string; asset?: string };
minify?:
| boolean // default false
| { identifiers?: boolean; whitespace?: boolean; syntax?: boolean };
}
其他打包器为了追求功能完整性而做出了糟糕的架构决策,最终削弱了性能;这是我们正在努力避免的错误。
模块系统
目前仅支持 format: "esm"
。我们计划添加对其他模块系统和目标(如 iife
)的支持。如果足够多的人要求,我们也将添加 cjs
输出支持(支持 CommonJS 输入,但不支持输出)。
目标
支持三个“目标”:"browser"
(默认)、"bun"
和 "node"
。
browser
- TypeScript 和 JSX 会自动转译为 vanilla JavaScript。
- 当可用时,模块使用
"browser"
package.json"exports"
条件解析 - 当在浏览器中导入时,Bun 会自动 polyfill 某些 Node.js API,例如
node:crypto
,类似于 Webpack 4 的行为。Bun 自己的 API 目前被禁止导入,但我们将来可能会重新考虑这一点。
bun
- 支持 Bun 和 Node.js API,并保持不变。
- 模块使用 Bun 运行时使用的默认解析算法解析。
- 生成的捆绑包标有特殊的
// @bun
pragma 注释,以指示它们是由 Bun 生成的。这向 Bun 的运行时表明该文件在执行前不需要重新转译。协同作用!
node
目前,这与 target: "bun"
相同。将来,我们计划自动 polyfill Bun API,例如 Bun
全局变量和 bun:*
内置模块。
文件类型
打包器支持以下文件类型
.js
.jsx
.ts
.tsx
- JavaScript 和 TypeScript 文件。废话。.txt
— 纯文本文件。这些文件以内联字符串的形式存在。.json
.toml
— 这些文件在编译时解析并以内联 JSON 的形式存在。
其他所有内容都被视为资产。资产按原样复制到 outdir
中,并且导入被替换为文件的相对路径或 URL,例如 /images/logo.png
。
import logo from "./images/logo.png";
console.log(logo);
var logo = "./images/logo.png";
console.log(logo);
插件
与运行时本身一样,打包器旨在通过插件进行扩展。实际上,运行时插件和打包器插件之间没有任何区别。
import YamlPlugin from "bun-plugin-yaml";
const plugin = YamlPlugin();
// register a runtime plugin
Bun.plugin(plugin);
// register a bundler plugin
Bun.build({
entrypoints: ["./src/index.ts"],
plugins: [plugin],
});
构建输出
Bun.build
函数返回一个 Promise<BuildOutput>
,定义为
interface BuildOutput {
outputs: BuildArtifact[];
success: boolean;
logs: Array<object>; // see docs for details
}
interface BuildArtifact extends Blob {
kind: "entry-point" | "chunk" | "asset" | "sourcemap";
path: string;
loader: Loader;
hash: string | null;
sourcemap: BuildArtifact | null;
}
outputs
数组包含构建生成的所有文件。每个工件都实现了 Blob
接口。
const build = await Bun.build({
/* */
});
for (const output of build.outputs) {
output.size; // file size in bytes
output.type; // MIME type of file
await output.arrayBuffer(); // => ArrayBuffer
await output.text(); // string
}
工件还包含以下附加属性
kind | 此文件是哪种构建输出。构建生成捆绑的入口点、代码拆分的“块”、sourcemap 和复制的资产(如图像)。 |
path | 磁盘上文件的绝对路径,如果文件未写入磁盘,则为输出路径。 |
loader | 用于解释文件的加载器。请参阅 打包器 > 加载器 以了解 Bun 如何将文件扩展名映射到适当的内置加载器。 |
hash | 文件内容的哈希值。始终为资产定义。 |
sourcemap | 如果生成,则为与此文件对应的 sourcemap 的另一个 BuildArtifact 。仅为入口点和块定义。 |
与 BunFile
类似,BuildArtifact
对象可以直接传递到 new Response()
中。
const build = Bun.build({
/* */
});
const artifact = build.outputs[0];
// Content-Type is set automatically
return new Response(artifact);
当将 BuildArtifact
对象记录到日志中时,Bun 运行时实现了特殊的漂亮打印,以使调试更容易。
// build.ts
const build = Bun.build({/* */});
const artifact = build.outputs[0];
console.log(artifact);
bun run build.ts
BuildArtifact (entry-point) {
path: "./index.js",
loader: "tsx",
kind: "entry-point",
hash: "824a039620219640",
Blob (114 bytes) {
type: "text/javascript;charset=utf-8"
},
sourcemap: null
}
服务器组件
Bun 的打包器通过 --server-components
标志对 React 服务器组件提供实验性支持。我们将在本周晚些时候发布更多文档和一个示例项目。
Tree shaking
Bun 的打包器支持 tree-shaking 未使用的代码。在打包时始终启用此功能。
package.json "sideEffects"
字段
Bun 在 package.json
中支持 "sideEffects": false
。这是给打包器的提示,表明该软件包没有副作用,并且可以更积极地消除死代码。
__PURE__
注释
Bun 支持 __PURE__
注释
function foo() {
return 123;
}
/** #__PURE__ */ foo();
由于 foo
是无副作用的,因此会导致一个空文件
在 Webpack 的文档中了解更多信息。
process.env.NODE_ENV
和 --define
Bun 支持 NODE_ENV
环境变量和 --define
CLI 标志。这些通常用于有条件地在生产版本中包含代码。
如果 process.env.NODE_ENV
设置为 "production"
,Bun 将自动删除包装在 if (process.env.NODE_ENV !== "production") { ... }
中的代码。
if (process.env.NODE_ENV !== "production") {
module.exports = require("./cjs/react.development.js");
} else {
module.exports = require("./cjs/react.production.min.js");
}
ES 模块 tree-shaking
ESM tree-shaking 同时支持 ESM 和 CommonJS 输入文件。当安全时,Bun 的打包器将自动从 ESM 文件中删除未使用的导出。
import { foo } from "./foo.js";
console.log(foo);
export const bar = 123;
export const foo = 456;
未使用的 bar
导出被消除,导致
// foo.js
var $foo = 456;
console.log($foo);
CommonJS tree-shaking
在有限的情况下,Bun 的打包器会自动将 CommonJS 转换为 ESM,而运行时开销为零。考虑这个简单的例子
import { foo } from "./foo.js";
console.log(foo);
// foo.js
exports.foo = 123;
exports.bar = "this will be treeshaken";
Bun 将自动将 foo.js
转换为 ESM,并 tree-shake 未使用的 exports
对象。
// foo.js
var $foo = 123;
// entry.js
console.log($foo);
请注意,在许多情况下,CommonJS 的动态性质使这非常困难。例如,考虑以下三个文件
// entry.js
export default require("./foo");
// foo.js
exports.foo = 123;
Object.assign(module.exports, require("./bar"));
// bar.js
exports.foobar = 123;
Bun 无法在不执行 foo.js
的情况下静态确定其导出。(此外,Object.assign
可以被覆盖,这使得在一般情况下静态分析变得不可能。)在这种情况下,Bun 将不会 tree-shake exports
对象;相反,它会注入一些 CommonJS 运行时代码以使其按预期工作。
CommonJS 包装器
Sourcemap
打包器支持内联和外部 sourcemap。
const build = await Bun.build({
entrypoints: ["./src/index.ts"],
// generates a *.js.map file alongside each output
sourcemap: "external",
// adds a base64-encoded `sourceMappingURL` to the end of each output file
sourcemap: "inline",
});
console.log(await build.outputs[0].sourcemap.json()); // => { version: 3, ... }
Minifier
没有 minifier 的 JavaScript 打包器是不完整的。此版本还引入了 Bun 内置的全新 JavaScript minifier。使用 minify: true
启用压缩,或使用以下选项更精细地配置压缩行为
{
minify?: boolean | {
identifiers?: boolean; // default: false
whitespace?: boolean; // default: false
syntax?: boolean; // default: false
}
}
minifier 能够删除死代码、重命名标识符、删除空格以及智能地浓缩和内联常量值。
// This comment will be removed!
console.log("this" + " " + "text" + " will" + " be " + "merged");
console.log("this text will be merged");
抢先看:Bun.App
打包器只是为一项更雄心勃勃的工作奠定基础。在接下来的几个月中,我们将宣布 Bun.App
:一个“超级 API”,它将 Bun 的原生速度打包器、HTTP 服务器和文件系统路由器缝合在一起,形成一个有凝聚力的整体。
目标是使使用几行代码即可轻松表达任何类型的 Bun 应用程序
new Bun.App({
bundlers: [
{
name: "static-server",
outdir: "./out",
},
],
routers: [
{
mode: "static",
dir: "./public",
build: "static-server",
},
],
});
app.serve();
app.build();
const app = new Bun.App({
configs: [
{
name: "simple-http",
target: "bun",
outdir: "./.build/server",
// bundler config...
},
],
routers: [
{
mode: "handler",
handler: "./handler.tsx", // automatically included as entrypoint
prefix: "/api",
build: "simple-http",
},
],
});
app.serve();
app.build();
const projectRoot = process.cwd();
const app = new Bun.App({
configs: [
{
name: "react-ssr",
target: "bun",
outdir: "./.build/server",
// bundler config
},
{
name: "react-client",
target: "browser",
outdir: "./.build/client",
transform: {
exports: {
pick: ["default"],
},
},
},
],
routers: [
{
mode: "handler",
handler: "./handler.tsx",
build: "react-ssr",
style: "nextjs",
dir: projectRoot + "/pages",
},
{
mode: "build",
build: "react-client",
dir: "./pages",
// style: "build",
// dir: projectRoot + "/pages",
prefix: "_pages",
},
],
});
app.serve();
app.build();
此 API 仍在 积极讨论中,并且可能会发生变化。
致谢
- bun 打包器和 minifier 的架构基于 esbuild 的设计,因此感谢 Evan Wallace (evanw)。
- 感谢 @paperclover 将 esbuild 的测试套件移植到 Bun。
- 感谢 @dylan-conway 实现 sourcemap 支持并修复了许多错误。