## 1 引言 Deno 是什么?Deno 和 Node 有什么关系?Deno 和我有什么关系? Deno 将于 2020-05-13 发布 1.0,如果你还有上面的疑惑,可以和我一起通过 [Deno 1.0: What you need to know](https://blog.logrocket.com/deno-1-0-what-you-need-to-know/) 这篇文章一起了解 Deno 基础知识。 希望你带着疑问思考,未来 10 年看今天,会不会出现 Deno 官方生态壮大,完全替代 Node 进而影响到 Web 生态的局面呢?这个思考结果会影响到你未来职业发展,你需要学会自己思考,并对这个思考结果负责。 ## 2 介绍 & 精读 Deno 的作者是 Ryan Dahl,他是 Nodejs 背后的策划者,曾经说过 [我对 Nodejs 感到遗憾的 10 件事](https://www.youtube.com/watch?v=M3BM9TB-8yA)。这也是为什么新开一个坑的原因,但 Deno 并不定位为 Nodejs 的替代品,从整体功能来看,Deno 有更大的野心,据我的推测是想要取代现在陈旧的前后端开发模式,让 Deno 一统前后端开发全流程。 Nodejs 是由 C++ 写的,而 Deno 则是由 Rust 写的,并选择了 [Tokio](https://tokio.rs/) 这个异步编程框架,并使用 V8 引擎解析 Javascript,并内置了对 Ts 的解析。 ### 安装 Deno 支持如下安装方式: **Shell:** ```shell curl -fsSL https://deno.land/x/install/install.sh | sh ``` **PowerShell:** ```shell iwr https://deno.land/x/install/install.ps1 -useb | iex ``` **Homebrew:** ```shell brew install deno ``` **Chocolatey:** ```shell choco install deno ``` 脚本执行方式为 `deno run`,可以类比为 `node`,但功能不同且支持远程文件,实际上远程依赖是 Deno 的一大特色,也是有争议的地方: ```shell deno run https://deno.land/std/examples/welcome.ts ``` 在 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 是默认安全的,这体现在默认没有环境、网络访问权限、文件读写权限、运行子进程的能力。所以如果直接运行一个依赖权限的文件会报错: ```shell 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` 等: ```shell deno --allow-read=/etc ``` 上面表示 `/etc` 文件夹下的文件拥有文件读权限。 除了直接加参数调用、Bash 脚本调用外,还可以用 Make 运行,或者使用类似的 [drake](https://deno.land/x/drake/) 启动。 或者使用 `deno install` 命令,将脚本转化为一个快捷指令: ```shell 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` 配置了,但你依然可以定制它: ```shell deno run -c tsconfig.json [file-to-run.ts] ``` Deno 默认还开启了 TS 严格模式,所以看到这里,可以认为 Deno 是为了构建高质量理想库而诞生的运行环境,基于已有的生态来做,但做了更多内置技术选型,这和 Facebook 的 [rome](https://github.com/facebookexperimental/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 规范,但引用路径必须加上后缀: ```ts 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 个三方库](<[](https://deno.land/x/)>)。 由于 Deno 走网络资源,我们可以借助 [Pika](https://www.pika.dev/cdn) 提供的 CDN 服务直接引用网络资源包: ```jsx 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` 集中管理依赖: ```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` 时,就可以这么写了: ```ts // 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 ` 命令可以根据文件按照 JS Doc 规则生成文档,同时也支持 TS 语法,比如下面这段代码: ```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 { // ... } ``` 生成文档如下: ```text function send(_: Context, path: string, options: SendOptions): Promise Asynchronously fulfill a response with a file from the local file system. ``` deno 本身文档就是用这个命令生成的,可以 [访问官方文档](https://doc.deno.land/) 查看使用效果。 ### 内置工具链 前端 Javascript 工具链相当混乱,虽然业界已有 Umi 等框架做了开箱即用的封装,但回到 Javascript 设计的初衷就是可以在浏览器直接使用的,包括浏览器对不依赖构建工具的模块化支持,注定了未来 Webpack 一定会被消灭。 Deno 通过内置一套工具链的方式解决这个问题,包括: - 测试:提供 `deno test` 命令与 `Deno.test()` 测试函数。 - 格式化:提供 [vscode 插件](https://marketplace.visualstudio.com/items?itemName=axetroy.vscode-deno)。 - 编译:提供 `deno bundle` 命令。 不过值得注意的是,在最重要的编译环节,`deno bundle` 目前提供的能力是相对欠缺的,比如还不支持 Tree Shaking。 用 Rust 等语言提升构建效率是业界一直在尝试的事,比如 @陈成 就基于 [esbuild](https://github.com/evanw/esbuild) 做了 [@umijs/plugin-esbuild](https://umijs.org/zh-CN/plugins/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 作者自己可能也不行。 然而我上面说的可能都是错的。 > 讨论地址是:[精读《Deno 1.0 你需要了解的》 · Issue #248 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/248) **如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。** > 关注 **前端精读微信公众号** > 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))