Bun的快速原生捆绑器现在处于测试阶段。它可以通过 bun build CLI 命令或新的 Bun.build() JAVAScript API 使用。
从头开始捆绑 10 份三份.js副本,带有源映射和缩小
使用捆绑器通过内置 Bun.build() 函数或 bun build CLI 命令构建前端应用。
CLI
Bun.build({
entrypoints: ['./src/index.tsx'],
outdir: './build',
minify: true,
// additional config
});
JavaScript 最初是表单字段的自动填充,如今它为将火箭发射到太空的仪器提供动力。
不出所料,JavaScript 生态系统的复杂性呈爆炸式增长。你如何运行打字稿文件?您如何构建/捆绑用于生产的代码?该软件包是否适用于 ESM?如何加载仅限本地的配置?是否需要安装对等依赖项?如何使源映射正常工作?
复杂性需要时间,通常花费在将工具粘合在一起或等待事情完成上。安装 npm 包需要很长时间。运行测试应该需要几秒钟(或更短)。为什么在 2023 年部署软件需要几分钟,而在 2003 年将文件上传到 FTP 服务器需要几毫秒?
多年来,我一直对JavaScript周围的一切缓慢感到沮丧。当从保存文件到测试更改的迭代周期时间变得足够长,以至于本能地检查黑客新闻时,就出了问题。
这种复杂性是有充分理由的。捆绑器和最小化器使网站加载速度更快。TypeScript 的编辑器内交互式文档使开发人员的工作效率更高。类型安全有助于在将错误交付给用户之前捕获错误。作为版本控制包的依赖项通常比复制文件更易于维护。
当“一件事”被拆分为如此多的孤立工具时,“做好一件事”的Unix哲学就崩溃了。
这就是我们构建 Bun 的原因,也是为什么今天我们很高兴推出 Bun 捆绑器。
使用新的捆绑器,捆绑现在是 Bun 生态系统的一流元素,包括 bun build CLI 命令、新的顶级 Bun.build 函数和稳定的插件系统。
我们决定 Bun 需要自己的捆绑器有几个原因。
捆绑器是一种元工具,它编排并支持所有其他工具,如 JSX、TypeScript、css 模块和服务器组件——所有这些都需要捆绑器集成才能工作。
今天,捆绑器是JavaScript生态系统中巨大复杂性的来源。通过将捆绑引入JavaScript运行时,我们认为我们可以使交付前端和全栈代码更简单,更快。
很快,捆绑器将与Bun的HTTP服务器API( Bun.serve )集成,从而可以用简单的声明式API表示当前复杂的构建管道。稍后会详细介绍。
这个不会让任何人感到惊讶。作为一个运行时,Bun的代码库已经包含了快速解析和转换源代码的基础(用Zig实现)。虽然可能,但很难与现有的本机捆绑器集成,并且进程间通信所涉及的开销会损害性能。
最终,结果不言自明。在我们的基准测试(源自 esbuild 的三.js基准测试)中,Bun 比 esbuild 快 1.75 倍,比 Parcel 2 快 150 倍,比 Rollup + Terserr 快 180 倍,比 Webpack 快 220 倍。
查看现有捆绑器的 API,我们看到了很多改进的空间。没有人喜欢与捆绑器配置搏斗。Bun 的捆绑器 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.sh/docs/bundler/plugins
loader?: { [k in string]: string }; // see https://bun.sh/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 otuput 支持(支持 CommonJS 输入,但不支持输出)。
支持三个“目标”: "browser" (默认值)、 "bun" 和 "node" 。
目前,这与 target: "bun" 相同。将来,我们计划自动填充 Bun API,例如 Bun 全局模块和 bun:* 内置模块。
捆绑器支持以下文件类型:
其他一切都被视为资产。资产按原样复制到 outdir 中,导入将替换为文件的相对路径或 URL,例如 /images/logo.png .
Input
Output
import logo from "./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 |
此文件是哪种类型的生成输出。构建会生成捆绑的入口点、代码拆分的“块”、源映射和复制的资产(如图像)。 |
path |
磁盘上文件的绝对路径或输出路径(如果文件未写入磁盘)。 |
loader |
加载程序用于解释文件。请参阅 捆绑程序 > 加载程序 以了解 Bun 如何将文件扩展名映射到相应的内置加载程序。 |
hash |
文件内容的哈希。始终为资产定义。 |
sourcemap |
与此文件对应的源映射的另一个 BuildArtifact (如果生成)。仅为入口点和区块定义。 |
与 BunFile 类似, BuildArtifact 对象可以直接传递到 new Response() 中。
const build = Bun.build({
/* */
});
const artifact = build.outputs[0];
// Content-Type is set automatically
return new Response(artifact);
Bun 运行时在记录 BuildArtifact 对象时实现了特殊的漂亮打印,以便更轻松地进行调试。
Build script
Shell output
// build.ts
const build = Bun.build({/* */});
const artifact = build.outputs[0];
console.log(artifact);
Bun 的捆绑器通过 --server-components 标志对 React Server Components 提供了实验性支持。我们将在本周晚些时候发布其他文档和示例项目。
Bun 的捆绑器支持对未使用的代码进行树摇晃。捆绑时始终启用此功能。
Bun 在 package.json 中支持 "sideEffects": false 。这是对捆绑器的提示,即该包没有副作用,并且可以更积极地消除死代码。
Bun 支持 __PURE__ 注释:
file.js
function foo() {
return 123;
}
/** #__PURE__ */ foo();
由于 foo 没有副作用,这会导致一个空文件:
output.js
在 Webpack 的文档 中了解更多信息。
Bun 支持 NODE_ENV 环境变量和 --define CLI 标志。这些通常用于有条件地在生产版本中包含代码。
如果 process.env.NODE_ENV 设置为 "production" ,Bun 将自动删除包装在 if (process.env.NODE_ENV !== "production") { ... } 中的代码。
node-env.js
if (process.env.NODE_ENV !== "production") {
module.exports = require("./cjs/react.development.js");
} else {
module.exports = require("./cjs/react.production.min.js");
}
ESM 树摇动适用于 ESM 和 CommonJS 输入文件。Bun 的捆绑器会在安全的情况下自动从 ESM 文件中删除未使用的导出。
entry.js
foo.js
import { foo } from "./foo.js";
console.log(foo);
未使用的 bar 导出将被消除,从而导致:
output.js
// foo.js
var $foo = 456;
console.log($foo);
在有限的情况下,Bun 的捆绑器会自动将 CommonJS 转换为 ESM,运行时开销为零。考虑这个微不足道的例子:
index.ts
foo.js
import { foo } from "./foo.js";
console.log(foo);
Bun 会自动将 foo.js 转换为 ESM,并对未使用的 exports 对象进行树摇晃。
Bundled
// foo.js
var $foo = 123;
// entry.js
console.log($foo);
请注意,在许多情况下,CommonJS的动态特性使这变得非常困难。例如,考虑以下三个文件:
// entry.js
export default require("./foo");
Bun 无法在不执行 foo.js 的情况下静态确定它的导出。( Object.assign 也可以被覆盖,这使得静态分析在一般情况下是不可能的。在这种情况下,Bun 不会摇晃 exports 对象;相反,它会注入一些 CommonJS 运行时代码以使其按预期工作。
捆绑器支持内联和外部源映射。
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, ... }
没有缩小器的JavaScript捆绑器是不完整的。此版本还引入了内置于 Bung 中的全新 JavaScript 缩减器。使用 minify: true 启用缩小,或使用以下选项更精细地配置缩小行为:
{
minify?: boolean | {
identifiers?: boolean; // default: false
whitespace?: boolean; // default: false
syntax?: boolean; // default: false
}
}
缩小器能够删除死代码,重命名标识符,删除空格,并智能地压缩和内联常量值。
Input
Minified
// This comment will be removed!
console.log("this" + " " + "text" + " will" + " be " + "merged");
我们更新了以下 React bun create 模板,以便在后台使用 Bun.build 。运行以下命令来搭建由 Bun 捆绑器提供支持的简单 React 项目的基架。
# a React single-page app
bun create react ./myapp
# a Next.js-like app with a /pages directory
# with SSR and client-side hydration
bun create react-ssr ./myapp
捆绑器只是为更雄心勃勃的努力奠定了基础。在接下来的几个月里,我们将宣布 Bun.App :一个“超级 API”,它将 Bung 的原生速度捆绑器、HTTP 服务器和文件系统路由器拼接成一个有凝聚力的整体。
目标是让使用Bun轻松表达任何类型的应用程序,只需几行代码:
Static file server
API server
Next.js-style framework
new Bun.App({
bundlers: [
{
name: "static-server",
outdir: "./out",
},
],
routers: [
{
mode: "static",
dir: "./public",
build: "static-server",
},
],
});
app.serve();
app.build();
此 API 仍在积极讨论中,可能会发生变化。