最近deno
发布了2.0
版本,主键开始兼容node
了,连同之前性能著称的bun
。我们重新来对比下这三个js的运行时。大多数时候官方都会宣传自己的优点,只有真正使用的时候才会发现有很多缺点。我个人尤其在意的是与node
的兼容性。像deno
早期版本甚至完全不兼容node
,也不能用npm
已有的包,这显然不会被大众接受。
只有很好的兼容了node
,才有可能被使用,如果不兼容node
,我为什么要去学习一个新的小众的东西来替换呢?我觉得性能和易用性都不足以说服让我放弃node
,因为选择js
运行时更多的是为了简单通用,而不是性能,不然为什么不选择用golang
呢。另外易用性上虽然node
项目要添加很多配置eslint
tsconfig
等,但是简单项目本来也可以不配置,且目前主流框架的脚手架都会帮忙配置好。
包管理与下载的兼容
node
采用package.json
文件来进行包管理,使用npm install
指令安装所有的依赖,依赖包的安装位置是当前目录下的node_modules
目录。有一些下载更快的衍生下载工具pnpm
yarn
等,不作为本文的讨论范围。deno
在2.0版本才兼容,使用deno install
安装,但是安装的库不在node_modules
目录,而是全局目录,为了多项目依赖共用。这一点上没兼容,但是并没有实质影响,也还好。bun install
则是完全兼容了node
的依赖安装。
我们以当前这个笔记项目为例来看下下载所有依赖需要多久。node(npm10.8.2)
(版本v20.17.0)用时33s。
deno2.0.0
因为是下载node项目,所以需要从npm上下载包,而且都是v8运行时,最终下载时间比node
更久是48s。
bun1.1.30
下载速度明显更快。
std库的兼容
写这样一段简单的js代码,用bun
deno
分别运行,会发现deno
是不识别require
的,导致无法正常运行,而bun
本着尽量兼容node
的思想,是能够正常运行的。
var fs = require('fs');
var path = require('path');
var content = fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8');
console.log(content);
虽然deno2
已经尽量去兼容node
了,但是代码还是需要有改动,对于node的包要用node:
前缀,而且需要用import
引入,而非require
。
-var fs = require('fs');
-var path = require('path');
+import fs from 'node:fs'
+import path from 'node:path'
+const __dirname = new URL('.', import.meta.url).pathname;
var content = fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8');
console.log(content);
虽然deno
的改动令人沮丧,但是bun
也并非完全兼容,当我用bun运行当前博客系统(next应用)的时候,会出现大量的下列报错。
NotImplementedError: worker_threads.Worker option "stderr" is not yet implemented in Bun.
NotImplementedError: worker_threads.Worker option "resourceLimits" is not yet implemented in Bun.
反倒是deno
在运行next
项目的时候并没有报错是正常运行的。
如何选择
我个人感觉抛开兼容性的问题,性能、编译、易用这些都是扯淡。所以我还是会使用node
。
如果是为了尝试下的话,我可能更愿意尝试下deno
,因为他有免费的云平台,可以托管一些小的应用。bun
,虽然性能确实很好,但实在是不想花时间来踩坑去用来部署服务.
此外,bun
没有打包独立运行的二进制文件的能力,nodejs
有pkg
工具,虽然vercel
官方已经不维护,最多支持到node18
版本,但是有人fork
了这个项目,继续提供新版本的支持。deno
自带了这个能力,需要deno init
初始化当前项目,然后deno compile xxx.js
即可。编译出来的文件大小会发现deno
的要更大一些(89M vs 54M),可见他的运行时核心应该比node
要大。