跳至主要内容
Deno 2 终于来了 🎉️
了解更多
Deno 1.24 Release Notes

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

类型检查和发射性能改进

以前,Deno 在指定 --check 标志时使用 TypeScript 编译器将 TypeScript 代码内部转换为 JavaScript,否则使用 swc。在这个版本中,所有发射都是用 swc 完成的,它快得多。

此外,由于一些架构重构

  1. 发射不再与 deno check 一起发生。
  2. 用于存储发射的 JavaScript 的缓存更加健壮。
  3. Deno 更加智能,如果它之前成功地对某些代码进行了类型检查,它就不会进行类型检查。

总的来说,这些改进应该会有相当大的性能改进,但会根据代码库而有所不同。例如,在第一次运行 deno check 时,在没有缓存的情况下对 oak 的仓库进行类型检查速度快 20%,而在代码发生更改后,后续运行的速度快 70%。在某些情况下,它快了 90% 以上,因为 Deno 更好地识别了它之前成功地进行了类型检查。

unhandledrejection 事件

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

示例

// 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

Deno 自 v1.0 起支持 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/[email protected]/dev.ts"
  }
}

…您现在可以解析

// resolve.js
console.log(import.meta.resolve("fresh"));
$ deno run resolve.js
https://deno.land/x/[email protected]/dev.ts

FFI API 改进

此版本在不稳定的 外部函数接口 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 跳板实现的。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/[email protected]/"
  }
}

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

此外,导入映射诊断现在在 LSP 中可见,以帮助更快地发现问题。

添加 semver 模块

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

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

import * as semver from "https://deno.land/[email protected]/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/[email protected]/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/[email protected]/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 为此功能做出的贡献。