以MacOS系统为例,说明安装的不同
涉及的文件:node
, npm
, npx
, node_modules
文件夹
安装方法一
原生Node包安装会需要 root权限,安装到 /usr/local/bin
,然后每一个 npm i -g
全局安装都会需要 root 权限!
安装方法二
使用nvm,安装多个node版本到 ~/.nvm
目录,同时需要修改 .bashrc
或 .bash_profile
来激活某个版本,严重拖慢启动bash
的速度。
安装方法三 全手动安装,涉及到一堆环境变量的修改和位置依赖,弄不好容易变成坑。
涉及环境变量:
NODE_HOME
(主目录)NODE_PATH
(全局模块目录)NPM_CONFIG_PREFIX
(NPM目录)NPM_CONFIG_CACHE
(NPM缓存)NPM_CONFIG_REGISTRY
(NPM中心仓库)- 还有很多...
由于Deno只有一个可执行文件,Deno官方安装其实就是下载它,1.0版本解压后为42M,下载即安装。官方脚本默认安装位置:~/.deno/bin/deno
,用任何方法安装都可以,只要你能运行得到。
涉及环境变量:
DENO_DIR
(Deno主目录, 不设置的话默认为~/.deno
)DENO_INSTALL_ROOT
(deno install
命令的输出目录,不设置的话默认为~/.deno/bin
)NO_COLOR
(是否启用控制台颜色)HTTP_PROXY
&HTTPS_PROXY
(代理)
Node运行时按node_modules
查找算法查找node_modules
,模块安装下载与依赖管理主要依靠npm
,另外还有:cnpm
,pnpm
, tnpm
, bnpm
, yarn
......
package.json
是模块管理入口文件,文件内容为JSON
格式,依赖包存放在dependencies
内。
npm install
是更新依赖包的命令。
没有独立的依赖管理工具,外部可以使用 HTTP 协议
来下载并缓存模块文件,社区中建议使用一个 deps.ts
文件来存放所有的外部文件,如下
// deps.ts
export * as server from "https://deno.land/[email protected]/http/server.ts";
但 以上用法并非官方!,官方建议使用 import_map.json
,但 它是非稳定的!
// import_map.json
{
"imports": {
"http/": "https://deno.land/std/http/"
}
}
// import { serve } from "http/server.ts";
然后:deno run --allow-net --unstable --importmap=import_map.json main.ts
来引入。
注意以上 --unsatble
参数是必须的,因为它模拟了浏览器标准的Import Map规范,但目前其状态是非官方草案,将来随时可能会发生变化。
关于缓存
建议设置 DENO_DIR
环境变量,若此变量不为空则缓存目录为$DENO_DIR
,否则会用系统缓存目录:
macOS: ~/Library/Caches/deno
Windows: ~/AppData/Local/deno
缓存通常包含目录:deps
(下载的依赖文件缓存,以内容hash命令) 和 gen
(编译后的js生成物)
删除上面两个文件即可达到清除缓存效果。
Linux | Node | Deno |
---|---|---|
Processes | require('process') | Web Workers |
Syscalls | require('v8) / Addons | Ops |
File descriptors (fd) | via process.stdxx , fs.open |
Resource ids (rid) |
Scheduler | libuv | Tokio |
Userland: libc++ / glib / boost | https://www.npmjs.com | https://deno.land/std/ |
/proc/$$/stat | process.report | Deno.metrics() |
man pages | node docs | deno types |
以下以Node经典API为例说明deno对应的用法。
注意: Deno跟Node不是同一个东西,以下只是列出可以在功能上达到相似效果的一些用法,便于理解,可以参考,不可照搬,切记!
Deno使用标准ES6 Modules,而Node使用了CommonJS2,虽然13.2.0兼容了ES6 Modules,但两种方式混用的情况在前端领域很常见,非常不利于维护。
常见require在Deno的对应
require.main -> import.meta.main
__filename -> import.meta.url
__dirname -> 核心无对应 (需借助外部模块实现)
一些Deno的node polyfill
import { createRequire } from "https://deno.land/std/node/module.ts";
const require = createRequire(import.meta.url);
// 加载原生模块polyfill
// 可加载列表:https://deno.land/std/node#supported-builtins
const path = require("path");
// 可加载不带后缀模块,自动解析后缀/目录.
const cjsModule = require("./my_mod");
// 可兼容Node经典的node_modules查找算法
const leftPad = require("left-pad");
console.log(leftPad, cjsModule);
console.log(path.join('root', 'xx.js'))
// 实现上面的__dirname
const __dirname = path.dirname(import.meta.url)
注意: https://deno.land/std/node/module.ts
模块使用了不稳定API,要用以下命令运行:
deno run --unstable --allow-read main.ts
另外还有以下独立模块不需加 --unstable
:
https://deno.land/std/path 对应于 path模块
https://deno.land/std/fs 对应于 fs模块
https://deno.land/std/bytes 可实现Buffer.concat()
https://deno.land/std/http 对应于 http模块
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
assertEquals("world", "world");
import EventEmitter, { on } from "https://deno.land/std/node/events.ts";
const testEmitter = new EventEmitter();
testEmitter.on("event", () => {
console.log("event fired");
});
testEmitter.emit('event');
注意Node的Buffer
实现是在ArrayBuffer
标准提出之前的,所以并不标准,Deno.Buffer
尽量向标准靠拢,两者有本质区别!某些API有相似之处。
Deno.Buffer
实现其实是基于Go Buffer
(https://golang.org/pkg/bytes/#Buffer)的。
Deno.Buffer is NOT the same thing as Node's Buffer. Node's Buffer was created in 2009 before JavaScript had the concept of ArrayBuffers. It's simply a non-standard ArrayBuffer.
Node示例代码:打印 0x0102030405060708
的Uint64
值
// buf 长度固定
const buf = Buffer.from(new Uint8Array([1,2,3,4,5,6,7,8]));
const d = new DataView(buf.buffer).getBigUint64(0);
console.log(buf.length, ',', d);
//output: 8, 72623859790382856n
// buf 长度可变
const buf = new Deno.Buffer();
// buf.write 会改变 Buffer 的长度,向 buf 后追加数据
await buf.write(new Uint8Array([1,2,3,4,5,6,7,8]));
const d = new DataView(buf.bytes().buffer).getBigUint64(0);
console.log(buf.length, ',', d);
//output: 8, 72623859790382856n
以上只是做为Demo示例,说明Node中Buffer
是定长内存块,而Deno.Buffer
是可变长内存块,所以 Deno
有以下API而 Node 没有:
buf.grow(n) //变长n字节
buf.truncate(n) //保留n字节
buf.reset() //相当于 buf.truncate(0)
Node要增加只能通过拼接一个新的Buffer:
let newBuf = Buffer.concat(buf, Buffer.alloc(10));
另外,由于历史包袱,Node现在的Buffer API看着非常乱!
对应的几个常用的代码:
process.argv -> Deno.execPath() + import.meta.url + Deno.args //注意Deno拆成了三部分
process.abort() -> 无
process.env -> Deno.env.toObject() // 需要--allow-env
// 以下相同
process.exit(1) -> Deno.exit(1)
process.chdir(dir) -> Deno.chdir(dir)
process.execPath() -> Deno.execPath()
process.pwd() -> Deno.pwd()
process.pid -> Deno.pid
process.stderr -> Deno.stderr
process.stdout -> Deno.stdout
process.stdin -> Deno.stdin
process.version -> Deno.version
Node会有exec
和spawn
,Deno只有run
:
//Node
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const { stdout } = await exec('echo hello');
console.log(`stdout: ${stdout}`);
//Deno
const p = Deno.run({
cmd: ["echo", "hello"],
stdout: 'piped',
stderr: 'piped',
});
const outBuf = new Deno.Buffer()
// p.stdout!是一个Deno.Reader
await outBuf.readFrom(p.stdout!)
const stdout = new TextDecoder().decode(outBuf.bytes().buffer)
console.log(`stdout: ${stdout}`);
可以看到Deno的代码量其实更大。
Stream的实现, Node与标准几乎完全不同,Deno完全按标准实现了一遍,两者对应如下:
Writable 对应于 标准的WritableStream
Readable 对应于 标准的ReadableStream
Duplex and Transform Streams 对应于 Deno的TransformStream
不见得哪个更优秀,目前Node已有的大量基于Stream的库才是王道。
基本上Node使用C++扩展,而Deno使用Rust
与V8通信,Node使用N-API,Deno内建了 插件 通信机制
向系统扩展,Node使用C++ Addons,Deno使用Rust插件
以下代码复制到命令行执行,可以感受一下Deno进行系统扩展的方便:
deno run -A -r --unstable https://deno.land/x/webview/examples/multiple.ts
因为Deno拥抱了浏览器生态与标准,所以浏览器中有但Node中无的东西基本都是Deno的优势,比如:Fetch API
具体可以参考Deno文档。
另外Deno内置了 window
对象,这样做同构应用(Isomorphic Application)方便不少。
还有就是原生TypeScript支持,做大项目工程化,协作利器。
主要分为运行时,网络I/O几个方面,详情可见官网Benchmarks,以及Deno 1.0发版时关于性能的说明,总体来说,如果特别关心性能,要考虑一下使用JS运行时是否真的合适。
执行时间官网只有Deno自身的数据,冷启动Deno要编译TS到JS,热启动是从缓存中直接执行JS,关键指标解读:
执行时间:
冷启动本地导入模块并编译
0.5s
,热启动100ms
, 起50个Worker0.5s
,给4个Worker发400条消息(round_robin)200ms
,下图:
官方没有提供Node对比数据,我们就以运行一个.js
文件(Deno此时为热启动)10次,做个简单的对比:(macOS 10.14, Node版本v12.16.3)
$ echo 'console.log("Hello World!");' > hello.js
$ time for i in {1..10}; do deno run hello.js; done
real 0m0.362s
user 0m0.171s
sys 0m0.088s
$ time for i in {1..10}; do node hello.js; done
real 0m0.475s
user 0m0.358s
sys 0m0.108s
系统+用户时间: Node 466ms
, Deno 259ms
,热启动Deno更快些。
内存占用:
冷启动本地导入模块并编译
68M
,热启动20M
,起50个Worker157M
,给4个Worker发400条消息(round_robin)43M
,下图:
官方没有提供Node对比数据,我们就以运行一个.js
文件(Deno此时为热启动),统计一下RSS:(macOS 10.14, Node版本v12.16.3)
$ echo 'console.log("Hello World!");' > hello.js
$ /usr/bin/time -lp deno run hello.js
... ...
16207872 maximum resident set size
$ /usr/bin/time -lp node hello.js
19189760 maximum resident set size
最大RSS: Node 18.3M
,Deno 15.5M
,两者相差不大,Deno略小。
HTTP吞吐:
deno_core_http_bench
是指在rust中使用deno_core
模块,处理JS发出的请求hyper
是rust原生开发的http server
QPS:
deno_core_http_bench
最高91K
,hyper为76K
,node_http
为37K
,deno_http
最低为27K
,其中Deno为Node的72%
,下图:
HTTP延迟:
node_http
延迟最高2ms - 65ms
,波动非常大,deno_http
一直稳定在1ms - 3ms
之间,波动较小,hyper
延时最小,下图:
Deno现有的库比Node少很多,常用的对应如下:
- nodemon -> denon
- express -> denotrain
- koa -> oak
- hapi -> pogo
- commander -> denomander
- ejs -> dejs
- webpack -> 原生替代:
deno bundle myLib.ts myLib.bundle.js
- prettier -> 原生替代:
deno fmt
npm scripts
-> denox / velociraptor
另外纯JS库像 lodash
, moment
等库,理论上基本直接能用或做很少的工作就能用。
更多的,可以在官网搜索
The images: