1 引言
Deno 是什么?Deno 和 Node 有什么关系?Deno 和我有什么关系?
Deno 将于 2020-05-13 发布 1.0,如果你还有上面的疑惑,可以和我一起通过 Deno 1.0: What you need to know 这篇文章一起了解 Deno 基础知识。
希望你带着疑问思考,未来 10 年看今天,会不会出现 Deno 官方生态壮大,完全替代 Node 进而影响到 Web 生态的局面呢?这个思考结果会影响到你未来职业发展,你需要学会自己思考,并对这个思考结果负责。
2 介绍 & 精读
Deno 的作者是 Ryan Dahl,他是 Nodejs 背后的策划者,曾经说过 我对 Nodejs 感到遗憾的 10 件事。这也是为什么新开一个坑的原因,但 Deno 并不定位为 Nodejs 的替代品,从整体功能来看,Deno 有更大的野心,据我的推测是想要取代现在陈旧的前后端开发模式,让 Deno 一统前后端开发全流程。
Nodejs 是由 C 写的,而 Deno 则是由 Rust 写的,并选择了 Tokio 这个异步编程框架,并使用 V8 引擎解析 Javascript,并内置了对 Ts 的解析。
安装
Deno 支持如下安装方式:
Shell:
代码语言:javascript复制curl -fsSL https://deno.land/x/install/install.sh | sh
PowerShell:
代码语言:javascript复制iwr https://deno.land/x/install/install.ps1 -useb | iex
Homebrew:
代码语言:javascript复制brew install deno
Chocolatey:
代码语言:javascript复制choco install deno
脚本执行方式为 deno run
,可以类比为 node
,但功能不同且支持远程文件,实际上远程依赖是 Deno 的一大特色,也是有争议的地方:
deno run https://deno.land/std/examples/welcome.ts
在 ts 文件中允许用远程脚本加载资源,这个后面还会提到:
代码语言:javascript复制import { serve } from "https://deno.land/std@v0.42.0/http/server.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");
for await (const req of s) {
req.respond({ body: "Hello Worldn" });
}
安全性
Deno 是默认安全的,这体现在默认没有环境、网络访问权限、文件读写权限、运行子进程的能力。所以如果直接运行一个依赖权限的文件会报错:
代码语言:javascript复制deno run file-needing-to-run-a-subprocess.ts
# error: Uncaught PermissionDenied: access to run a subprocess, run again with the --allow-run flag
可以通过参数方式允许权限的执行,有 --allow-read
、--allow-write
、--allow-net
等:
deno --allow-read=/etc
上面表示 /etc
文件夹下的文件拥有文件读权限。
除了直接加参数调用、Bash 脚本调用外,还可以用 Make 运行,或者使用类似的 drake 启动。
或者使用 deno install
命令,将脚本转化为一个快捷指令:
deno install --allow-net --allow-read -n serve https://deno.land/std/http/file_server.ts
-n
表示 --name
,可以对这个脚本进行重命名,比如上面的例子中,serve
命令就等同于 deno run --allow-net --allow-read https://deno.land/std/http/file_server.ts
。
标准库
Deno 在标准库上很有特点,对常用功能提供了官方版本,保证可用性与稳定性。原文中列出了一些与 Npm 三方库的对比:
Deno Module | Description | npm | Equivalents |
---|---|---|---|
colors | Adds color to the terminal | chalk, kleur, and colors | |
datetime | Helps working with the JavaScript Date object | ||
encoding | Adds support for external data scructures like base32, binary, csv, toml and yaml | ||
flags | Helps working with command line arguments | minimist | |
fs | Helps with manipulation of the file system | ||
http | Allows serving local files over HTTP | http-server | |
log | Used for creating logs | winston | |
testing | For unit testing assertion and benchmarking | chai | |
uuid | UUID generation | uuid | |
ws | Helps with creating WebSocket client/server | ws |
从这个点上来看,Deno 既做运行环境又做基础生态,缓解了 Npm 生态下选择困难症,这件事需要辩证来看:集成了官方包对功能确定的模块来说是很有必要的,而且提高了底层库的稳定性;但 Deno 生态也有三方库,而且本质上三方库和官方库在功能上没有任何壁垒,因为实现代码都类似,唯一区别是谁能为其稳定性站台,假设微软和 Deno 同时出了基于 Npm 生态与 Deno 生态官方库,都保证会持续维护,你更相信谁呢?官方是否有优势要取决于官方自身的实力。
内置 Typescript
Deno 内置支持了 TS,因此不需要 ts-node
我们就可以用 deno run test.ts
运行 Typescript 文件。值得注意的是,Deno 内部也是利用 Typescript 引擎解析为 Js 后交由 V8 引擎解析,因此本质上没太大的变化,只是这样 Deno 的生态会更规范。
由于内置了 TS 支持,自然也不需要写 tsconfig.json
配置了,但你依然可以定制它:
deno run -c tsconfig.json [file-to-run.ts]
Deno 默认还开启了 TS 严格模式,所以看到这里,可以认为 Deno 是为了构建高质量理想库而诞生的运行环境,基于已有的生态来做,但做了更多内置技术选型,这和 Facebook 的 rome 很像,但做的却更彻底。
其实从实现上来看,我们基于 Javascript 生态也能写出 deno run test.ts
这样类似的引擎,只不过是由 JS 驱动执行,可能编译还会选择 Webpack,但 Deno 本身基于 Rust 实现,并重新实现了一套模块加载标准,可以说从更底层的方式重新解读了 W3C 标准规范,以期望解决 Javascript 生态的各种痛点问题。
支持 Web 标准
Deno 还支持 W3C 标准规范,因此像 fetch
、setTimeout
等 API 都可以被直接使用,如果你按照 Deno 支持的那几个函数写代码,可以保证在 Deno、Node、Web 三个平台实现跨平台运行。
虽然距离完全实现 W3C 所有标准规范还有一些路要走,但我们看到了 Deno 兼容规范的决心。
ESModule
模块化是 Deno 的亮点,Deno 使用官方 ESModule 规范,但引用路径必须加上后缀:
代码语言:javascript复制import * as log from "https://deno.land/std/log/mod.ts";
import { outputToConsole } from "./view.ts";
Deno 不需要申明依赖,代码的引用路径就是依赖申明,会包括完整的路径以及文件后缀,也支持网络资源,可以摆脱 NPM 中心化的包管理模式,因为这个路径可以是任何网络地址。
包管理
对于 import * as log from "https://deno.land/std/log/mod.ts";
这行代码,Deno 会下载到一个缓存文件夹,用户不会感知到这个文件夹与这个过程的存在,也就是说,Deno 环境中是没有 node_modules
的。
也可以通过 deno --reload
的方式强制刷新缓存。
但这里也要辩证的看待 “Deno 去中心化” 这件事,虽然引用了网络源,但会引发下面几个问题:
- 实际上还存在一个 "node_modules",只是用户看不到。
- 网络下载速度放到运行时,第一次启动还是很慢。
- 普通模式下无 lock,必须配合
deps.ts
使用,这个后面会提到。
即使被打上 “中心化恶人” 的 npm 也有去中心化的一面,因为 npm 支持私有化部署,无论是速度还是稳定性都可以由公司自己掌控,从稳定性来说还是 npm 拥有压倒性优势。
三方库
Deno 还有第三方库生态,截止目前共有 221 个三方库。
由于 Deno 走网络资源,我们可以借助 Pika 提供的 CDN 服务直接引用网络资源包:
代码语言:javascript复制import * as pkg from "https://cdn.pika.dev/preact@^10.3.0";
虽然这样看上去很轻量,但对公司来说还是需要自建一个 “Pika” 保障稳定性,以及做全球 CDN 缓存等的工作。
告别 package.json
npm 生态下包信息存放在 package.json
,包含但不限于下面的内容:
- 项目元信息。
- 项目依赖和版本号。
- 依赖还进行分类,比如
dependencies
、devDependencies
甚至peerDependencies
。 - 标记入口,
main
和module
,还有 TS 用的types
与typings
,脚手架的bin
等等。 - npm scripts。
随着标准的不断更新,package.json
信息已经非常臃肿了。
对于 Deno 来说,则使用 deps.ts
集中管理依赖:
export { assert } from "https://deno.land/std@v0.39.0/testing/asserts.ts";
export { green, bold } from "https://deno.land/std@v0.39.0/fmt/colors.ts";
deps.ts
就是一个普通文件,只是将项目的依赖精确描述出来,这样其他地方引用 assert
时,就可以这么写了:
// import { assert } from "https://deno.land/std@v0.39.0/testing/asserts.ts";
import { assert } from "./deps.ts";
如果需要锁定依赖,可以通过 deno --lock=lock.json
方式申明。
deno doc
deno doc <filename>
命令可以根据文件按照 JS Doc 规则生成文档,同时也支持 TS 语法,比如下面这段代码:
/** Asynchronously fulfill a response with a file from the local file
* system. */
export async function send(
{ request, response }: Context,
path: string,
options: SendOptions = { root: "" }
): Promise<string | undefined> {
// ...
}
生成文档如下:
代码语言:javascript复制function send(_: Context, path: string, options: SendOptions): Promise<string | undefined>
Asynchronously fulfill a response with a file from the local file system.
deno 本身文档就是用这个命令生成的,可以 访问官方文档 查看使用效果。
内置工具链
前端 Javascript 工具链相当混乱,虽然业界已有 Umi 等框架做了开箱即用的封装,但回到 Javascript 设计的初衷就是可以在浏览器直接使用的,包括浏览器对不依赖构建工具的模块化支持,注定了未来 Webpack 一定会被消灭。
Deno 通过内置一套工具链的方式解决这个问题,包括:
- 测试:提供
deno test
命令与Deno.test()
测试函数。 - 格式化:提供 vscode 插件。
- 编译:提供
deno bundle
命令。
不过值得注意的是,在最重要的编译环节,deno bundle
目前提供的能力是相对欠缺的,比如还不支持 Tree Shaking。
用 Rust 等语言提升构建效率是业界一直在尝试的事,比如 @陈成 就基于 esbuild 做了 @umijs/plugin-esbuild 插件用于提升 Umi 构建速度,但为了防止生产构建产物与 Webpack 默认规则不一致,仅使用了其压缩(minifier)功能。
对 deno 来说也一样,目前其实没有任何证据表明 deno 的构建结果可以完美适配 webpack 环境,所以请勿认为 deno 发布了 1.0 版本就等于可以在生产环境使用。
3 总结
正如原文结尾所说的,Deno 虽然将要发布 1.0 版本,但仍不能完全替代 Nodejs,这背后的原因主要是历史兼容成本,也就是完整支持整个 Node 生态不只是设计的问题,更是一个体力活,需要一个个高地去攻克。
同样 Deno 对 Web 的支持也让人耳目一新,但仍不能放到生产环境使用,除了官方和三方生态还在逐渐完善外,deno bundle
对 Tree Shaking 能力的缺失以及构建产物无法保证与现在的 Webpack 完全相同,这样会导致对稳定性要求极高的大型应用迁移成本非常高。
最亮眼的改动是模块化部分,依赖完全去中心化从长远来看是一个非常好的设计,只是基础设施和生态要达到一个较为理想的水平。
最后,让我们站在一个预言者角度思考一下 Deno 到底会不会火吧:
Deno 做的初心是做一个更好的 Node,但很不信,对于这种级别的生态底层工具来说,重新做一个并重新火起来的难度,不亚于重新做一个阿里巴巴并取代现在阿里的难度。也就是不同的时间点做同一件事,哪怕后者可以吸取教训,大概率也无法复制以前成功的路线。
从 Deno 的功能来看,解决了 Node 很多痛点,其中就包括去中心化管理,有点云开发的意思,但在 2020 年,基于 Nodejs 和 Webpack 的云开发都搞出来了,说实话是没有 Deno 什么空间的。从功能上来看,开篇就说了 Deno 基于 V8 解析 Javascript,对于性能和功能都没有革命性提升,从技术上作出突破也几乎不可能了。
Deno 的思想确实比 Node 先进,但不能说比 Node 好十倍,则无法撼动 Node 的生态,即便是 Node 作者自己可能也不行。
然而我上面说的可能都是错的。