跳到主要内容
Deno 2.4 发布,带来 deno bundle、bytes/text 导入、OTel 稳定版等更多功能
了解更多

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 Cryptography API 现已完善

此版本最终完成了我们长达 6 个月的全面实现 Web Cryptography API 的努力。我们已完成 — 整个 Web Cryptography API 现已在 Deno 中实现*。

Deno 现在通过了 98.1% 的 Web Cryptography API 网络平台测试套件。作为一些对比数据: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:///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:///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 实现了此功能。

支持对外 WebSockets 设置请求头

用户现在可以在对外 WebSockets 上设置自定义请求头。这些请求头随 WebSocket 握手一起发送,服务器可以使用它们提供有关 WebSocket 连接的额外信息。这是一个非标准扩展,因此应谨慎使用。

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

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

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

入站 WebSockets 自动保持连接

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

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

import { serve } from "https://deno.land/std@0.140.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 和许多其他编辑器的用户均可使用。

测试调试的代码透镜

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

感谢 @jespertheend 贡献此功能。

改进的注册表补全

此版本大大改进了 LSP 中注册表的自动补全功能。注册表补全现在为 URL 的每个部分提供了有用的悬停卡片。对于 deno.land/x 包,悬停卡片包括包描述、星标数量、上次更新日期以及指向 doc.deno.land 上包文档的链接。

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

覆盖率报告现在更加健壮

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

我们收到了社区的反馈,称生成覆盖率数据很慢,并且数据通常不准确。生成覆盖率慢是由于对用于执行源映射的源文件进行了意外的类型检查;通过简化加载管道,我们成功显著缩短了处理时间。此外,如果覆盖率数据和可用源处于不一致状态,deno coverage 子命令现在会警告用户。第二个不准确数据的问题已通过修复用于合并从多个文件收集的覆盖率数据的一些逻辑得到缓解。

启动时间得到改进

Deno 使用 V8 快照来提供运行时和 TypeScript 编译器的快速启动。这些快照是二进制大对象。我们在构建时生成它们,然后将它们嵌入到 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 功能,但修复了我们最近几个月发现和报告的几个 bug,包括私有方法中的崩溃 (https://github.com/denoland/deno/issues/12940) 和 ES 模块加载中的 bug (https://github.com/denoland/deno/issues/11258)。