Bun 与 Node.js:你需要知道的一切
9 月 8 日,JavaScript 社区传来一股新的热潮:由Jarred Sumner创建的 Bun v1.0正式发布。然而,在如此热议的同时,许多人也开始思考:Bun 的本质是什么?为什么大家都把它与久经考验的Node.js相提并论?Bun 究竟只是昙花一现的潮流,还是要重新定义游戏规则?在本文中,我们将深入探讨 Bun,了解它的功能,并将其与成熟的 Node.js 进行比较。
什么是 Bun?
Bun 是一款超快速的 JavaScript 和 TypeScript 应用一体化工具包。Bun 的魅力在于它能够简化开发流程,使其比以往任何时候都更加顺畅高效。这得益于 Bun 不仅仅是一个运行时,它还是一个包管理器、打包器和测试运行器。想象一下,拥有一把用于 JS 开发的瑞士军刀;这就是 Bun。
Bun 解决了什么问题
Node.js 于 2009 年诞生,堪称开创性的。然而,如同许多技术一样,随着它的发展,其复杂性也随之增长。不妨把它想象成一座城市。随着城市的扩张,交通拥堵可能会成为一个问题。
Bun 的目标是成为缓解这种拥堵的新型基础设施,使一切运行得更顺畅、更快速。我们并非在重新发明轮子,而是在不断改进,确保我们在获得速度和简洁性的同时,不会丢失 JavaScript 独特而强大的本质。
Bun 旨在成为 Node.js 更快、更精简、更现代化的替代品,因此我们来仔细比较一下。不过,首先我们先讨论另一个话题。
Node.js 与 Deno 与 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。
入门
使用 Bun,我们可以使用命令 搭建一个空项目bun init -y
。我们生成了几个文件,并在 index.ts 中添加一行console.log("Hello, Bun!")
。在终端中,运行该命令bun index.ts
即可看到输出“Hello, Bun!”。
Bun 与 Node.js:JavaScript 运行时
JavaScript 运行时是一个提供使用和运行 JavaScript 程序所需的所有必要组件的环境。
Node.js 和 Bun 都是运行时。Node.js 主要用 C++ 编写,而 Bun 则使用一种名为Zig 的低级通用编程语言编写。但这仅仅是冰山一角。让我们仔细看看将 Bun 单独视为运行时时的其他差异。
JavaScript 引擎
JavaScript 引擎是一种将我们编写的 JavaScript 代码转换为机器代码的程序,使计算机能够执行特定任务。
Node.js 使用为 Chrome 浏览器提供支持的Google V8 引擎,而 Bun 则使用JavaScriptCore (JSC),这是 Apple 为 Safari 开发的开源 JavaScript 引擎。
V8 和 JSC 的架构和优化策略有所不同。JSC 优先考虑更快的启动时间和更低的内存占用,但执行时间会略慢。而 V8 则优先考虑更快的执行时间,并进行了更多的运行时优化,这可能会导致更高的内存占用。
这使得 Bun 速度很快,启动速度比 Node.js 快 4 倍。
总结:bun 的运行速度比 deno 快 2.19 倍,比 node 快 4.81 倍
转译器
虽然 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.执行
使用上述脚本,您可以轻松运行您的 TypeScript 文件:
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
语句,提供了一种更静态、更异步的方法,并针对浏览器和现代构建工具进行了优化。让我们使用colors
CommonJS 和chalk
ESM 这两个流行的包来向控制台添加彩色输出,并更好地理解模块系统。
Node.js 传统上与 CommonJS 模块系统相关联。以下是典型的用法:
// CommonJS in Node.js (index.js)
const colors = require("colors");
console.log(colors.green('Hello, world!'));
对于 Node.js 中的 ES 模块,您有两种选择:
- 您需要将其包含
"type": "module"
在您的 中package.json
。 - 使用
.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 API
Web API 是基于浏览器的应用程序不可或缺的一部分,它提供了类似fetch
和 的工具WebSocket
来进行 Web 交互。虽然这些 API 已经成为浏览器标准,但它们在 Node.js 等服务器端环境中的支持并不一致。
在早期版本的 Node.js 中,浏览器中常用的 Web 标准 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(); // Leanne Graham
热重载
热重载是一种提高开发人员工作效率的功能,它可以在代码更改时自动实时刷新或重新加载应用程序的各个部分,而无需完全重启。
在 Node.js 生态系统中,有几种实现热重载的选项。一种流行的工具是nodemon
,它可以硬重启整个进程:
nodemon index.js
或者,从 Node.js v18 开始,引入了一个实验性--watch
标志:
node --watch index.js
这两种方法都旨在在代码更改时提供应用程序的实时重新加载。但是,它们的行为可能有所不同,尤其是在某些环境或场景下。
例如,nodemon
可能会导致断开 HTTP 和 WebSocket 连接等中断,而该--watch
标志处于实验阶段,可能无法提供全套功能,并且在GitHub 问题中报告了一些问题。
Bun 将热重载功能更进一步。通过在 Bun 中加上--hot
以下标志,即可启用热重载:
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 API
Bun.文件()
延迟加载文件并以各种格式访问其内容。此方法比 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()
一个多功能 API,用于将数据写入磁盘,从字符串到 Blob。其写入速度比 Node.js 快 3 倍。
// Bun (index.ts)
await Bun.write("index.html", "<html/>");
// Node.js (index.mjs)
const fs = require("fs/promises");
await fs.writeFile("index.html", "<html/>");
包子.serve()
使用 Web 标准 API 设置 HTTP 服务器或 WebSocket 服务器。它每秒处理的请求数比 Node.js 多 4 倍,WebSocket 消息处理能力比ws
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 与 Node.js:包管理器
Bun 不仅仅是一个运行时;它是一个包含强大包管理器的高级工具包。如果您在依赖项安装过程中苦苦等待,Bun 提供了一个令人耳目一新的快速替代方案。即使您不使用 Bun 作为运行时,其内置的包管理器也可以加快您的开发工作流程。
查看此表,比较 Bun 命令与Node 的包管理器npm:
乍一看,Bun 的命令可能似曾相识,但实际使用体验却截然不同。Bun 的安装速度比 npm 快几个数量级。它通过利用全局模块缓存来实现这一点,从而消除了从 npm 注册表中进行的冗余下载。此外,Bun 还为每个操作系统配备了最快的系统调用,以确保最佳性能。
以下是从缓存中安装 Remix 启动项目依赖项的速度比较,比较了 Bun 和 npm:
CLIbun
包含一个与 Node.js 兼容的包管理器,旨在以更快的速度替代npm
、yarn
和 。pnpm
此外,abun run <command>
仅需 7 毫秒,而npm run <command>
则需 176 毫秒。虽然 Node.js 的 npm 多年来一直是 JavaScript 包管理的标准,但 Bun 的速度确实非常快,是一个引人注目的替代方案。
Bun 与 Node.js:捆绑器
打包是将多个 JavaScript 文件合并为一个或多个优化包的过程。此过程还可能涉及转换,例如将 TypeScript 转换为 JavaScript 或压缩代码以减小其大小。
在 Node.js 生态系统中,打包通常由第三方工具而非 Node.js 本身处理。Node.js 世界中一些最流行的打包工具包括Webpack、Rollup和Parcel,它们提供代码拆分、树状优化和热模块替换等功能。
另一方面,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 耗时 0.17 秒,esbuild 耗时 0.3 秒,rspack 耗时 4.45 秒,Parcel 2 耗时 26.32 秒,Rollup 耗时 32 秒,Webpack 5 耗时 38.02 秒
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 "Leanne Graham";
console.log(user);
虽然 Node.js 有其成熟的捆绑工具,但 Bun 提供了一种集成的、更快的、创新的替代方案,可以重塑捆绑格局。
Bun 与 Node.js:测试运行器
测试是软件开发中至关重要的一个环节,它可以确保代码按预期运行,并在投入生产之前发现潜在问题。Bun 除了是运行时、包管理器和打包器之外,还是一个测试运行器。
虽然 Node.js 开发人员传统上依赖Jest来满足他们的测试需求,但 Bun 引入了一个内置测试运行器,它保证了速度、兼容性以及一系列满足现代开发工作流程的功能。
Bun 的测试运行器bun:test
旨在与 Jest 完全兼容。Jest 是一个以其“expect”风格 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 的匹配器进一步凸显了这一速度优势,这些匹配器采用快速的原生代码实现。例如,expect().toEqual()
Bun 的速度比 Jest 快 100 倍,比 Vitest 快 10 倍。
无论您是想迁移现有测试还是启动新项目,Bun 都能提供符合现代开发需求的强大测试环境。
结论
Node.js 长期以来一直是 JavaScript 世界的基石,树立了标杆,引领着开发者。然而,Bun 正以一位值得关注的挑战者的身份崭露头角,不断突破界限。
虽然 Bun 尚处于起步阶段,但它所引发的热议毋庸置疑。目前,它已针对 MacOS 和 Linux 进行了优化,Windows 支持也正在推进中,但部分功能仍待开发。Bun 提供的功能如此丰富,绝对值得您考虑探索。
使用组件进行可视化构建
Builder.io是一个可视化编辑器,可以连接到任何网站或应用程序,并允许您拖放组件。
// Dynamically render your components
export function MyPage({ json }) {
return <BuilderComponent content={json} />
}
registerComponents([MyHero, MyProducts])