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.安装
1
npm install -D typescript ts-node
- 2.脚本配置
在你的package.json中,你可以设置脚本来简化这个过程:
1 | { |
- 3.执行
有了以上脚本,你可以轻松运行你的TypeScript文件:
1 | npm start |
相比之下,Bun
提供了一种更简化的方法。它内置了一个JavaScrip
t转译器到运行时中。这允许你直接运行.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
中,两种主要的模块系统是CommonJS
和ES
模块(ESM)
。CommonJS
源自Node.js
,使用require
和module.exports
进行同步模块处理,适用于服务器端操作。
ESM
是在ES6
中引入的,它采用import
和export
语句,提供了一种更静态和异步的方法,针对浏览器和现代构建工具进行了优化。让我们使用colors
表示CommonJS
,使用chalk
表示ESM
,这两个流行的包用于将彩色输出添加到控制台,以更好地理解模块系统。
Node.js传统上与CommonJS模块系统相关联。下面是Node.js中的典型用法:
1 | // CommonJS in Node.js (index.js) |
对于Node.js
中的ES
模块,你有两个选择:
你需要在你的package.json
中包含"type": "module"
。
使用.mjs
扩展名。
1 | // ESM in Node.js (index.mjs) |
从CommonJS
过渡到ES
模块(ESM
)是一个复杂的过程。Node.js
在ESM
引入后5年才支持它,而无需实验性标志。尽管如此,CommonJS
仍然在生态系统中普遍存在。
Bun
通过支持两者而不需要任何特殊配置来简化模块系统。Bun
的突出特点是其能够在同一文件中支持import
和require()
,这在Node.js
中是无法原生实现的:
1 | // Mixed modules in Bun (index.js) |
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
中这个功能已经稳定:
1 | // Experiment fetch in Node.js (v18 and above) and built-in fetch in Bun |
Hot reloading
Hot reloading
是一项功能,通过在代码更改时自动刷新或重新加载应用程序的部分内容,而无需完全重新启动,从而提高开发人员的生产力。
在Node.js
生态系统中,您有几种选项来实现Hot reloading
。其中一个流行的工具是 nodemon
,它会强制重新启动整个进程:
1 | nodemon index.js |
另外,从 Node.js v18
开始,引入了一个实验性的 --watch
标志:
1 | node --watch index.js |
这两种方法都旨在在代码更改时提供应用程序的热更新。然而,它们可能在某些环境或场景中有不同的行为。
例如,nodemon
可能会导致一些干扰,比如中断 HTTP
和WebSocket
连接,而实验性的 --watch
标志可能不会提供完整的功能集,并且在 GitHub
上报告了一些问题。
Bun
推进了热更新一步。通过使用 --hot
标志运行 Bun
,即可启用热重新加载:
1 | 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 倍。
1 | // Bun(index.ts) |
1 | // Node.js(index.mjs) |
- Bun.write()
将数据从字符串到Blob
写入磁盘的多功能API
。它的写入速度比Node.js
快多达 3 倍。
1 | // Bun(index.ts) |
1 | // Node.js(index.mjs) |
- Bun.serve()
使用Web
标准API
设置HTTP
服务器或WebSocket
服务器。它每秒能够提供比Node.js
多达 4 倍的请求数,并处理比Node.js
中的ws
包多达 5 倍的WebSocket
消息。这种后端能力类似于开发人员在Node.js
中使用Express
,但具有Bun
性能优化的额外好处。
1 | // Bun(index.ts) |
1 | // Node.js(index.mjs) |
Bun 还支持 sqlite 和密码内置功能。
Bun 与 Node.js 对比:包管理器
Bun
不仅仅是一个运行时;它还是一个包含强大包管理器的高级工具包。如果您在依赖项安装过程中发现自己需要耐心等待,Bun
提供了一个令人耳目一新的更快速的替代方案。即使您不将 Bun
用作运行时,其内置的包管理器也可以加快开发工作流程。
查看下表,比较了Bun
命令与 npm
、Node
的包管理器:
Bun | npm | Purpose |
---|---|---|
bun install | npm install | 从 package.json 安装所有依赖项 |
bun add |
npm install |
向项目添加新包 |
bun add |
npm install |
添加一个仅用于开发的新包 |
bun remove |
npm uninstall |
从项目中删除一个包 |
bun update |
npm update |
更新特定包到其最新版本 |
bun run | npm run | 从 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
进行打包,可以使用简单的命令:
1 | 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
调用,而是在捆绑时间获取数据,将结果直接内联到最终输出中:
1 | // users.ts |
虽然Node.js
拥有其成熟的打包工具,但Bun
提供了一个集成、更快速和创新的替代方案,可能会改变打包格局。
Bun 与 Node.js 对比:测试运行器
测试是软件开发的关键方面,它确保代码的行为符合预期,并在它们进入生产环境之前捕获潜在问题。除了是运行时、
包管理器和打包工具之外,Bun
还是一个测试运行器。
尽管 Node.js
开发人员传统上依赖于Jest
进行测试,但 Bun
引入了一个内置的测试运行器,该运行器承诺速度快、兼容性好,并具有一系列功能,可满足现代开发工作流程的需求。
Bun
的测试运行器 bun:test
设计为与Jest
完全兼容,Jest
是一种以“expect”
风格 API
而闻名的测试框架。这种兼容性确保了熟悉 Jest
的开发人员可以轻松过渡到 Bun
,而无需陡峭的学习曲线。
1 | import { test, expect } from "bun:test"; |
使用bun test
命令执行测试非常简单。此外,Bun
的运行时直接支持 TypeScript
和 JSX
,无需额外的配置或插件。
从 Jest 或 Vitest 迁移
Bun
对Jest
的全局导入的兼容性表现出其对兼容性的承诺。例如,从 @jest/globals
或 vitest
导入将在内部重新映射为 bun:test
。这意味着现有的测试套件可以在 Bun
上运行,而无需进行任何代码修改。
1 | // index.test.ts |
性能基准测试
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
绝对是一个您应该考虑探索的工具包。