Deno 1.18 发布(官文翻译)

376 阅读9分钟

作者:Hugo

链接: zhuanlan.zhihu.com/p/460474421

来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

原文链接:Deno 1.18 Release Notes

作者:Bartek Iwańczuk, Luca Casonato

全文:9969 字,时间 20 分钟。

如果你已经安装了 Deno,你可以通过下面的命令升级到 1.18:

(sudo)deno upgrade

如果你是第一次安装,你可以使用下面的方法:

# 使用 Shell  (macOS and Linux):
curl -fsSL https://deno.land/x/install/install.sh | sh

# 使用 PowerShell  (Windows):
iwr https://deno.land/x/install/install.ps1 -useb | iex

# 使用 Homebrew (macOS):
brew install deno

# 使用 Scoop (Windows):
scoop install deno

# 使用 Chocolatey (Windows):
choco install deno

新功能和变化

Web 加密接口全部完成

经过我们 6 个月的努力,终于完成了全部网络加密接口的功能。大家可以在 Deno 上使用完整的网络加密功能。

Deno 通过了 98.1% 的网络加密接口的网络平台测试集。以下是目前具体的数据:Chrom/Edge 通过了 94.5%,Firefox 通过了 93.4%,Safari 通过了 99.8%。你可以通过这个数据来进行你自己的测试。

本次发布增加了以下接口:

  • crypto.subtle.encrypt:
    • AES-GCM support
    • AES-CTR support
  • crypto.subtle.decrypt:
    • AES-GCM support
    • AES-CTR support
  • crypto.subtle.wrapKey:
    • AES-KW support
  • crypto.subtle.unwrapKey:
    • AES-KW support
  • crypto.subtle.importKey:
    • EC P-384 support
  • crypto.subtle.exportKey:
    • Support for ECDSA and ECDH pkcs8/spki/jwk exports

目前还有非常不常见的特性没有支持,例如 P-521 elliptic curve keys。

感谢 Sean Michael Wykes 实现了这次的特性。

自动发现配置文件

我们继续迭代在 v1.14 版本引入的配置文件的功能。在之前,引用配置文件,需要加上 --config 标签。

从这个版本开始,Deno 会自动寻找 deno.json 或者 deno.jsonc 配置文件。你仍然可以显示增加 --config 来指定这个文件。

在 v1.18 之前。

$ deno run --config ./deno.json ./src/file1.js
$ deno fmt --config ./deno.json
$ deno lint --config ./deno.json

v1.18

$ deno run ./src/file1.js
$ deno fmt
$ deno lint

对于不需要指定文件名的子命令(例如 deno fmt),Deno 会在当前文件夹向上遍历文件夹找配置文件。对于需要指定文件名的子命令(例如 deno run ./src/file1.js),Deno 会在入口文件附件找配置文件,也会向上遍历文件夹寻找。

以下面这个文件目录为例:

/dev
    /deno
         /my-project
                    /src
                        /file1.js
                        /file2.js

如果当前的工作目录是 /dev/deno/my-project/,并且我们运行 deno run src/file.js,Deno 会尝试按照下列的方式寻找配置文件:

/dev/deno / my -
  project / src / deno.json / dev / deno / my -
  project / src / deno.jsonc / dev / deno / my -
  project / deno.json / dev / deno / my -
  project /
    deno.jsonc /
    dev /
    deno /
    deno.json /
    dev /
    deno /
    deno.jsonc /
    dev /
    deno.json /
    dev /
    deno.jsonc /
    deno.json /
    deno.jsonc;

如果没有找到配置文件,Deno 会按照没有配置文件的方式运行程序。

并且,Deno 的 LSP 也会自动寻找配置文件。它会把相同的文件目录作为工作空间的根目录来寻找配置文件,并且向上遍历文件夹寻找配置文件。

我们会在接下来的时间继续迭代这个功能,期待大家的反馈。

Error.cause 会打印全部堆栈信息

Error.cause 是一个新引入的用来表示错误原因的属性。Deno 从 v1.13 开始支持了这个属性,然而,这个原因并不是所有堆栈的类型(比如未捕获的错误)。现在我们修复了这个问题,如果有未捕获的错误,也会显示错误原因。

例子:

// error_cause.js
function fizz() {
  throw new Error('boom!');
}

function bar() {
  try {
    fizz();
  } catch (e) {
    throw new Error('fizz() has thrown', { cause: e });
  }
}

function foo() {
  try {
    bar();
  } catch (e) {
    throw new Error('bar() has thrown', { cause: e });
  }
}

foo();

在 v1.18 之前:

$ deno run error_cause.js
error: Uncaught Error: bar() has thrown
        throw new Error("bar() has thrown", { cause: e });
              ^
    at foo (file:///test.js:17:15)
    at file:///test.js:21:1

v1.18:

error: Uncaught Error: bar() has thrown
        throw new Error("bar() has thrown", { cause: e });
              ^
    at foo (file:///test.js:17:15)
    at file:///test.js:21:1
Caused by: Uncaught Error: fizz() has thrown
        throw new Error("fizz() has thrown", { cause: e });
              ^
    at bar (file:///test.js:9:15)
    at foo (file:///test.js:15:9)
    at file:///test.js:21:1
Caused by: Uncaught Error: boom!
    throw new Error("boom!");
          ^
    at fizz (file:///test.js:2:11)
    at bar (file:///test.js:7:9)
    at foo (file:///test.js:15:9)
    at file:///test.js:21:1

test steps 接口稳定

Deno 1.15 通过 --unstable 引入了 test steps 功能。由于正面的社区反馈,我们稳定了这个功能。

这个接口可以让用户通过 Deno.test 定义测试的子步骤。这些子步骤有自己的 sanitizer scopes ,并且使用 test runner 时会用缩进进行显示。这个接口因为足够普遍,所以可以通过增加一些垫片来模拟 mocha 或者 node-tap 等测试的场景。这个 PR 详细解释了这个接口的细节。

下面是一个使用这个新 API 的例子。这个例子创建了一个数据库连接,在子测试里做了一些查询,然后关闭了连接:

Deno.test('database test', async (t) => {
  const db = await Database.connect('postgres://localhost/test');

  await t.step('insert user', async () => {
    const users = await db.query(
      "INSERT INTO users (name) VALUES ('Deno') RETURNING *",
    );
    assertEquals(users.length, 1);
    assertEquals(users[0].name, 'Deno');
  });

  await t.step('insert book', async () => {
    const books = await db.query(
      "INSERT INTO books (name) VALUES ('The Deno Manual') RETURNING *",
    );
    assertEquals(books.length, 1);
    assertEquals(books[0].name, 'The Deno Manual');
  });

  db.close();
});

如果用 Mocha 风格来写测试是:

describe('database test', () => {
  let db: Database;

  beforeAll(async () => {
    db = await Database.connect('postgres://localhost/test');
  });

  it('insert user', async () => {
    const users = await db!.query(
      "INSERT INTO users (name) VALUES ('Deno') RETURNING *",
    );
    assertEquals(users.length, 1);
    assertEquals(users[0].name, 'Deno');
  });

  it('insert book', async () => {
    const books = await db!.query(
      "INSERT INTO books (name) VALUES ('The Deno Manual') RETURNING *",
    );
    assertEquals(books.length, 1);
    assertEquals(books[0].name, 'The Deno Manual');
  });

  afterAll(() => {
    db!.close();
  });
});

如果你对 Mocha 更熟悉,我们写了一个垫片程序来让你使用这个接口:gist.github.com/lucacasonat….

FFI 接口改进

这次发布增加了一些不稳定的 FFI 接口。

我们看到一些使用 FFI 接口的有趣的项目,可以从这些项目看到 FFI 接口的威力:

Symbol 类型推断

通过动态库的 symbol 的定义,TypeScript 可以推断使用的方法是否符合预期的 symbol 类型。

const dylib = Deno.dlopen('dummy_lib.so', {
  method1: { parameters: ['usize', 'usize'], result: 'void' },
  method2: { parameters: ['void'], result: 'void' },
  method3: { parameters: ['usize'], result: 'void' },
} as const);

// 正确调用
dylib.symbols.method1(0, 0);
// error: TS2554 [ERROR]: Expected 2 arguments, but got 1.
dylib.symbols.method1(0);

// 正确调用
dylib.symbols.method2(void 0);
// TS2345 [ERROR]: Argument of type 'null' is not assignable to parameter of type 'void'.
dylib.symbols.method2(null);

// 正确调用
dylib.symbols.method3(0);
// TS2345 [ERROR]: Argument of type 'null' is not assignable to parameter of type 'number'.
dylib.symbols.method3(null);

感谢 @sinclairzx81 实现了这个功能。

symbol 定义别名

当在动态库定义可用的 symbol 时,你可以给它们增加别名。这个功能的场景有:

  • 你可以把动态库的方法名改为和你代码风格一致的名字,例如把 snake_case 改为 camelCase
  • 给同一方法更多的重载,例如 一个会阻塞的方法,和一个异步的方法。
use std::{
  thread::sleep,
  time::Duration
};

#[no_mangle]
pub extern "C" fn print_something() {
  println!("something");
}

#[no_mangle]
pub extern "C" fn sleep_blocking(ms: u64) {
  let duration = Duration::from_millis(ms);
  sleep(duration);
}

const dylib = Deno.dlopen(libPath, {
  printSomething: {
    name: 'print_something',
    parameters: [],
    result: 'void',
  },
  sleep_nonblocking: {
    name: 'sleep_blocking',
    parameters: ['u64'],
    result: 'void',
    nonblocking: true,
  },
  sleep_blocking: {
    parameters: ['u64'],
    result: 'void',
  },
});

dylib.symbols.printSomething();

let start = performance.now();
dylib.symbols.sleep_blocking(100);
console.assert(performance.now() - start >= 100);

start = performance.now();
dylib.symbols.sleep_nonblocking().then(() => {
  console.assert(performance.now() - start >= 100);
});

感谢 @DjDeveloperr 实现了这个功能。

Deno.UnsafeFnPointer 接口

提供了一个新的允许通过指针调用动态链接库的方法的接口 Deno.UnsafeFnPointer

#[no_mangle]
pub extern "C" fn add_u32(a: u32, b: u32) -> u32 {
  a + b
}

#[no_mangle]
pub extern "C" fn get_add_u32_ptr() -> *const c_void {
  add_u32 as *const c_void
}

const dylib = Deno.dlopen('dummy_lib.so', {
  get_add_u32_ptr: { parameters: [], result: 'pointer' },
} as const);

const addU32Ptr = dylib.symbols.get_add_u32_ptr();
const addU32 = new Deno.UnsafeFnPointer(addU32Ptr, {
  parameters: ['u32', 'u32'],
  result: 'u32',
});
console.log(addU32.call(123, 456));

感谢 @DjDeveloperr 实现了这个功能。

支持 WebSockets 出站设置头信息

用户现在可以在出站的 WebSockets 上设置自定义的头信息。这些头信息可以通过 WebSockets 握手来发送,并且可以让服务器用这些信息来做一些工作。因为这个功能不是符合标准的功能,所以使用时应该小心。

你可以通过使用不稳定的 WebSocketStream 接口来使用这个功能:

const ws = new WebSocketStream('wss://example.com', {
  headers: { 'X-Custom-Header': 'foo' },
});

这个头信息的签名和 fetch 方法的头属性的签名一致。

在入站 WebSockets 自动进行 keep-alive

通过 Deno.upgradeWebSocket 接口进行的客户端连接,现在可以自动处理 pong 信息。当没有更多其他信息时,客户端会自动发 ping 信息来保持连接。

你可以通过 Deno.upgradeWebSocket 的 idleTimeout 来配置 ping/pong 的间隔。默认时 120 秒,如果你设置为 0,则表示禁用自动 keep-alive 功能。

import { serve } from 'https://deno.land/std@0.121.0/http/server.ts';

serve((req: Request) => {
  const { socket, response } = Deno.upgradeWebSocket(req, { idleTimeout: 60 });
  handleSocket(socket);
  return response;
});

function handleSocket(socket: WebSocket) {
  socket.onopen = (e) => {
    console.log('WebSocket open');
  };
}

LSP 的改进

这次发布改进了一些 Deno LSP 的特性,你可以在 VS Code、JetBrains IDE 或者其他的 IDE 来使用这些特性。

Code lens for debugging tests

现在在 Deno.test 的 Debug 模式下,会在执行框上面增加一些快捷方式,当执行这些快捷方式时,你可以在编辑器的互动窗口里看到一些新的信息。

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9d111a36cb1b4a8abd3093e061890629~tplv-k3u1fbpfcp-zoom-1.image

编辑切换为居中

添加图片注释,不超过 140 字(可选)

deno.com/v1.18/debug…

感谢 @jespertheend 增加了这个功能。

改善注册补全

这次发布极大改善了使用 LSP 时的注册补全功能。现在对于你引入的 URL 下的资源,会有一些有用的自动提示。对于 deno/land/x 的包,你可以获得包信息,星星数,上次更新的日期,以及包在 doc.deno.land 的文档。

如果你想增加你自己发布的包的自动补全,请阅读这个文档

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5f30d2eb6a424b1caed9da4c8b1002d3~tplv-k3u1fbpfcp-zoom-1.image

上传视频封面

好的标题可以获得更多的推荐及关注者

测试覆盖度功能变得更健壮

这次发布带来了 deno coverage 功能的全面改造。

我们在社区收到老版本的测试覆盖度功能的很多反馈,都表示又慢又不准确。生成测试覆盖度慢的原因是因为源文件疏忽了类型检查。通过简化载入流水线,我们降低了很多这一块的时间。另外,如果覆盖数据和源文件不一致,deno coverage 会警告用户。另外,通过修改收集覆盖数据的逻辑改善了数据不准确的问题。

加快了启动时间

Deno 通过 V8 的快照来加快运行时和 TypeScript 编译器的启动速度。这些快照是二进制 blob 文件。我们在建造时生成这些文件,然后嵌入这些文件在 deno 执行文件里。为了让执行文件小一些,我们用 zlib 对这些文件进行了压缩。

通过一些调研,我们发现使用 zlib 压缩造成了启动速度慢的问题。从 v1.18 开始,我们使用 lz4 和 zstd 来压缩这些文件。这个改进让 JavaScript 运行时的启动速度加快了 33%,让 TypeScript 编译器加快了 10%。

更多信息请看:github.com/denoland/de….

感谢 @evanwashere 提供了这个改进。

V8 升级到了 9.8 版本

Deno 1.18 提供的 V8 引擎升级到了 9.8 版本。这个版本没有任何的 JavaScript 新功能,只是修复了一些 BUG,包括使用私有方法导致 crash 的 bug(Crash with basic private method example · Issue #12940 · denoland/deno)和一个 ES 模块载入的 bug(github.com/denoland/de…))