全新的JavaScript runtime
写在前面
2020年5月13日,Deno终于正式发布了。Deno是基于V8 JavaScript引擎和Rust编程语言的JavaScript和TypeScript运行时。它是由Node.js之父Ryan Dahl创建的,专注于安全性和生产力。
为什么会有Deno
已经有了Node.js,为什么还要搞一个Deno呢?按Ryan Dahl在2018年的一个演讲,他在设计Node.js的时候,犯了几个"错误"(演讲PPT我之前收集过,在公众号回复 前端学习 即可自行获取)。这几个"错误"是:
没有坚持使用Promise。
没有足够的安全性。
构建系统没有从GYP(node-gyp)切到GN。
继续使用GYP,没有提供Foreign Function Interface (FFI)的模式。
package.json(依赖了npm)。
在任何地方都可以require('module')。
package.json提供了错误的模块概念。
node_modules黑洞。
require('module') 没有加上扩展名'.js'。
默认加载index.js。
于是,Ryan Dahl决定开发一个新的JavaScript运行时。2年多后,Deno发布了。官网地址:
https://deno.land/
Deno初体验
赶紧来体验一下Deno。
安装
macOS下直接curl即可:curl -fsSL https://deno.land/x/install/install.sh | sh
更多安装方式,请移步官网
下载完成后,将deno加入系统环境变量。首先
> vi ~/.bash_profile
接着,加入以下两行:
export DENO_INSTALL="/Users/pankeyu/.deno"
export PATH="$DENO_INSTALL/bin:$PATH"
最后,执行:
source ~/.bash_profile
然后,就可以在命令行愉快的执行deno了:命令行
deno命令行的用法和node差不多。
想要执行脚本,直接deno run ${script}即可。这里的script,也可以是一个线上文件。
直接执行代码,可以 deno eval "console.log(30933 + 404)"。
deno支持以下命令:
bundle Bundle module and dependencies into single file
cache Cache the dependencies
completions Generate shell completions
doc Show documentation for a module
eval Eval script
fmt Format source files
help Prints this message or the help of the given subcommand(s)
info Show info about cache or info related to source file
install Install script as an executable
repl Read Eval Print Loop
run Run a program given a filename or url to the module
test Run tests
types Print runtime TypeScript declarations
upgrade Upgrade deno executable to given version
更多信息,可以使用deno help查看。
从上面的bundle、test、fmt命令我们可以看出来:deno原生支持打包,测试,格式化。
使用deno创建一个http server
我们使用官方的例子:
// server.ts
import { serve } from "https://deno.land/std@0.50.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 server.ts,命令行输出:
可以看到,直接报错了。这是deno的安全机制,需要加上--allow-net这个参数,可以访问网络。
执行deno --allow-net server.ts,命令行输出如下:
> deno --allow-net server.ts
Compile file:///Users/pankeyu/Desktop/server.ts
Download https://deno.land/std@0.50.0/http/server.ts
Download https://deno.land/std@0.50.0/encoding/utf8.ts
Download https://deno.land/std@0.50.0/io/bufio.ts
Download https://deno.land/std@0.50.0/testing/asserts.ts
Download https://deno.land/std@0.50.0/async/mod.ts
Download https://deno.land/std@0.50.0/http/_io.ts
Download https://deno.land/std@0.50.0/io/util.ts
Download https://deno.land/std@0.50.0/path/mod.ts
Download https://deno.land/std@0.50.0/path/win32.ts
Download https://deno.land/std@0.50.0/path/posix.ts
Download https://deno.land/std@0.50.0/path/common.ts
Download https://deno.land/std@0.50.0/path/separator.ts
Download https://deno.land/std@0.50.0/path/interface.ts
Download https://deno.land/std@0.50.0/path/glob.ts
Download https://deno.land/std@0.50.0/path/_constants.ts
Download https://deno.land/std@0.50.0/path/_util.ts
Download https://deno.land/std@0.50.0/fmt/colors.ts
Download https://deno.land/std@0.50.0/testing/diff.ts
Download https://deno.land/std@0.50.0/path/_globrex.ts
Download https://deno.land/std@0.50.0/async/deferred.ts
Download https://deno.land/std@0.50.0/async/delay.ts
Download https://deno.land/std@0.50.0/async/mux_async_iterator.ts
Download https://deno.land/std@0.50.0/textproto/mod.ts
Download https://deno.land/std@0.50.0/http/http_status.ts
Download https://deno.land/std@0.50.0/bytes/mod.ts
http://localhost:8000/
浏览器打开http://localhost:8000/,就可以看到输出的Hello World了。
也许你会疑惑为什么要加一个--allow-net的参数。这是Deno的安全策略。
从上面的例子可以看到,启动脚本时,deno会实时下载依赖到本地,下载完成后,再执行脚本逻辑。当我们control+C退出后,再次执行脚本,命令行输出:
> deno --allow-net server.ts
http://localhost:8000/
这一次不会再下载依赖了。
deno在第一次下载后,将依赖保存到了本地,并且这些依赖代码本身对用户是不可见的。这点和Node.js的node_modules完全不同。
如果我们想重新下载依赖,需要在执行脚本的时候加上--reload:deno --allow-net --reload server.ts。
如果我们想查看脚本的依赖树,需要执行deno info server.ts :
> deno info server.ts
local: /Users/pankeyu/Desktop/server.ts
type: TypeScript
compiled: /Users/pankeyu/Library/Caches/deno/gen/file/Users/pankeyu/Desktop/server.ts.js
map: /Users/pankeyu/Library/Caches/deno/gen/file/Users/pankeyu/Desktop/server.ts.js.map
deps:
file:///Users/pankeyu/Desktop/server.ts
└─┬ https://deno.land/std@0.50.0/http/server.ts
├── https://deno.land/std@0.50.0/encoding/utf8.ts
├─┬ https://deno.land/std@0.50.0/io/bufio.ts
...
上面的简单例子还体现出下面几点:
deno原生支持了typescript。实际上,deno内置了ts引擎,会将ts代码解析为js代码后,交给v8运行。
deno原生支持了top-level-await。
启动上面的示例脚本时,如果没有加上--allow-net这个flag,是会报错的。可以看出deno在安全方面的考量。
deno的https server性能
Deno 是一个合适的异步服务器,每秒 25k 请求足以满足大多数目的,此外,由于普遍使用 Promise,Deno 需要有更好的尾部延迟。目前 Deno HTTP 服务器每秒处理约 25 000 个请求,最大延迟为 1.3 毫秒,与之相比,Node 程序每秒处理 34 000 个请求,最大延迟介于 2 到 300 毫秒之间。
这样看来,作者认为 Deno 的 HTTP 服务器还有更多的性能优势,并表示希望在将来的版本中实现这一目标。
deno的http server性能可以在这里查看:https://deno.land/benchmarks
deno中的依赖与模块
在Node中,提供了许多的内置模块,如:
const fs = require('fs');
const path = require('path');
...
在deno中,也提供了不少的内置模块,但是并不支持Node一样的引入方式,而是挂在Deno这个全局变量上。看一个例子:
// denoFs.js
const readFile = Deno.readFile;
const serverBuffer = await readFile('./server.ts');
console.log(serverBuffer);
执行脚本:
> deno run --allow-read denoFs.js
Uint8Array(213) [
105, 109, 112, 111, 114, 116, 32, 123, 32, 115, 101, 114, 118, 101,
32, 125, 32, 102, 114, 111, 109, 32, 34, 104, 116, 116, 112, 115,
58, 47, 47, 100, 101, 110, 111, 46, 108, 97, 110, 100, 47, 115,
116, 100, 64, 48, 46, 53, 48, 46, 48, 47, 104, 116, 116, 112,
47, 115, 101, 114, 118, 101, 114, 46, 116, 115, 34, 59, 10, 99,
111, 110, 115, 116, 32, 115, 32, 61, 32, 115, 101, 114, 118, 101,
40, 123, 32, 112, 111, 114, 116, 58, 32, 56, 48, 48, 48, 32,
125, 41,
... 113 more items
]
deno的模块化完全遵循了es module。从前面的例子可以看出,deno可以直接import线上的资源包。对于本地资源,使用本地路径引入即可,但是必须要带上资源后缀(.ts,.js)。import就是申明依赖。
// world.ts
export const world:string = 'world';
// hello.ts
import { world } from './world.ts';
console.log(`Hello ${world}`);
执行脚本deno run hello.ts:
> deno run hello.ts
Compile file:///Users/pankeyu/Desktop/hello.ts
Hello world
deno的内置工具
前面我们提到,deno原生支持打包,测试,格式化等。我们来试一试吧。
打包
我们使用上面的denoFs.js作为例子。
> deno bundle denoFs.js denoFs.output.js
Bundling file:///Users/pankeyu/Desktop/deno/denoFs.js
Emitting bundle to "denoFs.output.js"
2482 bytes emmited.
最终输出了一个denoFs.output.js文件,大概长成下面的样子,这个js文件也是可以直接由deno运行的。
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
// This is a specialised implementation of a System module loader.
// @ts-nocheck
/* eslint-disable */
let System, __instantiateAsync, __instantiate;
(() => {
const r = new Map();
System = {
register(id, d, f) {
r.set(id, { d, f, exp: {} });
},
};
async function dI(mid, src) {
...
}
function gC(id, main) {
...
}
function gE(exp) {
...
}
function rF(main) {
...
}
async function gExpA(id) {
...
}
function gExp(id) {
...
}
__instantiateAsync = async (m) => {
...
};
__instantiate = (m) => {
...
};
})();
"use strict";
const readFile = Deno.readFile;
const serverBuffer = await readFile("./server.ts");
console.log(serverBuffer);
__instantiate("denoFs");
测试
我们使用上面的world.ts作为例子。
// world.ts
export const world:string = 'world';
deno会从当前目录开始,逐级向上读取文件名为{*_,}test.{js,ts,jsx,tsx}文件作为测试文件。我们先写好测试用例:
// world.test.ts
import { world } from "./world.ts";
Deno.test("env", () => {
if (world !== 'world') {
throw Error("wrong!");
}
});
执行 deno test:
Compile file:///Users/pankeyu/Desktop/deno/.deno.test.ts
running 1 tests
test env ... ok (4ms)
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (4ms)
格式化代码
假设我们待格式化的代码为:
// server.ts
import { serve } from "https://deno.land/std@0.50.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 fmt server.ts后,代码格式化完成:
// server.ts
import { serve } from "https://deno.land/std@0.50.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" });
}
总结
通过上面的探索,结合之前Ryan Dahl提到的Nodejs的"设计错误",可以稍微总结一下deno了。
1、deno重新实现了模块化机制,采用去中心化的设计,支持直接import线上的资源,不再像Node一样依赖npm,摆脱了node_modules。同时,deno官方也提供了一个第三方库仓库:https://deno.land/std/。
2、deno的内置模块是挂在全局变量上的。
3、deno内置了typescript解析引擎,原生支持typescript。并且,deno也在拥抱W3C的规范(deno支持fetch)。
4、deno默认是安全的。从上面的例子中就可以看出,想要访问网络,访问文件系统等,都需要加上特定的参数才可以。
5、deno原生支持打包,测试,代码格式化等操作,旨在提高生产效率。
deno可以说是在重塑之前Nodejs的开发模式,其设计思想相比于Node.js,确实有进步的地方。对比作者之前提到的几条Node.js的"设计错误",deno一一解决了。
deno让人眼前一亮的去中心化模块依赖,或许可以让前端cdn、生产环境自动化部署等技术得到进一步发展。不过deno想要达到Node.js的稳定性以及繁荣的生态,deno还有很长的路要走。
写在后面
本文通过一个例子,不完整地对deno进行了介绍。Ryan Dahl大号练废了,又开了一个小号修炼。那么,这一次,你认为deno会火吗?