大家好,很高兴又见面了,我是"web 前端分享",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
这段时间一个性能号称可以吊打Node和Deno的JavaScript运行时Bun(包子)横空出世,立即在JavaScript社区闹得沸沸扬扬。那么到底这个号称快到飞起的包子运行时到底几斤几两呢?这篇文章就让我们通过一些实际的例子来了解一下Bun提供的功能以及它比Node到底快了多少,最后再探讨一下Bun是不是可以替代Node。
我们先来看官方描述:
Bun is a fast all-in-one JavaScript runtime
Bundle, transpile, install and run JavaScript & TypeScript projects — all in Bun. Bun is a new JavaScript runtime with a native bundler, transpiler, task runner and npm client built-in.
简单来说Bun是一个大而全的JavaScript运行时。
它不止做了Node和Deno的工作(JavaScript运行时),而且还原生支持下面这些功能:
有同学看到这里可以会问:这些功能不是现在前端和node生态都支持了吗?有什么好学的?其实不然,Bun的特色其实不在这些功能,而是在原生和快这两个方面。原生就意味着这是Bun自带的,不需要写任何配置文件或者安装任何插件就可以用,这就降低了我们编写代码的成本以及编写效率。而Bun另外一个特点是快,那它有多么快呢?看一些官网的描述:
从上面的描述可以看出这些数据:
听起来确实有点快,到底实际效果如何呢?接着就让我们实际看一下。
这部分内容将会介绍Bun和Node或者Node生态其他工具如npm的对比,而不会涉及到Deno,因为Deno其实和Node的性能没有太大的区别。
作为一个前端工程师,我们经常需要使用Webpack等工具来对我们的代码进行transpile和bundle。和Node不同的是,Bun原生就支持了Bundler和Transpiler这些能力,而不用我们额外安装其它依赖。除了这些,Bun还内置了React脚手架的功能,你输入一个命令即可创建一个React应用:
bun create react ./react-app
上面的命令跑完后会生成一个这样目录结构的项目代码:
react-app├── node_modules├── README.md├── bun.lockb├── node_modules.bun├── package.json├── public│ ├── favicon.ico│ ├── index.html│ ├── logo192.png│ ├── logo512.png│ ├── manifest.json│ └── robots.txt└── src ├── App.css ├── App.jsx ├── images.d.ts ├── index.css ├── index.jsx ├── logo.svg └── reportWebVitals.js
从上面的目录结构来看,使用bun创建的React应用十分清爽,基本没有额外的配置文件,可以直接进行代码的编写。最重要的是,在笔者的渣渣电脑上跑这个命令只需要6.55s!。而相比之下使用npx create-react-app跑了足足四分钟才创建出一个React项目来。换句话来说Bun创建一个React项目的速度是用npm通过create-react-app创建速度的40倍!单纯从这个速度上看,Bun说自己快如闪电真的不足不为过。
上面生成的代码虽然只有JS文件,不过bun是原生支持TypeScript Transpiler的,所以你直接写TypeScript的代码也是可以运行的。
代码生成后,我们可以通过bun dev命令来启动一个监听在3000端口的带热更新的dev server。这个命令在我的电脑的执行时间是0.3s!,还没反应过来服务就起来的。相反使用create-react-app创建的项目用npm start耗时是25s。单纯看这个case的话,Bun task runner是npm task runner的83倍。
在你开发完代码之后运行bun bun这个命令就可以对项目打包了,这个命令会打包出一个带有.bun后缀的二进制文件,这个命令大概执行了0.1s,打包完的文件你直接扔到一个静态服务器就可以访问了。而相对之下,create-react-app创建的应用运行npm run build打包耗时28s,所以这个情况下,Bun的速度是npm的280倍。
这里值得一提的是,虽然bun打包文件相对于使用npm来打包文件快的不止一个数量级,不过bun还不支持minify和tree-shaking等常见的静态代码优化手段,这些都在它的RoadMap上面,后面会支持的。到时候加上了这些功能后还有没有这么快,就只能拭目以待了。
Bun对HTTP服务的支持是很好的,你可以编写极其精简的代码来启动一个Web服务:
// http.jsexport default { port: 3000, fetch(request) { return new Response("Hello World!") }}
上面的代码会在3000端口启动一个HTTP服务,这个服务会返回一个Hello World!字符串。在笔者的电脑(Macbook Pro 2.9GHz 双核 Intel Core i5 8GB内存)上使用wrk工具来测试一下这个服务的性能:
wrk -t 10 -c256 -d30s http://localhost:3000/
上面的命令会启动10个线程256个链接来连续测试我们的接口30s,最后得到的数据是:
Running 30s test @ http://localhost:3000/ 10 threads and 256 connections Thread Stats Avg Stdev Max +/- Stdev Latency 17.42ms 40.52ms 515.51ms 96.32% Req/Sec 2.33k 822.60 4.26k 65.55% 660630 requests in 30.08s, 57.33MB read Socket errors: connect 0, read 3, write 0, timeout 0Requests/sec: 21963.67Transfer/sec: 1.91MB
我们可以看到Bun原生的HTTP服务在我的电脑上的QPS可以达到21963.67,那么我们再来看一下Node的原生HTTP服务的性能可以到多少,代码如下:
const http = require('http')const server = http.createServer((req, res) => { res.end('Hello World!')})server.listen(3000, () => { console.log('server is up')})
上面的代码实现了和Bun的HTTP服务器一样的逻辑,只不过编写的代码多了一丢丢。在同样的电脑使用同样的参数来测试一下Node原生HTTP服务的性能得到的结果是:
Running 30s test @ http://localhost:3000/ 10 threads and 256 connections Thread Stats Avg Stdev Max +/- Stdev Latency 23.80ms 34.24ms 460.40ms 96.19% Req/Sec 1.31k 341.72 2.02k 82.15% 376228 requests in 30.07s, 46.64MB read Socket errors: connect 0, read 58, write 1, timeout 0Requests/sec: 12513.65Transfer/sec: 1.55MB
从上面的结果可以看到,Node原生的HTTP服务的QPS是12513。单纯从这个测试结果来看,Bun的性能大概是Node的1.75倍。这么一看虽然Bun是快点,不过也没有很夸张。不过这可能也和机器有关,因为我看了一下一些国外博主的测试结果,他们电脑的性能比较好,一样的测试代码,Bun的QPS可以达到200k,而Node只有20k。从他们的结果来看Bun的性能一下子比Node好了10倍,这真是质的飞跃了。所以在你的电脑上用Bun和Node实现一个简单的HTTP服务的性能差距又有多大呢?你还不抓紧试一下?
上面提到了,Bun内置了一个类似于npm的包管理器。使用Bun安装一个包(moment.js)的命令是:
bun install moment
上面的命令和npm install moment是一样的,会安装一个依赖到node_modules文件夹里面,这个命令在笔者电脑跑了2.6s,然后我们再来跑一下npm:
npm install moment
一样的电脑,npm跑了差不多12s,单纯从安装moment这个包的速度来看,bun的速度是npm的4.6倍。而国外博主一般测出的范围是4到80倍,这应该是因为不同依赖安装时间不同导致的。总的来说,Bun作为包管理工具来说真的比npm快了不少,至于和yarn以及pnpm比怎么样的话,有待读者自己验证并将结果留在评论区了。
Bun原生支持React的server-side rendering,使用Bun实现一个最简单的SSR是这样的:
import { renderToReadableStream } from "react-dom/server"const dt = new Intl.DateTimeFormat()export default { port: 3000, async fetch(request: Request) { return new Response( await renderToReadableStream( <html> <head> <title>Hello World</title> </head> <body> <h1>Hello from React!</h1> <p>The date is {dt.format(new Date())}</p> </body> </html> ) ) },}
访问localhost:3000可以得到:
不过一般人应该不会这么裸写SSR而是用NextJS这种现代化的框架。Bun也考虑到这点,所以它也原生支持Next脚手架。使用下面的命令可以创建一个Bun的Next应用:
bun create next next-app
上面的命令跑了在我电脑跑了20s,而使用npm运行create-next-app跑了10分钟。单纯从这个速度上看Bun是npm的30倍。
上面的命令会创建一个监听在3000端口的next应用:
我们同样尝试使用wrk来测试一下这个Next应用的并发性能:
wrk -t 10 -c256 -d30s http://localhost:3000/
测试过程中可以看到Bun应用在标准输出疯狂输出然后就奔溃了!没错是真的崩溃了
[603.38ms] / - 3 transpiled, 15 imports[604.97ms] / - 3 transpiled, 15 imports[613.77ms] / - 3 transpiled, 15 imports[618.38ms] / - 3 transpiled, 15 imports[621.86ms] / - 3 transpiled, 15 imports[623.29ms] / - 3 transpiled, 15 imports[648.52ms] / - 3 transpiled, 15 importsSegmentationFault at 0x0000000000000000----- bun meta -----Bun v0.1.10 (fe7180bc) macOS x64 21.6.0DevCommand: fast_refresh hot_module_reloading tsconfig filesystem_router framework public_folder bunfig Elapsed: 87754ms | User: 3341ms | Sys: 662msRSS: 127.34MB | Peak: 127.34MB | Commit: 67.15MB | Faults: 4894----- bun meta -----Ask for #help in https://bun.sh/discord or go to https://bun.sh/issues
具体原因不知道,不过官方文档也说了现在Bun对Next只是Partial Support,也就是部分支持,我们也就不深究原因了。接着我们来看一下一个使用Node跑的Next应用:
而使用create-next-app创建的运行在Node的Next应用是可以通过wrk的测试的,结果如下:
Running 30s test @ http://localhost:3000/ 10 threads and 256 connections Thread Stats Avg Stdev Max +/- Stdev Latency 922.74ms 200.38ms 2.00s 78.20% Req/Sec 30.48 22.34 160.00 80.72% 7635 requests in 30.09s, 31.90MB read Socket errors: connect 0, read 0, write 0, timeout 97Requests/sec: 253.71Transfer/sec: 1.06MB
Node的Next应用QPS可以达到253。这也得出一个结论就是现在Bun还没有完全支持Next的功能,并且很不稳定。
由于文章篇幅有限,这里就不全部列出Bun所有的功能了,总的来说Bun还有下面这些功能值得读者去自行探索:
从上面笔者的实际使用效果来看,Bun的确比Node快了不少,接着就让我们探索一下Bun比Node快的一些可能原因。
这个是Bun官方给出的原因。Bun使用了Apple的JavaScriptCore引擎而不是Google的V8引擎,JavaScriptCore是Safari底层的JS引擎,和V8比起来嵌入成本高一点。至于JavaScriptCore是不是真的比V8性能好的话,这个真的很难判断,我在网上没找到一些不同JS引擎Benchmark的对比数据,反而是一些国外博主实际测试了一下Hermes(Facebook出品),JavaScriptCore和V8,发现性能上来说Hermes 略好于 JavaScriptCore 略好于 v8。不过也就差了一点点,这也和测试代码有关,如果大家能有一些实际的比较权威的测试数据欢迎在留言区评论。
这个也是Bun官方给出的原因,我们先来看一下原话怎么说的:
Zig's low-level control over memory and lack of hidden control flow makes it much simpler to write fast software.
它的意思是Bun快的一个原因是使用了Zig这门对内存有更低层次的控制和隐藏控制流的语言。老实说要不是因为Bun我都不知道有这个语言的存在。我Google了一下这门语言,它大概借鉴了Rust的一些思想,目的是想改进C语言,而且发布时间非常短,最早发布于2016年,现在在TIOBE的语言受欢迎榜单连前50都没有。所以我对这门语言保持了怀疑的态度。
老实说官网给出的原因我觉得说服力是不够的。依我看来,可能是下面这些原因导致Bun目前性能好于Node
和之前另外一个Node替代者Deno比起来,我觉得Bun做得对的地方有两点。一个是兼容Node生态而不是推陈出新。这一点其实很重要,因为从Node 2009年发布而来已经在很多大公司使用实践了,拥有巨大的生态系统。Deno一开始压根就没有想着兼容Node,所以它的很多轮子都要重新造,并且现有Node应用迁移起来十分困难,所以你看看Deno v1也发布了两年多了,该用Node的还在用Node。
不过幸运的是,Deno团队也意识到了这些问题,在1.15后允许你使用std/node来运行部分Node应用。和Deno不同,Bun一开始就将兼容Node放在了第一位,虽然现在还没有做到完全兼容Node的API,不过这是它一直奋斗的目标,你想一下,如果有一天Bun做到Node应用无缝迁移,还有Deno什么事吗?
Bun做对的另外一个事是,它解决了一个Node生态的痛点,就是慢,特别是安装依赖和启动项目慢。这些是很重要也是很普遍的问题,所以它可以成为一个充足的理由让Node开发者愿意迁移到成熟的Bun。而相反Deno并没有解决这个问题,而是解决了一些我个人觉得不是那么普遍的问题,所以原来的Node开发者迁移到Deno动力也就不足了。
Bun和Deno比起来好那么多,那么是不是意味着它可以真正替代Node呢?先说结论,我觉得Bun不会替代Node,最起码未来几年不会。
在我看来,Bun存在下面这些问题。
上面在说Bun为什么这么快的原因时有提到Zig语言是一门很年轻而且并不受欢迎的语言。很年轻就意味着这门语言可能还会存在很多没经过实践检验的bug,不那么受欢迎就意味着这门语言的开发者群体少,也就意味着Bun的contributor的潜在开发人员少。这些原因都会阻碍Bun的发展。
在日常工作中,我发现如果一个人什么都想做好的话,往往会导致他最后什么都没做好,究其原因是因为一个人的精力是有限的。
虽然项目不同于人,不过也可以进行类比。Bun的目标很远大什么都想做,并且什么都想做好(你不做好别人也不会用)。这里有两个问题,一个是你真的是什么都可以做吗?按照官网的介绍来说Bun现在支持JSX,可是Vue和Angular的Transpile方式是和React不一样的,除了这两个框架还有Svelte等等其他框架而且未来可能还有更多新的框架,你真的可以每一个框架都支持吗?另外一个问题是什么都想做好,前面提到Zig语言的时候就说了,这是一门不那么受欢迎的语言,也就是说很少这方面优秀的编程专家,因此Bun后面开发起来估计会很吃力,总不能一直靠这几个人吧?既然开源出去了社区参与才是王道,而Zig语言的局限性就意味着能参与的优秀开发会很少,质量自然而然就比较难保证了。
相反我觉得Bun可以学习Unix的那种办法having small independent tools that do one thing well and still can play together的思想。换句话来说,就是不用什么都要做,而是提供一个类似于bun-core的核心,这个核心是一个JS运行时+包管理器,它保证了Bun飞快的JS运行速度,高效的包管理以及兼容所有的NodeAPI,因为这些才是它的核心。然后bun-core对外提供API支持外界扩展它的功能,这部分扩展的功能由对应的社区开发者开发,这样Bun才不会最后变成一个什么都做不好的庞然大物。
我觉得Bun暂时替代不了Node的最后一个原因是它目前还欠缺了很多核心的功能,所以远远达不到可以在生产环境使用的效果。在介绍Bun的Transpiler功能的时候,你也发现Bun还不支持Minify和Tree-Shaking等常用的功能,其实不止这样,如果你看一下Bun的RoadMap,你会发现它连百分之20的功能都没开发完,也就是说还需要很长很长的一段时间我们才能在实际中使用。
这篇文章我为大家介绍了一个全新的JS运行时Bun,它最大的特点就是快,不过我觉得大家现在大概了解这个技术并保持一定的关注程度即可,不用那么着急投入到学习中去,因为它未来的道路不确定性还是很大的,目前来说还替代不了Node。
原文地址:
https://segmentfault.com/a/1190000042362219原文作者:进击的大葱