跳到主要内容
Deno 2 终于来了 🎉️
了解更多

Deno 1.18 版本发布说明

Deno 1.18 已发布,并包含以下新功能和更改

如果您已经安装了 Deno,您可以通过运行以下命令升级到 1.18

deno upgrade

如果您是第一次安装 Deno,您可以使用以下方法之一

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

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

# Using Homebrew (macOS):
brew install deno

# Using Scoop (Windows):
scoop install deno

# Using Chocolatey (Windows):
choco install deno

新功能和更改

Web 加密 API 现已完成

此版本完成了我们为完全实现 Web 加密 API 所进行的为期 6 个月的努力。我们已经完成了 — Web 加密 API 的所有内容现在都已在 Deno 中实现*。

Deno 现在通过了 Web 加密 API 的 Web 平台测试套件的 98.1%。作为比较数据:Chrome/Edge 通过了 94.5% 的测试,Firefox 通过了 93.4%,Safari 通过了 99.8% 的测试。您可以在 wpt.fyi 上查看最新的数据。

为了实现这一目标,我们在本次发布中引入了以下 API

  • crypto.subtle.encrypt:
    • AES-GCM 支持
    • AES-CTR 支持
  • crypto.subtle.decrypt:
    • AES-GCM 支持
    • AES-CTR 支持
  • crypto.subtle.wrapKey:
    • AES-KW 支持
  • crypto.subtle.unwrapKey:
    • AES-KW 支持
  • crypto.subtle.importKey:
    • EC P-384 支持
  • crypto.subtle.exportKey:
    • 支持 ECDSA 和 ECDH pkcs8/spki/jwk 导出

* 除了某些非常利基的功能,例如 P-521 椭圆曲线密钥。

非常感谢 Sean Michael Wykes 帮助我们完成了这项工作。

自动发现配置文件

我们正在继续迭代我们在 Deno v1.14 中首次引入 的配置文件。以前,使用配置文件需要您指定 --config 标志,后跟配置文件的路径。

从本次发布开始,Deno 将自动发现具有 deno.jsondeno.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

测试步骤 API 的稳定性

Deno 1.15 在 --unstable 中引入了一个新的嵌套测试步骤 API。此版本在社区的积极反馈后稳定了此 API。

此 API 添加允许用户为 Deno.test 定义的测试指定子步骤。这些子步骤拥有自己的 清理器范围,并在测试运行程序中缩进显示。新的 API 足够通用,因此可以通过 polyfills 包装它来模拟现有的测试框架,例如 mocha 或 node-tap。关于此新 API 的 原始说明 更详细地解释了它。

这是一个使用新 API 的测试示例。它创建了一个数据库连接,在子测试中对其运行了一些查询,然后关闭了连接。

Deno.test("database test", async (t) => {
  const db = await Database.connect("postgres://127.0.0.1/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://127.0.0.1/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 polyfill,它建立在这个新 API 之上:https://gist.github.com/lucacasonato/54c03bb267074aaa9b32415dbfb25522

对 FFI API 的改进

此版本为不稳定的 FFI API 带来了更多更新。

我们看到了基于 FFI API 的非常有趣的项目,展示了 FFI API 的强大功能。

符号类型推断

根据动态库提供的符号定义,TypeScript 现在将推断可用方法的类型,并在调用站点不匹配预期类型时引发错误。

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,
);

// Correct invocation
dylib.symbols.method1(0, 0);
// error: TS2554 [ERROR]: Expected 2 arguments, but got 1.
dylib.symbols.method1(0);

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

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

感谢 @sinclairzx81 实现此功能。

符号定义的别名

在定义动态库中可用的符号时,您现在可以为它们添加别名。此功能的用例有两个:a) 您可以重命名符号以保持代码中的一致风格(将 snake_case 别名化为 camelCase);b) 提供相同函数的多个重载,例如同步版本,它将在函数返回之前阻塞执行,以及“非阻塞”版本,它将在线程池上运行函数,并返回对结果的 promise。

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 API

新增了一个 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 实现此功能。

支持在出站 WebSocket 上设置头信息

用户现在可以对出站 WebSocket 设置自定义头信息。这些头信息会与 WebSocket 握手一起发送,服务器可以使用它们来提供有关 WebSocket 连接的更多信息。这是一个非标准扩展,因此应谨慎使用。

要使用此功能,您需要使用不稳定的 WebSocketStream API。

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

选项包中的 headers 属性与 fetch 函数、Request/Response 构造函数中的 headers 属性具有相同的签名。

入站 WebSocket 中的自动保持活动

使用 Deno.upgradeWebSocket 接受来自客户端的 WebSocket 连接现在会自动处理来自客户端的 pong 消息。当一段时间内没有发送其他消息时,现在会自动发送 ping 消息以保持连接活动状态。

要配置 ping/pong 间隔,您可以在 Deno.upgradeWebSocket 选项包中使用 idleTimeout 选项。默认值为 120 秒。可以通过将值设置为 0 来禁用此功能。

import { serve } from "https://deno.land/[email protected]/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 和许多其他编辑器的用户都可以使用这些功能。

用于调试测试的代码透镜

通过添加“调试”代码透镜快速操作,调试单个测试用例现在变得更加简单,该操作将在 Deno.test 调用上方显示。单击后,测试将运行并附加到编辑器的交互式调试器。

感谢 @jespertheend 贡献此功能。

改进的注册表自动完成

此版本极大地改进了 LSP 中注册表的自动完成功能。注册表自动完成现在包括每个 URL 部分的有用悬停卡。对于 deno.land/x 包,悬停卡包含包描述、星级计数、最后更新日期以及指向 doc.deno.land 上包文档的链接。

如果您想为自己的注册表添加注册表自动完成的支持,请务必阅读 文档

覆盖范围现在更加稳健

此版本对 deno coverage 子命令进行了全面改进。

我们收到了社区的反馈,生成覆盖范围数据速度很慢,而且数据通常不准确。生成覆盖范围速度很慢是由于对用于执行源映射的源文件进行了意外的类型检查;通过简化加载管道,我们设法显着减少了处理时间。此外,deno coverage 子命令现在会在覆盖范围数据与可用源不一致的情况下向用户发出警告。通过修复用于合并从多个文件收集的覆盖范围数据的某些逻辑,解决了数据不准确的第二个问题。

启动时间已改进

Deno 使用 V8 快照 来提供运行时和 TypeScript 编译器的快速启动。这些快照是二进制 blob。我们在构建时生成它们,然后将它们嵌入到 deno 可执行文件中。为了使可执行文件更小,以前 V8 使用 zlib 压缩快照。

经过调查后发现,使用这种 zlib 压缩会产生相当大的启动开销,可以避免。从 v1.18 开始,Deno 对快照使用 lz4zstd 压缩。此更改导致 JavaScript 运行时启动速度提高了 33%,TypeScript 编译器启动速度提高了 10%。

有关更多详细信息,请参阅 https://github.com/denoland/deno/pull/13320

感谢 @evanwashere 为此改进做出贡献。

V8 升级到 9.8 版

Deno 1.18 附带 V8 引擎 的 9.8 版。此版本没有带来任何新的 JavaScript 功能,但修复了我们在最近几个月发现并报告的几个错误,包括私有方法中的崩溃 (https://github.com/denoland/deno/issues/12940) 以及 ES 模块加载中的错误 (https://github.com/denoland/deno/issues/11258)。