边学边译JS工作机制—28. Deno 一瞥

本系列其他译文请看[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/denodenotest.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/denodenotest.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的后台运行时。现在说还为时尚早,但是如果真的这样,我还是很期待的。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享