本系列其他译文请看[JS工作机制 – 小白1991的专栏 – 掘金 (juejin.cn)] (juejin.cn/column/6988…
本文阅读指数:3
Deno出来一段时间了,之前了解的并不多。本文算是科普性质,提供了一些点,都是日常做技术选择关注的点。可以一读
总览
Deno是一个JS和TS的安全运行时。这一章简单介绍一下Deno,跟Node的区别,解析一些机制比如模块,包,异步,TS,安全和工具。
Deno起源
Ryan Dahl’s在2018年的JSConfEU中“10 Things I Regret About Node.js”中宣布了Deno。开发者看到自己的项目受众越来越多,被使用在各个地方,并从项目的失误中学习。正常的开发者会去提高或者重构源项目。但是Ryan Dahl更激进,他害怕破坏掉兼容性。
但是Deno还是很年轻的,2020年5越才发布正是第一个版本。
对比一下Deno 和 Node
Deno vs. Node.js
下面是主要的区别。稍后我们会深入到机制。但是目前还是从功能性和开发者体验上说
#内置包管理 vs npm
node需要Npm来做包管理。Deno则从GO和RUST中学习,使用URL进行包引用。
ES modules vs. CommonJS modules
Node 使用CommonJS 规范:
const module = require(‘module-name’)
Deno 使用标准的 EcmaScript modules:
import module from ‘https://some-repo/module-name.ts‘
注意Deno需要模块的全部名词,包括扩展名。
基于权限的访问 vs. 全文访问
Node可以全文访问环境,文件系统和网络。这是严重的安全漏洞。恶意的npm模块可以轻易访问这些资源。
Deno需要明确的权限,这样就能限制一些不好的行为。
内置TS编译器 vs. 外部支持
Node不能直接使用YS。你需要使用一些很重的工具链近期编译
Deno则天生支持TS,使用起来更加流畅
Promises vs. 回调
Node使用不阻塞的I/O,然后当I/O操作完成时,需要用回调来处理通知
Deno则使用async/await 模式,隐藏了回调链的复杂性,然代码更干净。
异常死亡 vs. 未捕获异常
在Node中你可以写一个全局的句柄来处理所有未捕获的异常
process.on(‘uncaughtException’, function (err) {
console.log(‘ignoring…’);
})
在Deno时,如果有未捕获异常,程序就会死掉。这个决策很重要。
深入的看一下Deno的一些特性:
模块和包管理
Deno通过URL引入模块,这就不需要 package.json 和 node_modules。这就意味着,没有缓存了,你只用下载一次包和模块。比如:
import { assertEquals } from "https://deno.land/std@0.93.0/testing/asserts.ts";
assertEquals(2 + 2, 5);
console.log('success!')
复制代码
可以看到,因为2+2等于4,这个程序会出错。Deno也是这么认为的
$ deno run ./assert.ts
error: Uncaught AssertionError: Values are not equal:
[Diff] Actual / Expected
- 4
- 5
throw new AssertionError(message);
^
at assertEquals (deno.land/std@0.93.0/…)
at file:///Users/gigi.sayfan/git/deno_test/assert.ts:3:1
改一下
import { assertEquals } from "https://deno.land/std@0.93.0/testing/asserts.ts";
assertEquals(2 + 2, 4);
console.log('success!')
复制代码
ow, it succeeds:
$ deno run ./assert.ts
Check file:///Users/gigi.sayfan/git/deno_test/assert.ts
success!
OK了。
看一下包和导入
import { assertEquals } from “deno.land/std@0.93.0/…“;
这里通过URL导入了一个assertEquals symbol,这是在Deno标准库中做的事情。
注意URL中包含了版本信息,所以自然支持同一个包的不同版本。
Deno维护了一个规划中的包列表deno.land 但你可以从任何url中导入包
可以尝试着从GitHub的URL上导入一个包
import { assertEquals } from “raw.githubusercontent.com/denoland/de…“
异步支持
Deno的异步API会返回promise.这样你可以运行你的异步操作,然后await结果,而不用去处理一团乱麻的回调函数
比如:
const promise = Deno.run({cmd: ['deno', 'eval', 'console.log(2+3)']})
await promise.status()
/*
Output:
5
*/
复制代码
我们使用 Deno.run() 运行了一个子进程。子进程是另一个Deno的实例,然后我们传递了表达式console.log(2+3) ,它会在控制台输出5
对于运行时间比较长的任务,为了不阻塞当前线程,我们可以使用await promise.status()
Deno 和 TS
Deno内置了对TS的支持,这样你就不需要工具链来进行编译了。Deno自带了一个TS的编译器,并把转换后的TS模块放在缓存中。当TS文件改动时,会被重新编译一下,保持更新。
查看缓存位置,和已有的模块信息:
$ deno info
DENO_DIR location: “/Users/gigi.sayfan/Library/Caches/deno”
Remote modules cache: “/Users/gigi.sayfan/Library/Caches/deno/deps”
Emitted modules cache: “/Users/gigi.sayfan/Library/Caches/deno/gen”
Language server registries cache: “/Users/gigi.sayfan/Library/Caches/deno/registries”
安全
Deno自下而上的保证安全,控制用户可以访问的权限。
网络,环境,文件系统默认是不可访问的资源。比如,我们尝试写一个文件:
假如代码放在 write_file.ts中.
Deno.writeTextFileSync('data.txt', 'some data')
复制代码
运行一下:
$ deno run write_file.ts
Check file:///Users/gigi.sayfan/git/deno_test/write_file.ts
error: Uncaught PermissionDenied: Requires write access to “1.txt”, run again with the — allow-write flag
Deno.writeTextFileSync(‘data.txt’, ‘some data’)
^
at unwrapOpResult (deno:core/core.js:100:13)
at Object.opSync (deno:core/core.js:114:12)
at openSync (deno:runtime/js/40_files.js:32:22)
at writeFileSync (deno:runtime/js/40_write_file.js:24:18)
at Object.writeTextFileSync (deno:runtime/js/40_write_file.js:82:12)
at file:///Users/gigi.sayfan/git/deno_test/write_file.ts:1:6
抛出一个权限错误,告诉我们需要增加一个flag值。然后我们重新运行一下:
$ deno run — allow-write write_file.ts
Check file:///Users/gigi.sayfan/git/deno_test/write_file.ts
$ cat data.txt
some data
现在可以了。因此,要小心运行你的Deno代码。
工具
Deno很注重开发者体验,所以提供了很多工具。看看有哪些:
格式化
假如有文件 fmt-test.ts:
function foo()
{
console.log('foo here')
const x = 3
console.log('x + 2 =', x+2)
}
foo()
复制代码
它是TS的,但是代码写的很乱
我们执行一个命令 deno fmt:
$ cat fmt_test.ts | deno fmt –
我们会得到:
function foo() {
console.log("foo here");
const x = 3;
console.log("x + 2 =", x + 2);
}
foo();
复制代码
效果不错
测试
测试的重要性无容置疑,Deno不需要借用第三方的框架,只要使用自己的断言模块就可以。
我们再文件test-test.ts中定义了一个函数is_palindrome() ,用来检查字符串是否回文,然后加一些测试。前两哥测试应该通过,而第三个应该失败。
import { assert } from "https://deno.land/std@0.95.0/testing/asserts.ts";
function is_palindrome(s: string) {
const ss = s.replaceAll(' ', '')
const a = ss.split('')
return a.reverse().join('') == ss
}
await Deno.test("Palindrome 1 - success", () => {
assert(is_palindrome("tattarrattat"));
})
await Deno.test("Palindrome 2 - success", () => {
assert(is_palindrome("never odd or even"));
})
await Deno.test("Palindrome 3 - fail", () => {
assert(is_palindrome("this is not a palindrom"), "fail!")
})
复制代码
运行 deno test 命令:
$ deno test test_test.ts
Check file:///Users/gigi.sayfan/git/deno_test/test.ts
running 3 tests
test Palindrome 1 — success … ok (1ms)
test Palindrome 2 — success … ok (1ms)
test Palindrome 3 — fail … FAILED (2ms)
failures:
Palindrome 3 — fail
AssertionError: fail!
at assert (deno.land/std@0.95.0/…)
at file:///Users/gigi.sayfan/git/deno_test/test_test.ts:19:3
at asyncOpSanitizer (deno:runtime/js/40_testing.js:37:15)
at resourceSanitizer (deno:runtime/js/40_testing.js:73:13)
at Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:100:15)
at TestRunner.[Symbol.asyncIterator] (deno:runtime/js/40_testing.js:272:24)
at AsyncGenerator.next ()
at Object.runTests (deno:runtime/js/40_testing.js:347:22)at async file:///Users/gigi.sayfan/git/deno_test/test.ts:3:1
failures:
Palindrome 3 — fail
test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (4ms)
构建
构建可以让所有的包集成在一个bundle中。Deno提供了bundle命令。
假如foobar.ts 模块从 foo.ts中引入了 **foo()函数,从bar.ts.中引入了bar()**函数
foo.ts:
export function foo() {
console.log('foo')
}
复制代码
bar.ts:
export function bar() {
console.log('bar')
}
复制代码
foobar.ts:
import { foo } from "./foo.ts"
import { bar } from "./bar.ts"
foo()
bar()
复制代码
Let’s bundle them all up into a single file:
$ deno bundle foobar.ts
Bundle file:///Users/gigi.sayfan/git/deno_test/foobar.ts
Check file:///Users/gigi.sayfan/git/deno_test/foobar.ts
Here is the result:
function foo() {
console.log('foo');
}
function bar() {
console.log('bar');
}
foo();
bar();
复制代码
可以看到,import声明已经不见了,foo() 和 bar() 被直接嵌入到一个bundle文件中了。
调试
运行时使用 — inspect 或者**— inspect-brk** 标记,就可以使用Chrome DevTool来进行调试。个人推荐使用 JetBrains IDEs 和一些deno插件。
如果使用VSCode,就需要手动配置一下launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Deno",
"type": "pwa-node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "deno",
"runtimeArgs": ["run", "--inspect-brk", "-A", "${file}"],
"attachSimplePort": 9229
}
]
}
复制代码
脚本安装
使用命令行经常会要传递很多额外的参数,不太方便。你可以使用 deno install来做一些启动脚本,把这个脚本放在你想放的位置,或者**$HOME/.deno/bin**
我们安装一个foobar
$ deno install foobar.ts
✅ Successfully installed foobar
/Users/gigi.sayfan/.deno/bin/foobar
ℹ️ Add /Users/gigi.sayfan/.deno/bin to PATH
export PATH=”/Users/gigi.sayfan/.deno/bin:$PATH”
我在PATH环境变量中增加了**$HOME/.deno/bin**。这样我可以在任意位置执行foobar,运行脚本
$ cd /tmp
$ foobar
foo
bar
Deno 机制
Deno是基于Rust和TypeScript来实现的。主要的组件有这些:
- deno
- deno_core
- tsc
- swc
- rusty_v8
[deno]创建了可执行的deno,我们与之交互。
[deno_core]创建JS运行时。Deno使用了 Tokio 来实现异步事件循环。
tsc是一个TS的标准编译器。也用来做类型检查。
swc用来加速Web编译,现在主要编译你的JS和TS代码。
[rustry_v8]则使用Rust与V8的C++ API绑定。
综述
Deno很年轻,但是不可小觑。它基于Node的经验和教训来设计的。它的技术提升远远超过了Node,并且使用了更加现代的技术栈。最大的问题是它会不会在成为JS和TS的后台运行时。现在说还为时尚早,但是如果真的这样,我还是很期待的。