文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Bun vs Node:必知必会

2024-11-30 03:14

关注

JS 社区掀起了运行时狂潮:Jarred Sumner 孵化的 Bun 1.0 官宣了。话虽如此,还是有一大坨道友很好奇:Bun 的本质是什么鬼物?为什么每个人都将其与百炼成钢的 Node 相提并论?Bun 只是另一种昙花一现的过眼云烟,还是会重新定义这场游戏?在本文中,让我们深入 Bun,瞄一下其功能,并了解它与成熟的 Node 的异同点。

Bun 是什么鬼物?

Bun 是一款适用于 JS/TS App 的超快速一体化工具包。Bun 的魅力在于它能够简化开发流程,比以往更丝滑高效。这并非不可能事件,因为 Bun 不仅仅是运行时,它还是包管理器/打包器/测试运行器。请想象一下,JS 开发有了一把瑞士军刀;此乃 Bun 馈赠与君之福利。

Bun 解决了什么问题?

Node 于 2009 年横空出世。虽然但是,与一大坨技术一样,随着技术的发展,其复杂性也与日俱增。请将其想象成一座城市。随着城市的扩张,交通拥堵可能会成为一个问题。

Bun 旨在成为缓解这种拥堵的新基建,让一切运行得更丝滑。这绝非反复造轮子,而是对其优化,确保我们在获得速度和简单性的同时,不会丧失 JS 独特和强大的本质。

Bun 被设计为比 Node 更快、更精简、更现代的竞品,所以让我们仔细瞄一下其比较。但首先让我们按下不表。

Node vs Deno vs Bun

在讨论 JS 运行时的进化史时,很难无视 Deno。“Node 之父”Ryan Dahl 推出了 Deno 作为一个新的运行时,旨在解决在 Node 中发现的某些挑战和遗憾。

Deno 是 JS/TS 的安全运行时。它直接解决了 Node 的一大坨短板。举个栗子,Deno 原生支持 TS,无需外部工具。与 Node 不同,Node 的脚本默认具有广泛的权限,Deno 采用安全第一的方法,要求开发者显式授予潜在敏感操作的权限,比如文件系统访问或网络连接。

虽然 Deno 提供了一个引人注目的 Node 竞品,但它还没有达到 Node 的广泛采用程度。因此,本文主要将 Bun 与成熟的 Node 对比。

入门

有了 Bun,我们可以使用 bun init -y 命令构建一个空项目。我们生成了若干文件,并在 index.ts 中添加一行 console.log('Hello, Bun!'')。在终端中运行命令 bun index.ts,即可看到打印了 'Hello, Bun!'。

JS 运行时

JS 运行时是一个提供使用和运行 JS 程序所需的所有组件的环境。

Node 和 Bun 都是运行时。Node 主要使用 C++ 编写,而 Bun 则使用一种名为 Zig 的低级通用编程语言编写。但这只是冰山一角。让我们仔细瞄一下 Bun 单独作为运行时的其他差异。

JS 引擎

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

Node 使用为 Chrome 浏览器提供支持的谷歌 V8 引擎,而 Bun 使用 JSC(JavaScriptCore),这是苹果为 Safari 浏览器开发的开源 JS 引擎。

V8 和 JSC 有不同的架构和优化策略。JSC 优先考虑更快的启动时间和减少内存占用,以及稍慢的执行时间。另一方面,V8 优先考虑快速执行,并进行更多运行时优化,这可能会导致更多的内存占用。

这使得 Bun 速度更快,启动速度比 Node 快 4 倍。

图片

总结:Bun 的运行速度比 Deno 快 2.19 倍,比 Node 快 4.81 倍。

转译器

虽然 Node 是一个强大的 JS 运行时,但它本身并不支持 TS。要在 Node 环境中执行 TS,需要外部依赖。一种常见方案是使用构建步骤将 TS 转换为 JS,然后运行生成的 JS 代码。这是使用 tsx 包的基本设置:

  1. 安装
pnpm add -D typescript tsx
  1. 在 package.json 中,您可以设置脚本来简化流程。
{
  "scripts": {
    "dev": "tsx hello-bun.ts"
  }
}
  1. 执行

使用上述脚本,您可以轻松运行 TS 文件:

pnpm dev

相比之下,Bun 提供了一种更精简的方法。它自带集成到运行时的 JS 转译器。这允许您直接运行 .js/.ts/.jsx/.tsx 文件。Bun 的内置转译器将这些文件无缝转换为普通 JS,无需额外步骤即可立即执行。

bun hello-bun.ts

运行 TS 文件时,速度差异会被放大,因为 Node 在运行前需要一个转译步骤。

图片

ESM 和 CJS 兼容性

模块系统允许开发者将代码组织成可复用片段。在 JS 中,两个主要的模块系统是 CJS(CommonJS)和 ESM(ES 模块)。CJS 源自 Node,使用 require/module.exports 处理同步模块,十分适合服务器端操作。

ES6 引入的 ESM 采用 import/export 语句,提供更加静态和异步的方案,并针对浏览器和现代构建工具进行了优化。让我们诉诸 CJS 使用 colors,诉诸 ESM 使用 chalk,这两个流行的包用于向控制台添加彩色输出,并更好地理解模块系统。

Node 传统上与 CJS 模块系统相关联。这是一个经典用法:

// Node 中的 CJS(index.js)
const colors = require('colors')
console.log(colors.green('Hello, Bun!'))

对于 Node 中的 ESM,您可以二选一:

  1. 您需要在 package.json 中包含 "type": "module"。
  2. 使用 .mjs 扩展名。
// Node 中的 ESM(index.mjs)
import chalk from 'chalk'
console.log(chalk.blue('Hello, Bun!'))

从 CJS 过渡到 ESM 是一个复杂的过程。在 ESM 推出后,Node 花了 5 年时间才在没有实验标志的情况下支持它。尽管如此,CJS 在生态系统中依然人气爆棚。

Bun 通过支持两者而无需任何特殊配置,简化了模块系统。Bun 的亮点功能是,它能够在同一文件中同时支持 import/require(),这在 Node 中是不可能事件:

// 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 是浏览器筑基的 App 的组成部分,提供用于 Web 交互的工具,比如 fetch/WebSocket。虽然这些已经成为浏览器标准,但它们对 Node 等服务器端环境的支持却不一致。

在 Node 的早期版本中,浏览器中常用的 Web 标准 API 本身并不支持。开发者必须依赖 node-fetch 等第三方包来拷贝此功能。虽然但是,从 Node 18 开始,对 fetch API 提供了实验性支持,可能消除了这些包的需求。

Bun 通过为这些 Web 标准 API 提供内置支持来简化这一过程。开发者可以直接使用稳定的 fetch/Request/Response/WebSocket 等类似浏览器的 API,无需任何额外的包。此外,Bun 对这些 Web API 的原生实现,确保它们比第三方备胎更快、更可靠。

下面是一个兼容 Node(v18 及更高版本)和 Bun 的示例。虽然它在 Node 中是实验性的,但同款功能在 Bun 中是稳定的:

// Node(v18 及更高版本)的实验性 fetch,以及 Bun 内置的 fetch
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

热重载

热重载是一项功能,可在代码更改时自动刷新或实时重新加载 App 的某些部分,提高开发者的工作效率,而无需完全重新启动。

在 Node 的生态系统中,您有一大坨选择来实现热重载。一种流行的工具是 nodemon,它会硬重启整个过程:

nodemon index.js

或者,从 Node 18 开始,引入了一个实验性的 --watch 标志:

node --watch index.js

这两种方法的目的都是在代码更改时提供 App 的实时重新加载。虽然但是,它们的行为可能一龙一猪,尤其是在某些环境或场景下。

举个栗子,nodemon 可能会导致中断,比如断开 HTTP 和 WebSocket 连接,而 --watch 标志处于实验阶段,可能无法提供全套功能。

Bun 的热重载更进一步。通过使用 --hot 标志运行 Bun,可以启用热重载:

bun --hot index.ts

与可能需要重新启动整个进程的 Node 方法不同,Bun 会就地重新加载代码,而不会终止旧进程。这可确保 HTTP 和 WebSocket 连接保持不间断,并保留 App 状态,提供更丝滑的 DX(开发体验)。

Node 兼容性

当过渡到新的运行时或环境时,兼容性通常是开发者最关心的问题。Bun 通过将自己定位为 Node 的同款竞品来解决此问题。这意味着,现有的 Node App 和 npm 包无需任何修改即可与 Bun 无缝集成。确保这种兼容性的关键功能包括但不限于:

Bun 仍在茁壮成长。它专为增强开发工作流程而定制,十分适合资源有限的环境(比如 serverless 函数)。Bun 背后的团队正在努力实现全面的 Node 兼容性以及与流行框架的更好集成。

虽然 Bun 确保兼容 Node,但它不止于此。Bun 附带高度优化的标准库 API,可以满足开发者的刚需。

Bun APIs

延迟加载文件并访问各种格式的内容。此方法比 Node 对应的方法快 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')

用于将数据(从字符串到 Blob)写入磁盘的多功能 API。它的写入速度比 Node 快 3 倍。

// Bun (index.ts)
await Bun.write('index.html', '')

// Node.js (index.mjs)
const fs = require('fs/promises')
await fs.writeFile('index.html', '')

使用 Web 标准 API 设置 HTTP 服务器或 WebSocket 服务器。它每秒处理的请求比 Node 多 4 倍,处理的 WebSocket 消息比 Node 的 ws 包多 5 倍。这种后端功能让人想起开发者在 Node 中使用 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 不仅仅是一个运行时;它还是一个高级工具包,其中包括但不限于功能强大的包管理器。如果您发现自己在依赖安装过程中耐心等待,Bun 提供了一种令人耳目一新的更快竞品。即使您不使用 Bun 作为运行时,其内置的包管理器也可以加快您的开发工作流程。

瞄一下该表格,将 Bun 命令与 Node 的包管理器 npm 比较:

Bun

npm

目的

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