跳转至主要内容
Deno 1.24 Release Notes

Deno 1.24 版本发布说明

Deno 1.24 已被标记并发布,包含以下新功能和更改

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

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

类型检查和代码生成性能改进

以前,当指定 --check 标志时,Deno 内部使用 TypeScript 编译器将 TypeScript 代码转换为 JavaScript,否则它使用 swc。在此版本中,所有代码生成都使用 swc 完成,这速度更快。

此外,由于一些架构重构

  1. 代码生成不再与 deno check 一起发生。
  2. 用于存储生成的 JavaScript 的缓存更加健壮。
  3. Deno 在过去成功进行类型检查的情况下,可以更智能地避免再次进行类型检查。

总的来说,这些改进应该会带来可观的性能提升,但这会因代码库而异。例如,在首次运行 deno check 时,不使用缓存对 oak 的存储库进行类型检查速度提高了 20%,然后在代码发生更改的后续运行时速度提高了 70%。在某些情况下,速度提高了 90% 以上,因为 Deno 在识别以前成功进行类型检查方面做得更好。

unhandledrejection 事件

此版本增加了对 unhandledrejection 事件 的支持。当没有拒绝处理程序的 Promise 被拒绝时,会触发此事件,即没有 .catch() 处理程序或 .then() 的第二个参数的 Promise。

示例

// unhandledrejection.js
globalThis.addEventListener("unhandledrejection", (e) => {
  console.log("unhandled rejection at:", e.promise, "reason:", e.reason);
  e.preventDefault();
});

function Foo() {
  this.bar = Promise.reject(new Error("bar not available"));
}

new Foo();
Promise.reject();

运行此程序将打印

$ deno run unhandledrejection.js
unhandled rejection at: Promise {
  <rejected> Error: bar not available
    at new Foo (file:///dev/unhandled_rejection.js:7:29)
    at file:///dev/unhandled_rejection.js:10:1
} reason: Error: bar not available
    at new Foo (file:///dev/unhandled_rejection.js:7:29)
    at file:///dev/unhandled_rejection.js:10:1
unhandled rejection at: Promise { <rejected> undefined } reason: undefined

此 API 将允许我们在未来的版本中在 Node 兼容层中填充 process.on("unhandledRejection")

beforeunload 事件

此版本增加了对 beforeunload 事件 的支持。当事件循环没有更多工作要做并且即将退出时,将触发此事件。调度更多异步工作(如计时器或网络请求)将导致程序继续运行。

示例

// beforeunload.js
let count = 0;

console.log(count);

globalThis.addEventListener("beforeunload", (e) => {
  console.log("About to exit...");
  if (count < 4) {
    e.preventDefault();
    console.log("Scheduling more work...");
    setTimeout(() => {
      console.log(count);
    }, 100);
  }

  count++;
});

globalThis.addEventListener("unload", (e) => {
  console.log("Exiting");
});

count++;
console.log(count);

setTimeout(() => {
  count++;
  console.log(count);
}, 100);

运行此程序将打印

$ deno run beforeunload.js
0
1
2
About to exit...
Scheduling more work...
3
About to exit...
Scheduling more work...
4
About to exit...
Exiting

这使我们能够在 Node 兼容层中填充 process.on("beforeExit")

import.meta.resolve() API

自 v1.0 以来,Deno 支持 import.meta。两个可用的选项是 import.meta.url 以告知当前模块的 URL,以及 import.meta.main 以让您知道当前模块是否是程序的入口点。此版本增加了对 import.meta.resolve() API 的支持,该 API 允许您解析相对于当前模块的说明符。

之前

const worker = new Worker(new URL("./worker.ts", import.meta.url).href);

之后

const worker = new Worker(import.meta.resolve("./worker.ts"));

此 API 相对于 URL API 的优势在于,它考虑了当前应用的导入映射,这使您能够解析“裸”说明符。

加载了这样的导入映射...

{
  "imports": {
    "fresh": "https://deno.land/x/fresh@1.0.1/dev.ts"
  }
}

...您现在可以解析

// resolve.js
console.log(import.meta.resolve("fresh"));
$ deno run resolve.js
https://deno.land/x/fresh@1.0.1/dev.ts

FFI API 改进

此版本在不稳定的 Foreign Function Interface API 中添加了新功能和性能改进

回调

FFI 调用现在支持将 JS 回调作为线程安全的 C 函数传递。

新的 Deno.UnsafeCallback 类用于准备要用作参数的 JS 函数

// type AddFunc = extern "C" fn (a: u32, b: u32) -> u32;
//
// #[no_mangle]
// extern "C" fn add(js_func: AddFunc) -> u32 {
//   js_func(2, 3)
// }
//
const { symbols: { add } } = Deno.dlopen("libtest.so", {
  add: {
    parameters: ["function"],
    return: "u32",
    // Use `callback: true` indicate that this call might
    // callback into JS.
    callback: true,
  }
});

const jsAdd(a, b) { return a + b };

const cFunction = new Deno.UnsafeCallback({
  parameters: ["u32", "u32"],
  result: ["u32"]
}, jsAdd);

const result = add(cFunction.pointer); // 5

感谢 Aapo Alasuutari 实现了此功能。

改进的 FFI 调用性能

在此版本中,许多 FFI 调用的速度提高了约 200 倍。这是通过 V8 快速 API 调用和 JIT trampoline 的组合实现的。Deno 动态生成优化的 JIT 代码,用于调用 FFI 以及利用 V8 快速 API 调用。

之前

cpu: Apple M1
runtime: deno 1.23.0 (aarch64-apple-darwin)

file:///ffi_bench.js
benchmark                              time (avg)             (min … max)       p75       p99      p995
------------------------------------------------------------------------- -----------------------------
nop()                              436.71 ns/iter   (403.72 ns … 1.26 µs) 408.97 ns 968.96 ns   1.26 µs
add_u32()                          627.42 ns/iter (619.14 ns … 652.66 ns) 630.02 ns 652.66 ns 652.66 ns

之后

cpu: Apple M1
runtime: deno 1.24.0 (aarch64-apple-darwin)

file:///ffi_bench.js
benchmark                              time (avg)             (min … max)       p75       p99      p995
------------------------------------------------------------------------- -----------------------------
nop()                                2.23 ns/iter    (2.18 ns … 12.32 ns)    2.2 ns   2.36 ns   2.67 ns
add_u32()                            4.79 ns/iter     (4.7 ns … 11.91 ns)   4.73 ns   5.77 ns  10.05 ns

将来,我们将扩展此优化以适用于 TypedArrays 和安全数字指针。

有关更多信息,请参阅 denoland/deno#15125denoland/deno#15139

移除 Deno.UnsafePointer

以前,Deno 中的指针使用间接类 Deno.UnsafePointer 表示。Deno.UnsafePointer 已被移除,取而代之的是 bigint

因此,Deno.UnsafePointer.of 现在返回一个 bigint

const ptr: bigint = Deno.UnsafePointer.of(new Uint8Array([1, 2, 3]));
call_symbol(ptr, 3);

deno test 改进

在配置文件中包含和排除路径

以前在运行 deno test 时,如果您想包含或排除特定路径,则需要在 CLI 参数中指定。

例如

# only test these paths
deno test src/fetch_test.ts src/signal_test.ts
# exclude testing this path
deno test --ignore=out/

每次都需要在命令行上提供此信息在某些项目中不太理想,因此在此版本中,您可以在 Deno 配置文件 中指定这些选项

{
  "test": {
    "files": {
      "include": [
        "src/fetch_test.ts",
        "src/signal_test.ts"
      ]
    }
  }
}

或者更常见的情况是

{
  "test": {
    "files": {
      "exclude": ["out/"]
    }
  }
}

然后在与配置文件相同的目录树中运行 deno test 将考虑这些选项。

感谢 @roj1512 贡献了此功能。

--parallel 标志

在以前的版本中,可以通过使用 --jobs CLI 标志并行运行测试

deno test --jobs
# or specify the amount of parallelism
deno test --jobs 4

但是,此名称的可发现性不高。此外,它的设计存在一个疏忽,即 --jobs 在提供数值时不需要等号(例如 --jobs=4),这意味着如果您不提供值,则该标志需要是提供的最后一个参数(例如 deno test --jobs my_test_file.ts 将解析 “my_test_file.ts” 错误为数字,因此您需要编写 deno test my_test_file.ts --jobs)。

在此版本中,--jobs 标志已被软弃用并发出警告,并添加了新的 --parallel 标志。

deno test --parallel

此外,不再可以通过 CLI 参数限制最大并行量,因为此值通常取决于系统。因此,它已移至 DENO_JOBS 环境变量(例如 DENO_JOBS=4)。

感谢 Mark Ladyshau 贡献了此功能。

新的子进程 API 更新

在 Deno v1.21 中,我们引入了一个新的不稳定的子进程 API。此版本为此 API 带来了重大更新。

首先,我们更改了 stdio 流的类型;它们现在是简单的、始终可用的流,而不是描述哪些流可用的复杂泛型类型。如果您尝试访问其中一个流,但它未设置为 “piped”,则会抛出 TypeError。默认值记录在此处

之前

// spawn.ts
const child = Deno.spawnChild("echo", {
  args: ["hello"],
  stdout: "piped",
  stderr: "null",
});
const readableStdout = child.stdout.pipeThrough(new TextDecoderStream());
const readableStderr = child.stderr.pipeThrough(new TextDecoderStream());
$ deno check --unstable spawn.ts
Check file:///dev/spawn.ts
error: TS2531 [ERROR]: Object is possibly 'null'.
const readableStderr = child.stderr.pipeThrough(new TextDecoderStream());
                       ~~~~~~~~~~~~
    at file:///dev/spawn.ts:7:24

$ deno run --allow-run --unstable spawn.ts
error: Uncaught TypeError: Cannot read properties of null (reading 'pipeThrough')
const readableStderr = child.stderr.pipeThrough(new TextDecoderStream());
                                    ^
    at file:///dev/spawn.ts:7:37

之后

const child = Deno.spawnChild("echo", {
  args: ["hello"],
  stdout: "piped",
  stderr: "null",
});
const readableStdout = child.stdout.pipeThrough(new TextDecoderStream());
const readableStderr = child.stderr.pipeThrough(new TextDecoderStream());
$ deno check --unstable spawn.ts
Check file:///dev/spawn.ts

$ deno run --allow-run --unstable spawn.ts
error: Uncaught TypeError: stderr is not piped
const readableStderr = child.stderr.pipeThrough(new TextDecoderStream());
                             ^
    at Child.get stderr (deno:runtime/js/40_spawn.js:107:15)
    at file:///dev/spawn.ts:7:30

接下来,我们将 SpawnOutput 类型的签名更改为扩展 ChildStatus,而不是将其作为字段嵌入。

之前

const { status, stdout } = await Deno.spawn("echo", {
  args: ["hello"],
});
console.log(status.success);
console.log(status.code);
console.log(status.signal);
console.log(stdout);

之后

const { success, code, signal, stdout } = await Deno.spawn("echo", {
  args: ["hello"],
});
console.log(success);
console.log(code);
console.log(signal);
console.log(stdout);

最后,添加了两个新方法 Child.ref()Child.unref(),使您可以告知 Deno 在退出程序之前不应等待子进程完成。这些 API 对于需要在后台运行子进程的程序很有用。例如,您可能希望在后台生成 esbuild 以便能够按需转换文件,但您不希望该子进程阻止您的程序退出。

感谢 Nayeem Rahman 贡献了此功能!

LSP 改进

此版本在编辑器中具有更好的自动导入支持,并且不再需要在缓存依赖项后重新启动 LSP,这在以前的某些情况下是必要的。

导入映射快速修复和诊断

LSP 现在提供了一个快速修复,用于将绝对说明符转换为使用导入映射中的条目(如果存在)。

例如,给定以下导入映射

{
  "imports": {
    "std/": "https://deno.land/std@0.148.0/"
  }
}

快速修复会将诸如 "https://deno.land/std@0.148.0/path/mod.ts" 之类的导入说明符更改为 "std/path/mod.ts"

此外,导入映射诊断现在在 LSP 中浮出水面,以帮助更快地捕获问题。

添加 semver 模块

在此版本中,semver 模块已添加到标准模块中。

您可以使用此模块验证、比较和操作 semver 字符串。

import * as semver from "https://deno.land/std@0.149.0/semver/mod.ts";

semver.valid("1.2.3"); // "1.2.3"
semver.valid("a.b.c"); // null
semver.gt("1.2.3", "9.8.7"); // false
semver.lt("1.2.3", "9.8.7"); // true
semver.inc("1.2.3", "patch"); // "1.2.4"
semver.inc("1.2.3", "minor"); // "1.3.0"
semver.inc("1.2.3", "major"); // "2.0.0"

该模块还支持处理 semver 范围。其表示法与 node-semver npm 模块兼容。

semver.satisfies("1.2.3", "1.x || >=2.5.0 || 5.0.0 - 7.2.3"); // true
semver.minVersion(">=1.0.0"); // "1.0.0"

此模块是 node-semver 的一个分支,并添加了适当的类型定义。

感谢 @justjavac 贡献了此功能。

改进 flags 模块中的类型定义

在此版本中,flags 标准模块中 parse 方法的类型定义已得到极大改进。现在,解析后的对象具有根据给定选项正确键入的属性。

例如,让我们看下面的示例

import { parse } from "https://deno.land/std@0.149.0/flags/mod.ts";

const args = parse(Deno.args, {
  boolean: ["help"],
  string: ["n"],
});

此示例将 help 定义为布尔参数,将 n 定义为字符串参数。解析后的对象 args 具有以下类型

type Result = {
  [x: string]: unknown;
  n?: string | undefined;
  help: boolean;
  _: (string | number)[];
};

这里,nhelp 基于给定的选项对象被正确地键入。

感谢 Benjamin Fischer 贡献了此功能。

dotenv 模块中添加变量扩展

在此版本中,dotenv 标准模块中已启用变量扩展。

现在,像下面这样的 .env 文件是有效的

FOO=example
BAR=${FOO}
BAZ=http://${FOO}.com/
QUX=/path/to/${QUUX:-main}

.env 文件的工作方式如下

import "https://deno.land/std@0.149.0/dotenv/load.ts";

console.log(Deno.env.get("FOO"));
console.log(Deno.env.get("BAR"));
console.log(Deno.env.get("BAZ"));
console.log(Deno.env.get("QUX"));

上面的输出

example
example
http://example.com/
/path/to/main

感谢 @sevenwithawp 贡献了此功能。