【译】带你了解Bun vs Node.js

2023年9月8日,JavaScript社区再次掀起了新一轮热潮:由Jarred Sumner创建的Bun v1.0问世了。然而,随着各种讨论的进行,许多人都在疑惑:Bun的本质是什么?为什么人们会将其与经过时间考验的Node.js相提并论?Bun只是又一个短暂的趋势,还是它将重新定义游戏规则?在本文中,让我们深入了解``Bun,了解其特点,并了解它与深受信任的Node.js相比如何。

什么是Bun?

Bun是一个针对JavaScriptTypeScript应用的超快全能工具包。Bun的美妙之处在于它能够简化开发流程,使其比以往更加顺畅和高效。这是可能的,因为Bun不仅是一个运行时,它还是一个包管理器、一个打包工具和一个测试运行器。想象一下拥有一个JS开发的瑞士军刀;那就是Bun

Bun解决的问题

Node.js在2009年的诞生是具有突破性的。然而,就像许多技术一样,随着它的发展,它的复杂性也在增加。想象一下城市。随着城市的扩张,交通拥堵可能成为一个问题。

Bun旨在成为缓解这种拥堵的新基础设施,使事物运行更加顺畅和快速。这并不是要重复造轮子,而是要对其进行精益求精,确保我们既获得了速度和简单性,又没有失去JavaScript独特和强大之处的本质。

Bun被设计为Node.js的更快、更精简、更现代化的替代品,因此让我们更详细地比较一下它们的一些差异。但首先让我们讨论另一个话题。

Node.js vs Deno vs Bun

当讨论JavaScript运行时的演变时,很难忽略DenoNode.js的创始人Ryan Dahl介绍了Deno作为一个新的运行时,旨在解决他在Node.js中发现的一些问题和缺陷。

Deno是一个用于JavaScriptTypeScript的安全运行时。它直接解决了Node.js的许多缺点。例如,Deno原生支持TypeScript,无需外部工具。与Node.js不同,Node.js默认情况下脚本具有广泛的权限,Deno采用安全优先的方法,要求开发人员明确授予权限,以执行可能涉及敏感操作的操作,例如文件系统访问或网络连接。

虽然DenoNode.js提供了一种引人注目的替代方案,但它还没有达到Node.js的广泛应用。因此,本文重点讨论Bun与深受信任的Node.js之间的对比。

入门

使用Bun,我们可以使用命令bun init -y创建一个空项目。我们生成了一些文件,在index.ts中添加一行,console.log("Hello, Bun!")。在终端中,运行命令bun index.ts以查看“Hello, Bun!”被记录。

Bun vs Node.js:JavaScript运行时

JavaScript运行时是提供所有必要组件以便使用和运行JavaScript程序的环境。

Node.jsBun都是运行时。Node.js主要是用C++编写的,而Bun是用一种称为Zig的低级通用编程语言编写的。但这只是冰山一角。让我们更仔细地看看在将Bun视为仅运行时时的其他区别。

JavaScript引擎

JavaScript引擎是一个将我们编写的JavaScript代码转换为机器代码的程序,从而使计算机能够执行特定任务。

Node.js使用了谷歌的V8引擎,该引擎驱动Chrome浏览器,而Bun使用了JavaScriptCore(JSC),这是由苹果为Safari开发的开源JavaScript引擎。

V8JSC具有不同的架构和优化策略。JSC优先考虑更快的启动时间和更少的内存使用,而执行时间略慢一些。另一方面,V8优先考虑快速执行,具有更多的运行时优化,这可能导致更多的内存使用。
这使得Bun启动速度快,比Node.js快4倍。

总结:bundeno快2.19倍,比node快4.81倍。

转译器

虽然Node.jsJavaScript的强大运行时,但它不直接支持TypeScript文件。要在Node.js环境中执行TypeScript,需要外部依赖。一个常见的方法是使用构建步骤将TypeScript(TS)转译为JavaScript(JS),然后运行生成的JS代码。以下是一个使用ts-node包的基本设置:

  • 1.安装
    1
    npm install -D typescript ts-node
  • 2.脚本配置

在你的package.json中,你可以设置脚本来简化这个过程:

1
2
3
4
5
{
"scripts": {
"start": "ts-node ./path/to/your/file.ts"
}
}
  • 3.执行

有了以上脚本,你可以轻松运行你的TypeScript文件:

1
npm start

相比之下,Bun提供了一种更简化的方法。它内置了一个JavaScript转译器到运行时中。这允许你直接运行.js.ts.jsx.tsx文件。Bun的内置转译器无缝地将这些文件转换为原生JavaScript,无需额外步骤即可立即执行。

1
bun index.ts

当运行TypeScript文件时,速度差异被放大,因为Node.js需要一个转译步骤才能运行。

Bun花费8ms,Node esbuild花费40ms,tsx花费120ms,而tsx花费350ms。

ESM和CommonJS兼容性

模块系统允许开发人员将代码组织成可重用的段。在JavaScript中,两种主要的模块系统是CommonJSES模块(ESM)CommonJS源自Node.js,使用requiremodule.exports进行同步模块处理,适用于服务器端操作。

ESM是在ES6中引入的,它采用importexport语句,提供了一种更静态和异步的方法,针对浏览器和现代构建工具进行了优化。让我们使用colors表示CommonJS,使用chalk表示ESM,这两个流行的包用于将彩色输出添加到控制台,以更好地理解模块系统。

Node.js传统上与CommonJS模块系统相关联。下面是Node.js中的典型用法:

1
2
3
// CommonJS in Node.js (index.js)
const colors = require("colors");
console.log(colors.green('Hello, world!'));

对于Node.js中的ES模块,你有两个选择:

你需要在你的package.json中包含"type": "module"
使用.mjs扩展名。

1
2
3
// ESM in Node.js (index.mjs)
import chalk from 'chalk';
console.log(chalk.blue('Hello, world!'));

CommonJS过渡到ES模块(ESM)是一个复杂的过程。Node.jsESM引入后5年才支持它,而无需实验性标志。尽管如此,CommonJS仍然在生态系统中普遍存在。

Bun通过支持两者而不需要任何特殊配置来简化模块系统。Bun的突出特点是其能够在同一文件中支持importrequire(),这在Node.js中是无法原生实现的:

1
2
3
4
5
6
// 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 版本中,浏览器中常见的 Web 标准 API 并没有得到原生支持。开发人员不得不依赖第三方包(例如 node-fetch)来复制这些功能。然而,从Node.js v18开始,对fetch API的实验性支持已经存在,这可能消除了对这些包的需求。

Bun 通过提供对这些Web标准 API 的内置支持来简化这一过程。开发人员可以直接使用稳定的 fetchRequestResponseWebSocket 和其他类似于浏览器的 API,而无需使用额外的包。此外,Bun 对这些 Web API 的本地实现确保了它们与第三方替代方案相比更快、更可靠。

以下是兼容 Node.jsv18及以上版本)和Bun的示例。虽然在Node.js中还处于实验阶段,但在Bun中这个功能已经稳定:

1
2
3
4
5
6
7
8
//  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(); // Leanne Graham
Hot reloading

Hot reloading是一项功能,通过在代码更改时自动刷新或重新加载应用程序的部分内容,而无需完全重新启动,从而提高开发人员的生产力。

Node.js生态系统中,您有几种选项来实现Hot reloading。其中一个流行的工具是 nodemon,它会强制重新启动整个进程:

1
nodemon index.js

另外,从 Node.js v18 开始,引入了一个实验性的 --watch 标志:

1
node --watch index.js

这两种方法都旨在在代码更改时提供应用程序的热更新。然而,它们可能在某些环境或场景中有不同的行为。

例如,nodemon 可能会导致一些干扰,比如中断 HTTPWebSocket连接,而实验性的 --watch 标志可能不会提供完整的功能集,并且在 GitHub 上报告了一些问题。

Bun 推进了热更新一步。通过使用 --hot 标志运行 Bun,即可启用热重新加载:

1
bun --hot index.ts

与可能需要完全进程重新启动的 Node.js 方法不同,Bun 在不终止旧进程的情况下就地重新加载您的代码。这确保了 HTTPWebSocket 连接保持不中断,并且应用程序状态得以保留,从而提供了更流畅的开发体验。

Node.js 兼容性

在过渡到新的运行时或环境时,兼容性通常是开发人员的主要关注点。Bun 通过将自己定位为 Node.js 的替代品来解决了这个问题。这意味着现有的 Node.js 应用程序和 npm 包可以无缝地集成到 Bun 中,而无需进行任何修改。确保此兼容性的关键功能包括:

  • 支持内置的 Node.js 模块,例如 fspath net
  • 识别全局变量,如__dirnameprocess
  • 遵循 Node.js 模块解析算法,包括熟悉的 node_modules 结构。

Bun 仍在不断发展。它旨在增强开发工作流程,是服务器无服务功能等资源有限环境的理想选择。Bun 团队正在努力实现全面的 Node.js 兼容性,并更好地与普遍存在的框架集成。

虽然 Bun 确保与 Node.js 的兼容性,但它并不止步于此。Bun 附带了高度优化的标准库 API,满足开发人员最需要的功能需求。

Bun APIs
  • Bun.file()
    延迟加载文件并以各种格式访问它们的内容。与其 Node.js 对应方法相比,这个方法速度提高了多达 10 倍。
1
2
3
// Bun(index.ts)
const file = Bun.file("package.json");
await file.text();
1
2
3
// 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 倍。
1
2
// Bun(index.ts)
await Bun.write("index.html", "<html/>");
1
2
3
// Node.js(index.mjs)
const fs = require("fs/promises");
await fs.writeFile("index.html", "<html/>");
  • Bun.serve()
    使用 Web 标准 API 设置HTTP服务器或 WebSocket 服务器。它每秒能够提供比 Node.js 多达 4 倍的请求数,并处理比Node.js中的 ws 包多达 5 倍的 WebSocket 消息。这种后端能力类似于开发人员在Node.js中使用 Express,但具有 Bun 性能优化的额外好处。
1
2
3
4
5
6
7
// Bun(index.ts)
Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello from Bun!");
},
});
1
2
3
4
5
6
7
8
9
// 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 与 Node.js 对比:包管理器

Bun 不仅仅是一个运行时;它还是一个包含强大包管理器的高级工具包。如果您在依赖项安装过程中发现自己需要耐心等待,Bun 提供了一个令人耳目一新的更快速的替代方案。即使您不将 Bun 用作运行时,其内置的包管理器也可以加快开发工作流程。

查看下表,比较了Bun命令与 npmNode 的包管理器:

Bun npm Purpose
bun install npm install 从 package.json 安装所有依赖项
bun add npm install 向项目添加新包
bun add –dev npm install –dev 添加一个仅用于开发的新包
bun remove npm uninstall 从项目中删除一个包
bun update npm update 更新特定包到其最新版本
bun run