Bun vs Node:必知必会

系统 其他OS
虽然 Bun 还处于早期发展阶段,但它名噪一时毋庸置疑。目前,它针对 MacOS 和 Linux 进行了优化,虽然 Windows 的支持正在进行中,但某些功能即将推出。凭借它提供的一切,Bun 无疑是您应该考虑探索的工具包。

大家好,这里是大家的林语冰。

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 无缝集成。确保这种兼容性的关键功能包括但不限于:

  • 支持内置 Node 模块,比如 fs/path/net。
  • 识别全局变量,比如 __dirname/process。
  • 遵守 Node 模块解析算法,包括熟悉的 node_modules 结构。

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

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

Bun APIs

  • Bun.file()

延迟加载文件并访问各种格式的内容。此方法比 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')
  • Bun.write()

用于将数据(从字符串到 Blob)写入磁盘的多功能 API。它的写入速度比 Node 快 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 多 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 <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 vs npm:

图片图片

bun CLI 包含一个兼容 Node 的包管理器,旨在以更快的速度替代 npm/yarn/pnpm。

此外,bun run <command> 只需要 7ms,而 npm run <command> 则需要 176ms。虽然 Node 的 npm 多年来一直是 JS 包管理的标准,但 Bun 确实是一个速度强大的引擎,并提供了一个引人注目的竞品。

打包器

打包是获取多个 JS 文件,并将它们合并到一个或多个优化包中的过程。此过程还可能涉及转换,比如将 TS 转换为 JS,或者压缩代码减小体积。

在 Node 的生态系统中,打包通常由第三方工具而不是 Node 本身处理。Node 世界中某些人气爆棚的打包器包括但不限于 Webpack、Rollup 和 Vite,提供了代码分割、树摇优化和热模块替换等功能。

另一方面,Bun 不仅仅是一个运行时和包管理器,而且它本身也是一个打包器。它旨在打包各种平台的 JS/TS 代码,包括浏览器中的前端 App(Vue/React App)和 Node。

要使用 Bun 打包,您可以使用一个简单的命令:

bun build ./index.ts --outdir ./build

此命令打包 index.ts 文件,并将结果输出到 ./build 目录中。打包过程非常快,Bun 比 esbuild 快 1.75 倍,并且直接对 Parcel 和 Webpack 等其他打包器“降维打击”。

图片图片

Bun 的一个天秀功能是它引入了 JS 宏。这允许在打包期间执行 JS 函数,并将结果直接内联到最终打包中。这种机制为打包提供了全新的视角。

瞄一下此栗子,其中在打包过程中利用 Bun 的 JS 宏来获取用户名。该宏不是运行时 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 拥有成熟的打包工具,但 Bun 提供了一种可集成的、更快的、新颖的竞品,可以重塑打包格局。

测试运行器

测试是软件开发的一个重要方面,它确保代码如期运行,并在落地生产之前捕获潜在问题。除了作为运行时、包管理器和打包器之外,Bun 也是一个测试运行器。

虽然 Node 开发者传统上依赖 Vitest/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 的运行时支持开箱即用的 TS/JSX,无需额外的配置或插件。

从 Vitest/Jest 迁移

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 世界的基石,设定基准并引领开发者。虽然但是,Bun 正以一位不容小觑的挑战者身份粉墨登场,破而后立。

虽然 Bun 还处于早期发展阶段,但它名噪一时毋庸置疑。目前,它针对 MacOS 和 Linux 进行了优化,虽然 Windows 的支持正在进行中,但某些功能即将推出。凭借它提供的一切,Bun 无疑是您应该考虑探索的工具包。

免责声明

本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考,英文原味版请传送 Bun vs Node.js: Everything you need to know[1]。

责任编辑:武晓燕 来源: 人猫神话
相关推荐

2020-07-10 07:58:14

Linux

2022-05-18 09:01:19

JSONJavaScript

2022-08-19 10:31:32

Kafka大数据

2015-10-20 09:46:33

HTTP网络协议

2019-01-30 14:14:16

LinuxUNIX操作系统

2018-10-26 14:10:21

2015-08-17 16:05:35

javascript对象编程

2024-01-09 13:58:22

PandasPython数据分析

2023-05-08 15:25:19

Python编程语言编码技巧

2023-12-26 12:10:13

2023-04-20 14:31:20

Python开发教程

2023-09-12 11:25:15

2022-08-26 14:46:31

机器学习算法线性回归

2019-11-06 10:56:59

Python数据分析TGI

2020-08-23 18:18:27

Python列表数据结构

2021-04-15 10:01:18

Sqlite数据库数据库知识

2022-12-01 08:15:41

CGOswitcherror

2022-12-07 09:00:18

错误异常CGO

2022-11-03 07:48:27

CSSat-rule

2021-06-09 11:06:00

数据分析Excel
点赞
收藏

51CTO技术栈公众号