襁褓中的 deno(零):初次见面,多多关照

襁褓中的 deno(零):初次见面,多多关照

Tips: deno 项目现在属于飞速发展的阶段,源码随时可能更新,所以这篇文章中的代码只是作为说明需要,和项目中的代码不保持同步。展示代码只保留核心,异常检查等会酌情删去。

最近前端风起云涌,Ryan Dahl 大佬的 deno 甫一开源就引起了巨大的关注,甚至引发了一些不太好的刷 issue 事件。不过 deno 这一席华美的袍之下,到底隐藏着什么呢?这个系列文章就结合源码来对 deno 的做一点简单的分析与梳理,也算是一份学习笔记了。


deno 是什么?

这个问题非常好回答,在 deno 的 GitHub 主页 上有着非常明确的定义:A secure TypeScript runtime on V8,也就是一个基于 v8 的 TypeScript 运行时。单纯看定义,可能有些盆友会发出「每个字都能看懂但是合在一起就不懂」的感慨,不过我们可以采用类比法来理解这个定义。

在 Node.js 的 GitHub 主页上,写着一段相似的定义:Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js 是一个基于 v8 的 JavaScript 运行时。这么一对比就非常明显了, deno 和 node 的定位相似,都是一个「运行时」,而且底层都是基于知名的 JS 引擎 v8,不过一个支持 TS,一个支持 JS。举个最简单的例子,我们可以写出这样的代码:

// hello.ts
const name: string = "yingying";
console.log(name);

然后我们可以用 deno 的可执行文件来运行这个 hello.ts 代码文件

 $ deno ./hello.ts
 yingying

有个很有意思的点,Node 和 deno 在名字上只是简单地调换了一下字母顺序,这更是说明了两者的兄弟关系 :) 。


deno 有什么特点?

Node 之父选择再做一个定位与 Node 类似的运行时出来,这肯定是有很多理由的,正巧在 jsconf 2018 上,Ryan Dahl 专门阐述了他设计 deno 的原因,感兴趣的可以阅读 Design Mistakes in Node 这篇 PPT,我这里只是针对 deno 的主要特点进行简单的描述和解释。

安全

这里所说的安全不止是二进制代码安全,还包括比较容易被人忽视的 IO 安全。

在 Node 中我们可以利用 vm 来构建沙盒运行环境,然后调用 vm 上的相关方法来执行相应操作。而在 deno 中则是隔离地更为彻底,我们只能通过消息的发送与接收来进行系统调用,将恶意代码完全隔绝在实现层(go、c++)而不是调用层(ts)之中。

除此之外,在 deno 中运行的 ts 代码将默认没有网络请求和文件读取的权限,用户必须在运行代码的时候手动加上 --allow-net--allow-write 等 flags 来开启相应的权限,这样可以更安全地运行第三方的代码。

简化模块系统

Ryan 认为 Node 的模块系统太过繁琐复杂,package.json 文件更是包含了过多的冗余信息,所以在 deno 中,模块系统选择向 go 看齐,通过 url 或者相对路径来引用文件,并且需要指定文件的格式,而不再像 Node 中的利用 resolve 算法 来进行文件路径补全。通过网络加载的文件会被缓存,下次引入会直接加载缓存文件。而模块的版本控制则是通过在包名中加入语义化版本号来实现,比如 https://unpkg.com/deno_testing@0.0.5/testing.ts, 去中心化的版本控制可以实现更细粒度的依赖管理。

面向 TypeScript

没有人不喜欢 TypeScript

在 8102 年,TypeScript 已经毫无争议地成为了(前端)新项目开发和老项目重构的 dream language,关于它的优点够再写一篇文章了,所以这里就按下不表。deno 中 主要是修改了一些 TS compiler 中的钩子函数,以支持新的模块查找策略。比如 fileExists:

class TypeScriptHost implements ts.LanguageServiceHost {
    // ts 转换器的钩子函数之一
    fileExists(fileName: string): boolean {
      // 这里的 resolveModule 就是自定义的模块解析函数
      const m = resolveModule(fileName, ".");
      const exists = m != null;
      util.log("fileExist", fileName, exists);
      return exists;
    }
}

deno 的不足

deno 本身虽然说是「TS runtime」,但是由于基于的运行引擎是一个 JS 的引擎(V8),所以需要在 runtime 保持一个 ts compiler,来自动地将 ts 转译为 js ,最后交给 v8 来执行,这无疑增加了启动性能与运行的性能,关于这一点的讨论可以参见 Compare bootstrap speed with Node.js

与此同时,我们在编写代码的时候是具有相当多的类型信息的,但是在执行代码时却不得不将这些「宝藏」丢弃掉。最后小心翼翼地让 v8 去做 JIT,收集我们本来就知道的「feedback vector」,这实在是让人有点恼火。如果能有一个土豪的大厂(暗示某软),愿意和 deno 合作推出一个 ts engine,叫个 v8s 什么的,利用丰富的静态信息去做 AOT,再结合一些 v8 的 JIT 优化,那速度妥妥更上一个台阶。


总结一下

本篇文章主要是简单介绍了 deno 的定位、特点与不足,由于现在项目还处于原型阶段,所以相关的介绍也比较少,建议希望深入了解的可以参照 deno 的 GitHub ,以及 Ryan 大佬在 jsconf 上的演讲 PPT Design Mistakes in Node

下一篇的内容暂定为 deno 中 go 和 js 的交互方式,希望自己不要太鸽(咕咕咕。

编辑于 2018-06-04