9 月 8 日,JavaScript 社区一则消息引起热议: 由 Jarred Sumner 创建的 Bun v1.0 正式发布。但在热议之余,许多人不禁要问:Bun 的本质是什么?为什么大家都把它与比较成熟的 Node.js 相提并论?Bun 只是另一种稍纵即逝的趋势,还是它将重新定义游戏规则?在本文中,让我们深入了解 Bun,看看它的功能,并了解它与成熟的 Node.js 相比有何不同。
什么是 Bun
Bun 是一款适用于 JavaScript 和 TypeScript 应用程序的超快速一体化工具包。Bun 的魅力在于它能简化开发流程,使其比以往更加顺畅高效。之所以能做到这一点,是因为 Bun 不仅是一个运行时,还是一个包管理器、一个 bundler 和一个测试运行器。想象一下,在开发 JavaScript 时有一把瑞士军刀,这就是 Bun。
Bun 解决了什么问题
2009 年 Node.js 的诞生具有划时代的意义。然而,与许多技术一样,随着它的发展,其复杂性也在增加。把它想象成一座城市。随着城市的扩张,交通拥堵可能会成为一个问题。
Bun 的目标是成为缓解交通拥堵的新基础设施,让一切运行得更顺畅、更快速。这不是要重新发明轮子,而是要对其进行改进,确保我们在获得速度和简洁性的同时,不会失去 JavaScript 独特和强大的本质。
Bun 被设计为 Node.js 更快、更精简、更现代的替代品,因此让我们来仔细比较一下。不过,首先让我们讨论另一个话题。
Node.js VS Deno VS Bun
在讨论 JavaScript 运行时的演变时,很难忽略 Deno。Node.js 的创建者 Ryan Dahl 将 Deno 作为一种新的运行时推出,旨在解决他在 Node.js 中发现的一些挑战和遗憾。
Deno 是 JavaScript 和 TypeScript 的安全运行时。它直接解决了 Node.js 的许多缺点。例如,Deno 本机支持 TypeScript,无需外部工具。与脚本默认具有广泛权限的 Node.js 不同,Deno 采用了安全优先的方法,要求开发人员为文件系统访问或网络连接等潜在敏感操作明确授予权限。
虽然 Deno 为 Node.js 提供了一个令人信服的替代方案,但它还没有达到 Node.js 被广泛采用的程度。因此,本文将重点对比 Bun 和成熟的 Node.js。
Getting statted
可以通过 curl -fsSL https://bun.sh/install | bash
安装 Bun,使用 bun init -y
命令为一个空项目搭建脚手架。我们生成了几个文件,在 index.ts 中添加一行 console.log("Hello,Bun!")
。在终端运行 bun index.ts 命令,就能看到 "Hello, Bun!"
的输出。
Bun VS Node:JavaScript 运行时
JavaScript 运行时是为使用和运行 JavaScript 程序提供所有必要组件的环境。
Node.js 和 Bun 都是运行时。Node.js 主要用 C++ 编写,而 Bun 则用一种名为 Zig
的低级通用编程语言编写。但这只是冰山一角。让我们仔细看看 Bun 作为运行时的其他不同之处。
JavaScript 引擎
JavaScript 引擎是一种程序,它能将我们编写的 JavaScript 代码转换成机器代码,让计算机执行特定任务。
Node.js 使用谷歌为 Chrome 浏览器提供支持的 V8 引擎,而 Bun 则使用 JavaScriptCore (JSC),这是苹果公司为 Safari 开发的开源 JavaScript 引擎。
V8 和 JSC 有着不同的架构和优化策略。JSC 优先考虑的是更快的启动时间和更少的内存使用,执行时间稍慢。另一方面,V8 优先考虑快速执行,同时进行更多运行时优化,这可能会导致更多内存使用。
这使得 Bun 的速度很快,启动速度比 Node.js 快达 3-4 倍。
从上面基准测试可以看出 Bun 是比 Node 和 Deno 都要快的。
Transpiler
虽然 Node.js 是 JavaScript 的强大运行时,但它并不原生支持 TypeScript 文件。要在 Node.js 环境中执行 TypeScript,需要外部依赖。一种常见的方法是使用构建步骤将 TypeScript (TS) 转换为 JavaScript (JS),然后运行生成的 JS 代码。下面是一个使用 ts-node 软件包的基本设置:
1. 安装
npm install -D typescript ts-node
2. 配置脚本命令
可以在 package.json
中设置脚本,以简化流程:
{
"scripts": {
"start": "ts-node ./path/to/your/file.ts"
}
}
3. 运行
有了上面的脚本你可以轻松的执行 TS 文件:
npm start
相比之下,Bun 提供了一种更精简的方法。它的运行时集成了 JavaScript 转换器。这样,你就可以直接运行 .js、.ts、.jsx 和 .tsx
文件。Bun 内置的转换器能将这些文件无缝转换为普通 JavaScript,无需额外步骤即可立即执行。
bun index.ts
在运行 TypeScript 文件时,速度上的差异会被放大,因为 Node.js 在运行前需要一个转译步骤。
ESM 与 CommonJS 的兼容性
模块系统允许开发人员将代码组织成可重复使用的片段。在 JavaScript 中,两个主要的模块系统是 CommonJS 和 ES 模块(ESM)。CommonJS 源自 Node.js,使用 require 和 module.exports 进行同步模块处理,非常适合服务器端操作。
ES6 中引入的 ESM 使用 import 和 export 语句,提供了一种更静态和异步的方法,并针对浏览器和现代构建工具进行了优化。让我们使用 CommonJS 的颜色和 ESM 的粉笔颜色,这两个流行的软件包可以在控制台中添加彩色输出,让我们更好地了解模块系统。
Node.js 传统上与 CommonJS 模块系统相关联。下面是一个典型的用法:
// CommonJS in Node.js (index.js)
const colors = require("colors");
console.log(colors.green('Hello, world!'));
对于 Node.js 中的 ES 模块,有两种选择:
package.json
中包含 "type: "module"
。.mjs
扩展名。// ESM in Node.js (index.mjs)
import chalk from 'chalk';
console.log(chalk.blue('Hello, world!'));
从 CommonJS 到 ES 模块(ESM)的过渡是一个复杂的过程。ESM 推出后,Node.js 花了半年时间才在不使用实验标志的情况下支持它。尽管如此,CommonJS 仍在生态系统中占据主导地位。
Bun 简化了模块系统,无需任何特殊配置即可同时支持两种模块。Bun 的突出特点是能够在同一个文件中同时支持 import 和 require(),这在 Node.js 中是无法实现的:
// Mixed modules in Bun (index.js)
import chalk from "chalk";
const colors = require("colors");
console.log(chalk.magenta('Hello from chalk!'));
console.log(colors.cyan('Hello from colors!'));
Web APIs
Web API 是基于浏览器的应用程序不可或缺的一部分,为网络交互提供了 fetch 和 WebSocket 等工具。虽然这些已成为浏览器标准,但 Node.js 等服务器端环境对它们的支持却不一致。
在 Node.js 的早期版本中,浏览器中常见的网络标准 API 并不支持本机。开发人员不得不依赖 node-fetch
等第三方软件包来复制这些功能。不过,从 Node.js v18 开始,对 fetch API 的实验性支持可能会消除对这些软件包的需求。
Bun 为这些 Web 标准 API 提供内置支持,从而简化了这一过程。开发人员可以直接使用稳定的 fetch、Request、Response、WebSocket
和其他类似浏览器的 API,而无需额外的软件包。此外,Bun 对这些 Web API 的原生实现确保了它们比第三方替代品更快、更可靠。
下面是一个同时兼容 Node.js(v18 及以上版本)和 Bun 的示例。虽然在 Node.js 中是实验性的,但同样的功能在 Bun 中是稳定的:
// Experiment fetch in Node.js (v18 and above) and built-in fetch in Bun
async function fetchUserData() {
const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
const user = await response.json();
console.log(user.name);
}
fetchUserData();
热加载
热重载是一项提高开发人员工作效率的功能,它能在代码发生变化时实时自动刷新或重载应用程序的某些部分,而无需完全重启。
在 Node.js 生态系统中,有几种实现热重载的方法。其中一个流行的工具是 nodemon,它可以硬重启整个过程:
nodemon index.js
另外,从 Node.js v18 开始,还引入了一个试验性的 --watch:
node --watch index.js
这两种方法的目的都是在代码发生变化时实时重新加载应用程序。不过,它们可能会有不同的行为,尤其是在某些环境或场景中。
例如,nodemon 可能会导致中断 HTTP 和 WebSocket 连接,而 --watch 作为实验性标记,可能无法提供全部功能。
Bun 在热重载方面更进一步。使用 --hot 标志运行 Bun,就能启用热重载:
bun --hot index.ts
与可能需要重启整个进程的 Node.js 方法不同,Bun 会在不终止旧进程的情况下就地重新加载代码。这可确保 HTTP 和 WebSocket 连接不中断,并保留应用程序状态,从而提供更流畅的开发体验。
Node.js 兼容性
在过渡到新的运行时或环境时,兼容性往往是开发人员最关心的问题。Bun 将自己定位为 Node.js 的直接替代品,从而解决了这个问题。这意味着现有的 Node.js 应用程序和 npm 软件包可以与 Bun 无缝集成,无需做任何修改。确保这种兼容性的主要功能包括
- 支持内置的 Node.js 模块,如 fs、path 和 net。
- 识别 __dirname 和 process 等全局变量。
- 遵循 Node.js 模块解析算法,包括熟悉的 node_modules 结构。
Bun 仍在不断发展。它专为增强开发工作流而定制,非常适合资源有限的环境,例如无服务器功能。Bun 背后的团队正在努力实现与 Node.js 的全面兼容以及与主流框架的更好集成
Bun 在确保与 Node.js 兼容的同时,并没有止步于此。Bun 为开发人员最需要的东西提供了高度优化的标准库 API。
Bun APIs
Bun.file()
通过懒加载文件并以各种格式访问其内容。这种方法比 Node.js 的对应方法快 10 倍。
// Bun (index.ts)
const file = Bun.file("package.json");
await file.text();
// Node.js (index.mjs)
const fs = require("fs/promises");
const fileContents = await fs.readFile("package.json", "utf-8");
Bun.write()
从字符串到 Blob,向磁盘写入数据的通用 API。它的写入速度是 Node.js 的 3 倍。
// Bun (index.ts)
await Bun.write("index.html", "");
// Node.js (index.mjs)
const fs = require("fs/promises");
await fs.writeFile("index.html", "");
Bun.serve()
使用 Web 标准 API 设置 HTTP 服务器或 WebSocket 服务器。与 Node.js 中的 ws
软件包相比,它每秒可处理的请求数量是 Node.js 的 4 倍,处理的 WebSocket 消息数量是 Node.js 的 5 倍。这种后端功能让人想起开发人员在 Node.js 中使用 Express 的方式,但又增加了 Bun 性能优化的优势。
// Bun (index.ts)
Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello from Bun!");
},
});
// Node.js (index.mjs)
import http from "http";
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello from Node.js!");
});
server.listen(3000);
Bun 还支持 sqlite 和内置密码。
Bun VS Node.js: 包管理
Bun 不仅仅是一个运行时,它还是一个高级工具包,其中包括一个功能强大的软件包管理器。如果你曾发现自己在依赖安装过程中耐心等待,那么 Bun 提供了一种令人耳目一新的快速替代方案。即使你不把 Bun 用作运行时,它内置的软件包管理器也能加快你的开发工作流程。
下图将 Bun 命令与 Node 的软件包管理器 npm 进行了比较:
Bun 的安装速度比 npm 快了好几个数量级。它利用全局模块缓存消除了从 npm 注册表的多余下载,从而实现了快速安装。此外,Bun 还为每个操作系统采用了最快的系统调用,以确保最佳性能。
下面是 Bun 和 npm 从缓存为 Remix 初始项目安装依赖项的速度对比:
bun CLI 包含一个与 Node.js 兼容的软件包管理器,旨在以更快的速度取代 npm、yarn 和 pnpm。
此外,bun 运行 仅需 7 毫秒,而 npm 运行 则需要 176 毫秒。多年来,Node.js 的 npm 一直是 JavaScript 软件包管理的标准,而 Bun 确实是速度上遥遥领先
,可以把玩一下。
Bun VS Node.js: bundler
打包是将多个 JavaScript 文件合并为一个或多个优化包的过程。这一过程还可能涉及转换,例如将 TypeScript 转换为 JavaScript 或对代码进行最小化以减小其大小。
在 Node.js 生态系统中,打包通常由第三方工具而非 Node.js 本身处理。Node.js 世界中最流行的 打包工具包括 Webpack、Rollup 和 Parcel,它们具有代码拆分、tree shaking 和热加在换等功能。
另一方面,Bun 不仅是一个运行时和软件包管理器,它本身也是一个打包程序。它旨在为各种平台打包 JavaScript 和 TypeScript 代码,包括浏览器中的前端应用程序(React 或 Next.js 应用程序)和 Node.js。
要打包 Bun,只需使用一个简单的命令即可:
bun build ./index.ts --outdir ./build
该命令打包 index.ts 文件,并在 ./build 目录中输出结果。打包过程 遥遥领先
,Bun 的速度是 esbuild 的 1.75 倍,大大超过 Parcel 和 Webpack 等其他打包程序。
Bun 的一个突出特点是引入了 JavaScript 宏。这些宏允许在打包过程中执行 JavaScript 函数,并将结果直接内联到最终的包中。这种机制为打包提供了一个全新的视角。
请看这个例子,在打包过程中,Bun 的 JavaScript 宏被用来获取用户名。宏不是在运行时调用 API,而是在打包时获取数据,并将结果直接内联到最终输出中:
// users.ts
export async function getUsername() {
const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
const user = await response.json();
return user.name;
}
// index.ts
import { getUsername } from "./users.ts" with { type: "macro" };
const username = await getUsername();
// build/index.js
var user = await "掘金";
console.log(user);
虽然 Node.js 有其成熟的打包工具,但 Bun 提供了一个集成、更快和创新的替代方案,可以重塑打包格局。
Bun VS Node.js: test runner
测试是软件开发的一个重要环节,它能确保代码按照预期运行,并在生产前发现潜在问题。除了运行时、软件包管理器和捆绑程序之外,Bun 还是一个测试运行器。
传统上,Node.js 开发人员一直依赖 Jest 来满足他们的测试需求,而 Bun 则引入了一个内置测试运行器,承诺提供速度、兼容性和一系列满足现代开发工作流的功能。
Bun 的测试运行器 bun:test
设计为与 Jest 完全兼容,Jest 是一个以 "期望 "式 API 而闻名的测试框架。这种兼容性确保了熟悉 Jest 的开发人员可以轻松过渡到 Bun,而不需要陡峭的学习曲线。
import { test, expect } from "bun:test";
test("2 + 2", () => {
expect(2 + 2).toBe(4);
});
使用 bun test 命令可以直接执行测试。此外,Bun 的运行时支持 TypeScript 和 JSX,无需额外的配置或插件。
从 Jest 或 Vitest 迁移
Bun 对兼容性的承诺体现在对 Jest 全局导入的支持上。例如,从 @jest/globals
或 vitest
导入的内容将在内部重新映射到 bun:test
。这意味着现有的测试套件无需修改代码即可在 Bun 上运行。
// index.test.ts
import { test } from "@jest/globals";
describe("test suite", () => {
test("addition", () => {
expect(1 + 1).toBe(2);
});
});
性能基准
Bun 的测试运行器不仅注重兼容性,还注重速度。在针对 Zod 测试套件的基准测试中,Bun 的速度比 Jest 快 13 倍,比 Vitest 快 8 倍。Bun 的匹配器以快速的本地代码实现,进一步凸显了速度优势。例如,Bun 中的 expect().toEqual()
比 Jest 快 100 倍,比 Vitest 快 10 倍。
无论是迁移现有测试还是启动新项目,Bun 都能提供符合现代开发需求的强大测试环境。
总结
长期以来,Node.js 一直是 JavaScript 世界的基石,为开发人员树立了基准并提供了指导。然而,Bun 作为一个值得关注的挑战者,正在挑战极限。
虽然 Bun 还处于早期阶段,但它引发的热议已是不争的事实。目前,它已针对 MacOS 和 Linux 进行了优化,对 Windows 的支持也在进行中,但有些功能仍在开发中。Bun 提供的所有功能,目前值得进行深入探索了解。