2023年9月8日,JavaScript社区再次掀起了新一轮热潮:由Jarred Sumner创建的Bun v1.0问世了。然而,随着各种讨论的进行,许多人都在疑惑:Bun的本质是什么?为什么人们会将其与经过时间考验的Node.js相提并论?Bun只是又一个短暂的趋势,还是它将重新定义游戏规则?在本文中,让我们深入了解``Bun,了解其特点,并了解它与深受信任的Node.js相比如何。

什么是Bun?

Bun是一个针对JavaScriptTypeScript应用的超快全能工具包。Bun的美妙之处在于它能够简化开发流程,使其比以往更加顺畅和高效。这是可能的,因为Bun不仅是一个运行时,它还是一个包管理器、一个打包工具和一个测试运行器。想象一下拥有一个JS开发的瑞士军刀;那就是Bun

Bun解决的问题

Node.js在2009年的诞生是具有突破性的。然而,就像许多技术一样,随着它的发展,它的复杂性也在增加。想象一下城市。随着城市的扩张,交通拥堵可能成为一个问题。

Bun旨在成为缓解这种拥堵的新基础设施,使事物运行更加顺畅和快速。这并不是要重复造轮子,而是要对其进行精益求精,确保我们既获得了速度和简单性,又没有失去JavaScript独特和强大之处的本质。

Bun被设计为Node.js的更快、更精简、更现代化的替代品,因此让我们更详细地比较一下它们的一些差异。但首先让我们讨论另一个话题。

Node.js vs Deno vs Bun

当讨论JavaScript运行时的演变时,很难忽略DenoNode.js的创始人Ryan Dahl介绍了Deno作为一个新的运行时,旨在解决他在Node.js中发现的一些问题和缺陷。

Deno是一个用于JavaScriptTypeScript的安全运行时。它直接解决了Node.js的许多缺点。例如,Deno原生支持TypeScript,无需外部工具。与Node.js不同,Node.js默认情况下脚本具有广泛的权限,Deno采用安全优先的方法,要求开发人员明确授予权限,以执行可能涉及敏感操作的操作,例如文件系统访问或网络连接。

虽然DenoNode.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.jsBun都是运行时。Node.js主要是用C++编写的,而Bun是用一种称为Zig的低级通用编程语言编写的。但这只是冰山一角。让我们更仔细地看看在将Bun视为仅运行时时的其他区别。

JavaScript引擎

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

Node.js使用了谷歌的V8引擎,该引擎驱动Chrome浏览器,而Bun使用了JavaScriptCore(JSC),这是由苹果为Safari开发的开源JavaScript引擎。

V8JSC具有不同的架构和优化策略。JSC优先考虑更快的启动时间和更少的内存使用,而执行时间略慢一些。另一方面,V8优先考虑快速执行,具有更多的运行时优化,这可能导致更多的内存使用。
这使得Bun启动速度快,比Node.js快4倍。

 

 

 

总结:bundeno快2.19倍,比node快4.81倍。

转译器

虽然Node.jsJavaScript的强大运行时,但它不直接支持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需要一个转译步骤才能运行。

Bun花费8ms,Node esbuild花费40ms,tsx花费120ms,而tsx花费350ms。

ESM和CommonJS兼容性

模块系统允许开发人员将代码组织成可重用的段。在JavaScript中,两种主要的模块系统是CommonJSES模块(ESM)CommonJS源自Node.js,使用requiremodule.exports进行同步模块处理,适用于服务器端操作。

ESM是在ES6中引入的,它采用importexport语句,提供了一种更静态和异步的方法,针对浏览器和现代构建工具进行了优化。让我们使用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.jsESM引入后5年才支持它,而无需实验性标志。尽管如此,CommonJS仍然在生态系统中普遍存在。

Bun通过支持两者而不需要任何特殊配置来简化模块系统。Bun的突出特点是其能够在同一文件中支持importrequire(),这在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 提供了像 fetchWebSocket 这样的工具,用于网络交互。尽管这些已经成为浏览器标准,但它们在诸如 Node.js之类的服务器端环境中的支持一直不一致。

在早期的 Node.js版本中,浏览器中常见的 Web标准 API并没有得到原生支持。开发人员不得不依赖第三方包(例如 node-fetch)来复制这些功能。然而,从Node.js v18开始,对fetch API的实验性支持已经存在,这可能消除了对这些包的需求。

Bun 通过提供对这些Web标准 API的内置支持来简化这一过程。开发人员可以直接使用稳定的fetchRequestResponseWebSocket 和其他类似于浏览器的 API,而无需使用额外的包。此外,Bun 对这些 Web API 的本地实现确保了它们与第三方替代方案相比更快、更可靠。

以下是兼容 Node.jsv18及以上版本)和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 可能会导致一些干扰,比如中断 HTTPWebSocket连接,而实验性的--watch 标志可能不会提供完整的功能集,并且在 GitHub上报告了一些问题。

Bun 推进了热更新一步。通过使用--hot 标志运行 Bun,即可启用热重新加载:

bun --hot index.ts

与可能需要完全进程重新启动的 Node.js方法不同,Bun 在不终止旧进程的情况下就地重新加载您的代码。这确保了 HTTPWebSocket 连接保持不中断,并且应用程序状态得以保留,从而提供了更流畅的开发体验。

Node.js 兼容性

在过渡到新的运行时或环境时,兼容性通常是开发人员的主要关注点。Bun 通过将自己定位为 Node.js的替代品来解决了这个问题。这意味着现有的 Node.js应用程序和 npm 包可以无缝地集成到 Bun中,而无需进行任何修改。确保此兼容性的关键功能包括:

  • 支持内置的 Node.js 模块,例如fspathnet
  • 识别全局变量,如__dirnameprocess
  • 遵循 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命令与npmNode的包管理器:

BunnpmPurpose
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兼容的包管理器,旨在成为 npmyarnpnpm的显著更快的替代品

此外,bun run <command> 仅需 7 毫秒,而npm run <command> 需要 176 毫秒。虽然 Node.jsnpm多年来一直是 JavaScript 包管理的标准,但 Bun真的是一个速度强大的工具,并提供了一个引人注目的替代方案。

Bun 与 Node.js 对比:打包工具

打包是将多个 JavaScript文件合并成一个或多个优化的捆绑包的过程。此过程还可能涉及转换,例如将TypeScript转换为 JavaScript或将代码缩小以减小其大小。

Node.js 生态系统中,打包通常由第三方工具处理,而不是 Node.js本身。在 Node.js 世界中,一些最流行的打包工具包括 WebpackRollupParcel,它们提供了代码分割、树摇和热模块替换等功能。

另一方面,Bun 不仅是一个运行时和一个包管理器,还是一个自己的打包工具。它设计用于为各种平台(包括浏览器中的前端应用程序,如ReactNext.js 应用程序以及 Node.js)打包 JavaScriptTypeScript代码。

要使用 Bun 进行打包,可以使用简单的命令:

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

此命令将 index.ts文件打包,并将结果输出到./build 目录。Bun的打包过程非常快,比 esbuild快了 1.75 倍,并且明显超过了ParcelWebpack等其他打包工具。

 

Bun耗时0.17秒,esbuild耗时0.3秒,rspack耗时4.45秒,Parcel 2耗时26.32秒,Rollup耗时32秒,Webpack 5耗时38.02秒

Bun的一个突出特点是引入了 JavaScript 宏。这些允许在打包过程中执行 JavaScript函数,并将结果直接内联到最终捆绑包中。此机制为打包提供了一种新的视角。

请看以下示例,在此示例中,BunJavaScript宏被利用来在打包过程中获取用户名。它不是在运行时执行 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 的运行时直接支持 TypeScriptJSX,无需额外的配置或插件。

从 Jest 或 Vitest 迁移

BunJest的全局导入的兼容性表现出其对兼容性的承诺。例如,从 @jest/globalsvitest导入将在内部重新映射为 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还处于早期阶段,但它所产生的热度是不可否认的。目前,它针对 MacOSLinux 进行了优化,虽然 Windows支持正在进行中,但某些功能仍在路上。鉴于它所提供的一切,Bun绝对是一个您应该考虑探索的工具包。

posted on 2024-08-30 10:13  ygunoil  阅读(4)  评论(0编辑  收藏  举报