2023年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 vs Deno vs Bun
当讨论JavaScript
运行时的演变时,很难忽略Deno
。Node.js
的创始人Ryan Dahl
介绍了Deno
作为一个新的运行时,旨在解决他在Node.js
中发现的一些问题和缺陷。
Deno
是一个用于JavaScript
和TypeScript
的安全运行时。它直接解决了Node.js
的许多缺点。例如,Deno
原生支持TypeScript
,无需外部工具。与Node.js
不同,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 vs Node.js:JavaScript运行时
JavaScript
运行时是提供所有必要组件以便使用和运行JavaScript
程序的环境。
Node.js
和Bun
都是运行时。Node.js
主要是用C++
编写的,而Bun
是用一种称为Zig
的低级通用编程语言编写的。但这只是冰山一角。让我们更仔细地看看在将Bun视为仅运行时时的其他区别。
JavaScript引擎
JavaScript
引擎是一个将我们编写的JavaScript
代码转换为机器代码的程序,从而使计算机能够执行特定任务。
Node.js
使用了谷歌的V8
引擎,该引擎驱动Chrome
浏览器,而Bun使用了JavaScriptCore(JSC)
,这是由苹果为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
提供了一种更简化的方法。它内置了一个JavaScrip
t转译器到运行时中。这允许你直接运行.js
、.ts
、.jsx
和.tsx
文件。Bun
的内置转译器无缝地将这些文件转换为原生JavaScript
,无需额外步骤即可立即执行。
bun index.ts
当运行TypeScript文件时,速度差异被放大,因为Node.js需要一个转译步骤才能运行。
Bun
花费8ms,Node esbuild
花费40ms,tsx
花费120ms,而tsx
花费350ms。
ESM和CommonJS兼容性
模块系统允许开发人员将代码组织成可重用的段。在JavaScript
中,两种主要的模块系统是CommonJS
和ES
模块(ESM)
。CommonJS
源自Node.js
,使用require
和module.exports
进行同步模块处理,适用于服务器端操作。
ESM
是在ES6
中引入的,它采用import
和export
语句,提供了一种更静态和异步的方法,针对浏览器和现代构建工具进行了优化。让我们使用colors
表示CommonJS
,使用chalk
表示ESM
,这两个流行的包用于将彩色输出添加到控制台,以更好地理解模块系统。
Node.js传统上与CommonJS模块系统相关联。下面是Node.js中的典型用法:
// 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
)是一个复杂的过程。Node.js
在ESM
引入后5年才支持它,而无需实验性标志。尽管如此,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
版本中,浏览器中常见的 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
Hot reloading
Hot reloading
是一项功能,通过在代码更改时自动刷新或重新加载应用程序的部分内容,而无需完全重新启动,从而提高开发人员的生产力。
在Node.js
生态系统中,您有几种选项来实现Hot reloading
。其中一个流行的工具是 nodemon
,它会强制重新启动整个进程:
nodemon index.js
另外,从 Node.js v18
开始,引入了一个实验性的 --watch
标志:
node --watch index.js
这两种方法都旨在在代码更改时提供应用程序的热更新。然而,它们可能在某些环境或场景中有不同的行为。
例如,nodemon
可能会导致一些干扰,比如中断 HTTP
和WebSocket
连接,而实验性的--watch
标志可能不会提供完整的功能集,并且在 GitHub
上报告了一些问题。
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", "<html/>"); // 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
性能优化的额外好处。
// 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
命令与npm
、Node
的包管理器:
Bun | npm | Purpose |
---|---|---|
bun install | npm install | 从 package.json 安装所有依赖项 |
bun add <package> | npm install <package> | 向项目添加新包 |
bun add <package> --dev | npm install <package> --dev | 添加一个仅用于开发的新包 |
bun remove <package> | npm uninstall <package> | 从项目中删除一个包 |
bun update <package> | npm update <package> | 更新特定包到其最新版本 |
bun run <script> | npm run <script> | 从 package.json 执行指定的脚本 |
乍一看,Bun
的命令可能看起来很熟悉,但实际体验却并不平凡。Bun
的安装速度比 npm
快上数个数量级。它通过利用全局模块缓存来实现这一点,消除了从npm
注册表中下载的冗余文件。此外,Bun
使用每个操作系统可用的最快的系统调用,确保了最佳的性能。
以下是从缓存安装 Remix
初始项目的依赖项时,Bun 和 npm 的速度比较:
bun CLI
包含一个与 Node.js
兼容的包管理器,旨在成为 npm
、yarn
和 pnpm
的显著更快的替代品
此外,bun 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
不仅是一个运行时和一个包管理器,还是一个自己的打包工具。它设计用于为各种平台(包括浏览器中的前端应用程序,如React
或 Next.js
应用程序以及 Node.js
)打包 JavaScript
和 TypeScript
代码。
要使用 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
的匹配器也是用快速的本机代码实现的。例如,在Bun
中,expect().toEqual()
的速度比Jest
快了惊人的 100 倍,比 Vitest
快了 10 倍。
无论您是要迁移现有测试还是启动新项目,Bun
都提供了一个符合现代开发需求的强大测试环境。
结论
Node.js
长期以来一直是JavaScript
世界的基石,它设定基准并指导开发人员。然而,Bun
正在成为一个引人注目的挑战者,打破了界限。
虽然Bun
还处于早期阶段,但它所产生的热度是不可否认的。目前,它针对 MacOS
和Linux
进行了优化,虽然 Windows
支持正在进行中,但某些功能仍在路上。鉴于它所提供的一切,Bun
绝对是一个您应该考虑探索的工具包。