Bun 1.0 初体验体验:这口包子香吗?
Bun 1.0 初体验体验:这口包子香吗?
⭐省流版:大体不可用,现阶段没有引进必要
Bun现阶段作为更快的包管理器是可用的;作为运行时加速Node全栈应用可作为可能的技术储备,现阶段不能用于生产。其他功能如测试、构建器由于生态和API不够健全,不具备生产价值。
2023 年 9 月 8 日,Javascript 运行时 Bun 正式发布 1.0 版本,标志着这个由前 Facebook 工程师创建的项目正式进入稳定生产可用阶段。
从官网来看,Bun具有如下特点(或者说它是以下这些东西)
- 更快的、Node兼容的运行时:Bun是一个全新的JavaScript运行时,旨在服务于现代JavaScript生态系统和作为Node.js的直接替代品(drop-in alternative)。它原生地实现了数百个Node.js和Web API,包括
fs
、path
、Buffer
等。 - 全面的JS开发解决方案:Bun是一个完整的工具包,用于构建JavaScript应用程序,包括包管理器、测试运行器和打包器。
本文主要从Web前端开发对Bun进行体验,对BFF和全栈Node开发并无涉及。
What’s better?
个人总结Bun相对于Node主要的优势有下,优先级从上到下递减:
- 一站式开发解决方案:Bun集成了npm/pnpm, node, webpack/esbuild, jest的功能,在Bun中可以直接完成项目管理、打包和测试
- 统一模块标准:Bun不再区分CJS和ESM模块标准,可以交叉混用
- Web API实现:Bun原生实现了如
fetch
等Web API,不再需要polyfill - 更快的速度:Bun使用Zig实现和JS-Core引擎,还支持无转译原生运行TS,加快了工具链和代码本身的运行速度
- 原生JSX和TS语言支持:Bun支持直接运行TS和JSX,不再需要转译,提高运行效率
- 集成常用工具:Bun内置了常用功能,如
dot-env
,nodemon
等
继续加速
Bun的加速体现在两方面——运行时和工具链上。
工具链加速部分采用和rspack和swc等一样的思路,就是内置化、集成化。插件化虽然拓展性好,但集成更快!
因此Bun提供了包管理、内置语言支持、环境变量管理等,将原本分散的功能集中在一起优化实现。
更快的包管理
Bun作为包管理器比pnpm更快,下图来自Bun官方:
在本地的一个中型项目中,此前已经运行过一次bun install
和若干次pnpm install
的情况下,使用hyperfine跑分如下,可以看出相对pnpm都有惊人的4倍提升。
根据现有的资料,Bun作为包管理器的性能优势来自:
- 使用Zig而非JS实现,具有语言性能优势
- 根据环境选择最优的系统调用,这也是low-level语言带来的优势
根据官方的说法,我们可以只把Bun作为包管理器而不作为运行时使用,这也是我认为Bun现阶段能对现有工具链最大的提升。
作为包管理器它仍具备大部分情况下符合我们预期的行为:
- 使用项目中的
package.json
和node_modules
,不会另起炉灶 - 使用自己的
bun.lockb
,采用二进制数据,不必序列化,存取更快 - 使用全局的安装缓存
~/.bun/install/cache
,尽量避免重复下载,并采用最快的系统调用进行复制或链接- macOS中采用复制,Linux中采用硬链接,因此mac上并不能节约硬盘空间
- 不使用
.npmrc
,而是使用自己的bunfig.toml
配置,这点需要注意
更快的测试
个人对测试不是很了解,这里不多做介绍。下图来自Bun官方:
更快的运行时
Bun采用了Apple Webkit的JS Core引擎取代Node/Deno中的V8引擎,使用Zig作为开发语言,实现了更快的速度。其主要目的是加速那些原本运行在Node上的JS服务端应用,如SSR Worker、BFF等。
下图性能对比来自Bun官网:
需要阐明的是,作为Native语言,C/C++/Rust/Zig在性能上并没有明显的差异,Bun的性能提升除了JS Core外主要来自其内部实现较高的Native占比。
如下图所示,Node中JS实现高达60%以上,而Bun只有20%左右,这可能是Bun加速的重要原因。
Bun除了提供Node的API实现外,还提供了自己的API实现,以提供更高的性能:
Bun.serve()
:使用 uWebSockets 实现,据称有5~10倍的性能提升Bun.file()
:使用最优系统调用优化了文件API
作为构建工具
为什么Bun已经原生支持了JSX和TS还需要构建?
很简单,浏览器不支持,最终代码需要在浏览器上而不是Bun上跑。
Bun.build()
可以将Bun作为构建工具使用(in Beta)。同样的,附上一张官方跑分图:
当然,现有的项目一般都依赖于围绕构建工具建立起的生态,所以甚至从webpack迁移到rspack或esbuild都是十分困难的事。
因此,使用Bun作为构建工具在现阶段个人感觉更多是一种技术探索和储备,并不具备生产价值。
Bun的构建API和esbuild类似(但不是Go而是Zig实现的,只是提供了相似的API),都是从一个入口处解析依赖,生成转译后的chunk代码。
1 | await Bun.build({ |
如上面的代码所示,每个入口会生成一个JS文件。
完整的API定义如下,需要注意的是loader
仅仅指内置的Loader,并不支持拓展。
1 | interface Bun { |
Bun只提供Bundle功能
和esbuild或rspack不同,Bun并没有集成文件监听(热更新)和Dev Server,需要结合--watch
和Bun.serve()
实现。这点上Bun并不能替代rspack或esbuild,提供完整的开发体验。
而Bun.serve()
更多地是用于服务BFF用于渲染前端页面的,面向生产的前端开发这方面还是更推荐使用现有的脚手架。
模块解析策略
Bun最吸引我的地方就是不再区分ESM和CJS模块规范, 大家应该都理解在import
中遇到一个间接的require()
的崩溃。
需要注意的是,只有将Bun作为运行时才能享受到这种模块解析策略:
- 使用
import
导入不需要拓展名(可选),不区分大小写,将按顺序遍历以下拓展名- ts → tsx → js → mjs → cjs → json → 同名目录中index(index拓展名按上面排序)
- 使用统一的标准解析CJS和ESM,同时支持
require
和import
,如下表所示:模块类型 require()
import * as
ESM Module Namespace Module Namespace CJS module.exports
对象default
是module.exports
,其中keys作为命名导出 - 对于
node_modules
中package的寻找策略与Node保持一致
体验Bun
这一部分我们使用Bun和Vite构建一个CSR React 单页应用。
首先安装Bun:
1 | curl -fsSL https://bun.sh/install | zsh # 选择自己的终端 |
使用Vite创建全新应用
根据官方引导 初始化脚手架:(不得不说真的很快)
需要注意的是,使用Bun运行命令(包括bunx
和bun run
)都需要加上—-bun
参数才会使用Bun而不是Node运行,否则Bun仅仅起到了包管理器的作用。
同样可以用于构建:
同样,我们使用hyperfine对比使用bun、pnpm、npm的构建时间。
第一组使用bunx, pnpx, npx运行vite build
,结果有些出乎意料,npx居然是最快的,bunx的表现和npm相近,pnpx出乎意料地慢。
第二组测试带--bun
对时间产生的影响,不带参数表示仅将Bun作为包管理器,不会将shebang中的!env node
调用为Bun。
结果显示使用Bun反而使时间变长了,what a shame!
使用Bun.build()
我们同样使用hyperfine来测试使用Bun的打包效率。
这里我们使用Bun的CLI bun build ./src/main.tsx --outdir dist
和 bunx vite build
来对比,发现Bun.build()
非常快,比起Vite使用的rollup方案有24倍的加速。
使用EdenX创建全新应用
EdenX是字节内部的前端框架,外部可以使用Modern.js等价替代。
使用官方教程初始化一个EdenX项目:
1 | bunx @byted/create@latest edenx-bun # 选择yarn和rspack |
接着尝试使用Bun运行rspack的dev-server,使用bun run --bun
指令出现了以下错误:
值得注意的是,这个关于Module
错误出现频率较高,值得后续探究。
使用bun run dev
(Node运行时)后问题解决,可以正常运行,Bun作为包管理器的优势还是很大的。
测试现有应用
这里拿之前的一个EdenX应用测试,在删除node_modules
后使用Bun全新安装后rspack打包卡住。
猜测是依赖解析出现了问题,表现如下:
Git Commit时husky报错
使用
* run build
进行rspack打包时(包括npm、pnpm和bun)均会卡在中间60%左右的位置使用Bun安装依赖,使用任何包管理器
run build
都会卡住使用pnpm或yarn安装依赖,使用
bun run build
正常运行
Bun的技术栈和生态
- JSCore:Apple Webkit中使用的JS引擎
- Zig语言:low-level语言
插件和loader
⭐这里的插件和loader通常指将Bun作为构建器使用的场景,同时也要运行时能力作基础
由于Bun也可以兼职Bundler,因此也有一套基于esbuild的插件API,用于拦截导入行为并执行自定义操作。使用其他esbuild插件时应注意兼容性,因为Bun并不支持全部API。
同样地,Bun也支持loader,其定义也是Plugin
类型,用于拓展Bun支持的类型。
与Node + Bundler的方案不同,Bun中定义的插件可以无需构建转译过程,直接在运行时层面支持loader定义的类型。
横向对比
与Node生态的兼容性
Bun is designed as a drop-in replacement for Node.js. It natively implements hundreds of Node.js and Web APIs, including fs
, path
, Buffer
and more.
- 实现了Node中的JS API
- 完全兼容现有的
node_modules
的组织方式 bun create
==pnpm create
与Deno的对比
Bun和Deno采用了相似的技术栈,如下表所示,为什么Bun相对Deno仍有较大的性能领先(根据官网对比图)是值得探究的。
Bun | Deno | |
---|---|---|
主要语言 | Zig (C-like) | Rust |
编译平台 | llvm | llvm |
引擎 | JS Core | V8 |
- 标题: Bun 1.0 初体验体验:这口包子香吗?
- 作者: ChlorineC
- 创建于 : 2023-10-25 02:28:00
- 更新于 : 2024-10-18 18:03:42
- 链接: https://chlorinec.top/2023/10/24/Development/bun-hands-on/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。