前言
最近Nodejs作者Ryan Dahl发布了Deno 1.0正式版,圈子一下沸腾起来了。或许你早在两年前就听说了这个东西,但是也有人不知道这个东西是什么,干什么用的,所以今天我将为大家来简单的聊一下这个将来可能会推翻Node的新轮子。
正文
什么是Deno?
Deno是一个基于v8、rust和Tokio的Javascript/Typescript的安全运行时。它在内部嵌入了一个typescript的编译器。可以将typescript编译成js然后运行在v8上,并通过c libdeno实现js与rust的通信交互,当然deno也可以直接运行Javascript代码。
已经有了Node,为什么作者还要写一个Deno?
在过去的几年里,JS 标准引入了大量新的语法特性。影响最大的就是Promis和模块化。
对于Node来说,这两个东西支持的都不是很理想。由于历史原因,Node.js 必须支持回调函数,导致异步接口会有Promise和回调函数两种写法;同时,Node.js自己的模块CommonJS与ES模块化不兼容,这样就导致无法完全支持ES模块化。
另一个原因就是Node.js的模块管理工具Npm,逻辑越来越复杂;模块安装目录 npm_modules 非常庞杂,难以管理。Node.js也几乎没有安全措施,用户只要下载了外部模块,就只好听任别人的代码在本地运行,进行各种读写操作。
然后Node.js的功能也不完整,导致外部工具层出不穷,让开发者疲劳不堪:webpack,babel,typescript、eslint、prettier.....
So,由于上面这些原因,作者Ryan Dahl决定放弃Node.js,重新写一个替代品,来彻底解决这些问题。
如何安装Deno?
linux/mac
代码语言:javascript复制curl -fsSL https://deno.land/x/install/install.sh | sh
windows
代码语言:javascript复制iwr https://deno.land/x/install/install.ps1 | iex
详细可以参考官网查看:https://deno.land/
安全性
Deno默认安全。相比之下,Node.js默认拥有访问文件系统和网络的权限。
要在没有授权的情况下运行一个需要启动子进程的程序,比如:
代码语言:javascript复制deno run file-needing-to-run-a-subprocess.ts
运行后你会看到一条警示消息
代码语言:javascript复制error: Uncaught PermissionDenied: access to run a subprocess, run again with the --allow-run flag
相较于Node,Deno默认使用沙箱环境执行代码,这意味着运行环境没有操作以下模块权限:
- 环境
- 网络
- 文件系统读/写
- 运行子进程
必须使用参数,显式打开权限才可以,参数分别如下:
- --allow-read:打开读权限,可以指定可读的目录,比如--allow-read=/temp。
- --allow-write:打开写权限。
- --allow-net=google.com:允许网络通信,可以指定可请求的域,比如--allow-net=google.com。
- --allow-env:允许读取环境变量。
例如,要授予Deno对/etc目录的只读权限,可以这样:
代码语言:javascript复制deno --allow-read=/etc
模块机制
Deno使用浏览器一样的方式,通过URL来加载模块。很多人第一次见到在服务端的import语句中见到URL会感到有点困惑,但对我来说这还蛮好理解的:
代码语言:javascript复制import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
通过使用URL来加载模块,Deno就可以避免引入一个类似npm的中心化系统来发布package,npm最近受到了很多吐槽。
通过URL来引入代码,可以让包的作者们使用自己最喜爱的方式来维护和发布自己的代码。再也不会有package.json和node_modules了。
当我们启动应用之后,Deno会下载所有被引用的文件,并将它们缓存到本地。一旦引用被缓存下来,Deno就不会再去下载它们了,除非我们使用-- relaod标志位去触发重新下载。
只能使用URL来引用模块吗?
其实,你可以在本地文件中将已经引用的模块重新export出来,比如:
代码语言:javascript复制export { test, assertEquals } from "https://deno.land/std/testing/mod.ts";
假如上面这个文件叫utils.ts。现在,如果我们想再次使用test或者assertEquals方法,只需要像下面这样引用它们:
代码语言:javascript复制import { test, assertEquals } from './local-test-utils.ts';
另一种方式就是建一个引用映射表,比如像下面这样一个JSON文件:
代码语言:javascript复制{
"imports": {
"http/": "https://deno.land/std/http/"
}
}
然后import到代码里:
代码语言:javascript复制import { serve } from "http/server.ts";
最后为了让它生效,还需要通过--importmap让Deno来引入import映射表:
代码语言:javascript复制deno run --importmap=import_map.json hello_server.ts
Deno的命令
Deno内置了开发者需要的各种功能,不再需要外部工具。打包、格式清理、测试、安装、文档生成、linting、脚本编译成可执行文件等,都有专门命令。
- deno bundle 将脚本和依赖打包
- deno eval 执行代码
- deno fetch 将依赖抓取到本地
- deno fmt 代码的格式美化
- deno help 等同于-h参数
- deno info 显示本地的依赖缓存
- deno install 将脚本安装为可执行文件
- deno repl 进入 REPL 环境
- deno run 运行脚本
- deno test 运行测试
Deno的内部结构
下面是Deno的部分目录结构图
上图中圈出来的三个文件夹分别是
- js
- libdeno
- src
分别对应Deno的api层、中间层、和实现层;其中js中主要是typescript的代码,包含typescript的编译器和Deno暴露给用户的api。libdeno中主要是c 代码,用来加载v8实例,实现typescript和rust的通信。src文件中主要是rust的代码,是Deno功能的具体实现。例如用户使用File实例的write方法来写文件,实际上是api层(typescript)通过中间层(libdeno)将数据传输给实现层(rust),最终写文件操作由rust去完成。Deno结合了Typescript/Javascript的易用性和rust的系统语言能力。
此图可以清晰的表示js和rust之间的逻辑关系。
总结
就目前来看,Deno还是一个比较新的东西,功能不稳定,周围生态还不够完整。
So,还不能完全取代Node.js,建议先不要用于生产环境,但是现在已经是一个能够使用的工具了,有时间的话可以造造轮子,研究一下也不是不可的。