11

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:

curl -fsSL https://deno.land/x/install/install.sh | sh

PowerShell:

iwr https://deno.land/x/install/install.ps1 -useb | iex

Homebrew:

brew install deno

Chocolatey:

choco install deno

脚本执行方式为deno run,可以类比为node,但功能不同且支持远程文件,实际上远程依赖是 Deno 的一大特色,也是有争议的地方:

deno run https://deno.land/std/examples/welcome.ts

在 ts 文件中允许用远程脚本加载资源,这个后面还会提到:

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 World\n" });
}

安全性

Deno 是默认安全的,这体现在默认没有环境、网络访问权限、文件读写权限、运行子进程的能力。所以如果直接运行一个依赖权限的文件会报错:

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 三方库的对比:

image.png

从这个点上来看,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 标准规范,因此像fetchsetTimeout等 API 都可以被直接使用,如果你按照 Deno 支持的那几个函数写代码,可以保证在 Deno、Node、Web 三个平台实现跨平台运行。

虽然距离完全实现 W3C 所有标准规范还有一些路要走,但我们看到了 Deno 兼容规范的决心。

ESModule

模块化是 Deno 的亮点,Deno 使用官方 ESModule 规范,但引用路径必须加上后缀:

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 去中心化” 这件事,虽然引用了网络源,但会引发下面几个问题:

  1. 实际上还存在一个 "node_modules",只是用户看不到。
  2. 网络下载速度放到运行时,第一次启动还是很慢。
  3. 普通模式下无 lock,必须配合deps.ts使用,这个后面会提到。

即使被打上 “中心化恶人” 的 npm 也有去中心化的一面,因为 npm 支持私有化部署,无论是速度还是稳定性都可以由公司自己掌控,从稳定性来说还是 npm 拥有压倒性优势。

三方库

Deno 还有第三方库生态,截止目前共有221 个三方库 "%5B%5D(https://deno.land/x/)")。

由于 Deno 走网络资源,我们可以借助Pika提供的 CDN 服务直接引用网络资源包:

import * as pkg from "https://cdn.pika.dev/preact@^10.3.0";

虽然这样看上去很轻量,但对公司来说还是需要自建一个 “Pika” 保障稳定性,以及做全球 CDN 缓存等的工作。

告别 package.json

npm 生态下包信息存放在package.json,包含但不限于下面的内容:

  • 项目元信息。
  • 项目依赖和版本号。
  • 依赖还进行分类,比如dependenciesdevDependencies甚至peerDependencies
  • 标记入口,mainmodule,还有 TS 用的typestypings,脚手架的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> {
  // ...
}

生成文档如下:

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 作者自己可能也不行。

然而我上面说的可能都是错的。


黄子毅
7k 声望9.5k 粉丝