编辑|覃云 全文来自 Node 之父 Ryan Dahl 于 2018 年 6 月在柏林 JS 大会上的演讲,他主要讲述了过去他在设计 Node 时犯的一些错误,包括 Node 安全、构建系统(GYP)、package.json 等方面上的问题,并阐述了开发新项目 Deno 背后的一些故事、原因和未来规划。 背景介绍
我在 Node 早期开发阶段就参与了相关创建与管理工作。
我的目标主要在集中在编程事件驱动的 HTTP 服务器方面。这一关注重点随后被证明正是服务器端 JavaScript 的关键所在。尽管当时表现得并不明显,但事实证明服务器端 JS 必须配合事件循环才能成功实现。
在 2012 年离开时,我认为 Node 已经(或多或少)实现了其既定目标:为用户提供一套友好的无阻塞框架:
-
核心支持多种协议: HTTP、SSL 以及更多
-
能够运行在 Windows(IOCP)、Linux(epoll)以及 Mac(kqueue)之上。
-
核心相对较小,且 API 较为稳定。
-
通过 NPM 实现持续发展的外部模块生态系统。
但我错了,该项目仍有大量工作需要完成……
推动 Node 持续增长的关键性工作成果npm (即‘Isaac’) 对 Node 库进行解耦,同时允许生态系统实现分发能力。
-
N-API 是一种设计精美的绑定 API。
-
Ben Noordhuis 与 Bert Belder 共同打造了 libuv。
-
Mikeal Rogers 负责组织治理与社区工作。
-
Fedor Indutny 在整个代码库中拥有巨大的影响力,特别是在加密方面。
-
还有众多其他贡献者:TJ Fontaine, Rod Vagg, Myles Borins, Nathan Rajlich, Dave Pacheco, RobertMustacchi, Bryan Cantrill, Igor Zinkovsky, Aria Stewart, Paul Querna, Felix Geisendörfer, TimCaswell, Guillermo Rauch, Charlie Robbins, Matt Ranney, Rich Trott, Michael Dawson, James Snell
我在过去 6 个月当中才刚刚开始使用 Node。
在这段时间内,我的目标发生了变化。动态语言成为理想的科学计算工具,允许用户借此快速实现一次性计算。JavaScript 是最好的动态语言。
我开始抱怨 Node 中的种种不足。成为负责人之后,你才会意识到 bug 问题有多么严重且不可回避。Node 曾经无数次令人如坐针毡,Node 本来可以做得更好。
遗憾:未能信守约定。我曾经在 2009 年 6 月为 Node 制定了不少约定,但却愚蠢地在 2010 年 2 月将其清除。
约定包括实现异步 / 等待的必要抽象机制,在 Node 当中统一约定使用可能会加快最终标准化与异步 / 等待机制的交付速度。
目前,Node 当中的不少异步 API 因此而严重老化。
遗憾:安全性V8 本身是一款非常出色的安全沙箱。
如果当时能够更多考虑到特定应用程序的维护方法,Node 本来可以实现其它任何语言都无法比拟的出色安全能力。
举例来说:您的 linter 不应完全访问计算机与网络。
遗憾:构建系统(GYP)构建系统非常困难且非常重要。
V8(通过 Chrome)最初使用 GYP,我之后连同 Node 整体对接 GYP。
后来的 Chrome 版本弃用 GYP 并转而选择 GN,这使得 Node 成为惟一的 GYP 用户。
GYP 并不是那种糟糕的内部接口——其面向任何试图绑定至 V8 的用户开放。
这给用户带来了糟糕的使用体验,这不是 JSON,而是 Python 改编版本的 JSON。
对 GYP 的坚持可能是 Node 核心的最大失误所在。
-
我不该引导用户面向 V8 编写 C++ 绑定,而应为用户提供核心外函数接口(FFI)。
-
很多早就建议过转向 FFI(Cantrill 就曾多次提出),遗憾的是我忽略了这些金玉良言。
-
(我对于 libuv 采用 autotools 非常不满意。)
在 NPM 当中,isaac(在很大程度上)发明了 package.json。
但我通过允许 Node 的 require() to 检查 package.json 文件作为“main”的方式,默许了 package.json 的存在。
最终,我将 NPM 包含在 Node 发行版当中,这使其成为客观层面的标准方案。
遗憾的是,其中存在一套面向各模块的集中(私有控制)库。
require("somemodule") 并非专有。其负责的定义范围过于广泛。
Package.json 的存在衍生出新的概念,即“模块”可作为文件目录。
这不是一种绝对必要的抽象机制——而且在以往的 Web 当中也从未出现。
package.json 如今包含着各种各样的非必要信息。许可?库?描述?总而言之,这就是一堆噪音样本的集合。
如果在导入时仅使用相关文件与 URL,则路径本身就能够定义版本——而不再需要列出依赖关系。
遗憾:node_modules它极大地提高了模块解析算法的复杂度。
vendored-by-default 的出发点是好的,但其实际上只是在使用 $NODE_PATH 所未能排除的模块。
极大偏离了浏览器语义。这是我的错,非常抱歉。遗憾的是,如今已经无法将其剔除出去了。
遗憾:require("module") 没有加上扩展名“.js”本应采取更明确的表达方式,浏览器 JavaScript 的工作方式与之不符。我们无法在脚本标记 src 属性当中忽略“.js”。
模块加载器必须从多个位置尝试查询文件系统以猜测用户意图。
遗憾: index.js我认为这样的命名很可爱,因为已经存在 index.html……其会给模块加载系统带来不必要的复杂性因素。
在要求支持 package.json 之后,index.js 变得毫无存在意义。
我在 Node 项目当中遇到的问题,基本都与管理用户代码有关。与早期关注 I/O 均衡工作相反,该模块系统在本质上其实是一种事后考量方案。
考虑到这一点,我一直在反思如何使其变得更好……
免责声明: 这里,我打算提出一种非常新颖的原型设计思路。除非您急于立即投身于 Ildb 的探索,否则请不要尝试构建。
Deno一套立足 V8 的安全 TypeScript 运行时。
Deno 目标:安全性利用 JavaScript 本身即为安全沙箱这一事实:
-
在默认情况下,脚本应在不产生任何网络或文件系统写入访问的前提下运行。
-
用户可通过标记介入访问: --allow-net --allow-write
-
这种方式允许用户运行各类不受信实用程序(例如 linter)。
不允许将任意本地函数绑定至 V8 当中:
-
所有系统调用都将通过消息传递完成(protobuf 序列化)。
-
两项原生函数:send 与 recv。
-
这既简化了设计流程,又使得系统更易于审计。
-
不再尝试与现有节点模块相兼容。
-
仅导入相对或绝对 URL。
import { test } from "https://unpkg.com/deno_testing@0.0.5/testing.ts"
import { log } from "./util.ts"
导入操作必须提供扩展名。
-
远程 URL 在首次加载时被无限期获取取及缓存。
-
只有在提供—reload 标记的情况下,方可再次获取资源。
-
可以通过指定非默认缓存目录以完成交付。
TS 非常强大。
-
TS 的出现终于带来了一种实用的可选类型语言选项。
-
允许代码实现从快速构建到大型架构的良好无缝扩展。
Deno 钩入 TS 编译器以实现模块解析与构建结果的增量缓存。
未修改的 TS 文件不应进行重新编译。
普通 JS 也应正常起效(这一点非常简单,因为 TS 属于 JS 的一个超集)。
应该利用 V8 快照实现快速启动(目前尚未纳入原型设计)。
Deno 目标:只发送一个具有最低关联度的可执行文件> ls -lh deno
-rwxrwxr-x 1 ryan ryan 55M May 28 23:46 deno
> ldd deno
linux-vdso.so.1 => (0x00007ffc6797a000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f104fa47000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f104f6c5000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f104f3bc000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f104f1a6000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f104eddc000)
/lib64/ld-linux-x86-64.so.2 (0x00007f104fc64000)
Deno 目标:发挥 2018 年的技术优势
通过将包含 Parcel 的 Node 模块编译为一个绑定包的形式,实现运行时引导。(这将给 Node 的使用流程带来显著简化。)
以原生代码方式提供强大的基础设施:
EG 不再需要为 HTTP 担心。已经有其它机制负责实现相关功能。(此前 Node 无法实现这样的效果,Web 服务器还 100% 需要手动操作。)
目前,Deno 中的非 JS 部分以 Go 语言编写而成,但各项替代方案还没有全面完成原型设计。
-
Rust 可能是个不错的选择。
-
C++ 可能也是个好选项,因为其允许其他用户构建自己的 Deno 目标在 Go 或者 Rust 上?
在非处理的约定中总会立即关闭(令人无法理解的是,Node 竟然不提供这样的机制)。
支持最高级等待(尚未在原型设计中实现)。
在功能交集范围内实现浏览器兼容。
需要强调的是,此项目才刚刚诞生一个月,而且还无法实际使用。但我对迄今为止的设计成果感到相当满意。
欢迎大家发表评论、问题以及关注的其它重点。ry@tinyclouds.org
链接Deno GitHub:https://github.com/ry/deno
原文链接:http://tinyclouds.org/jsconf2018.pdf