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

Deno 1.17 已发布,包含以下功能和变更:

如果已安装 Deno,可以通过运行以下命令升级到 1.17:

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

新功能和变更

导入断言和 JSON 模块

Deno v1.17 全面支持导入断言和 JSON 模块。

导入断言是 Stage 3 提案,已于今年早些时候在 V8 中实现。此提案的主要用例是允许导入 JSON 模块;如果没有导入断言,这可能会造成安全漏洞

在此版本之前,读取 JSON 文件的唯一方法是结合使用 fetch()Deno.readTextFile()JSON.parse()。从本版本开始,可以直接将 JSON 文件导入到模块图中:

// Prior to Deno v1.17

// Get JSON data from remote server
const response = await fetch("https://example.com/data.json");
const jsonData = await response.json();
console.log(jsonData);

// Get JSON data from local file
const text = await Deno.readTextFile("./data.json");
const jsonData = JSON.parse(text);
console.log(jsonData);

// Starting with Deno v1.17

// Get JSON data from remote server
import jsonData from "https://exmaple.com/data.json" with { type: "json" };
console.log(jsonData);

// Get JSON data from local file
import jsonData from "./data.json" with { type: "json" };
console.log(jsonData);

也可以使用 import() 动态导入 JSON 模块:

const jsonData = await import("./data.json", { assert: { type: "json" } });
console.log(jsonData);

使用 JSON 模块的导入声明的一个优势是,无需提供权限即可导入它们;动态导入受常规权限检查的约束。

Web 加密 API 改进

此版本再次为 Web 加密 API 添加了新功能。通过这些新增功能,该 API 现在正接近与浏览器功能对等。

v1.17 中新增:

  • crypto.subtle.importKey:
    • 支持导入 SPKI 格式的 RSA 密钥
    • 支持导入 JWK 格式的 RSA 密钥
    • 支持导入 JWK 格式的 AES 密钥
    • 支持导入 PKCS#8 格式的 EC 密钥
    • 支持导入 SPKI 格式的 EC 密钥
    • 支持导入 JWK 格式的 EC 密钥
  • crypto.subtle.exportKey:
    • 支持导出 JWK 格式的 RSA 密钥
    • 支持导出 JWK 格式的 AES 密钥
  • crypto.subtle.unwrapKey:
    • 支持使用 RSA-OAEP 解包
    • 支持使用 AES-GCM 解包

最后几个功能(椭圆曲线密钥的导入和导出,以及更多 AES 加密/解密格式)仍在开发中,但进展顺利,很可能在下一个版本中可用。

在此处查看进度:denoland/deno#11690

感谢 Yacine HmitoSean Michael WykesDivy Srivastava 对这些改进的贡献。

--no-check=remote 标志

Deno 的一个常见挑战是,远程依赖有时即使具有正确的运行时行为也无法进行正确的类型检查。在对本地代码进行类型检查时,这可能导致发出诊断信息,并且即使您无法控制远程依赖,您的程序也无法运行。

添加 --no-check=remote 选项有助于解决此问题。当传递此标志时,程序将进行整体类型检查,但来自远程模块的任何诊断信息都将被丢弃。如果没有本地诊断信息,您的程序将运行。

在命令行中传递此选项:

> deno run --no-check=remote server.ts

我们鼓励您尝试此标志并报告反馈。我们认为此模式可能是 Deno 未来版本的一个不错的默认类型检查配置。

Deno.connectTls() 中对协商 ALPN 的不稳定支持

ALPN 是一种 TLS 扩展,允许客户端与服务器协商通信协议。例如,在使用 HTTP/2 时,它用于协商服务器是否支持 HTTP/2。

到目前为止,Deno 不支持手动协商 ALPN。此版本添加了该功能。如果您对此功能感兴趣,我们很乐意听取您的反馈。

示例:

const conn = await Deno.connectTls({
  hostname: "example.com",
  port: 443,
  alpnProtocols: ["h2", "http/1.1"],
});
const { alpnProtocol } = await conn.handshake();
if (alpnProtocol !== null) {
  console.log("Negotiated protocol:", alpnProtocol);
} else {
  console.log("No protocol negotiated");
}

感谢 Yury Selivanov 贡献此功能。

取消引用计时器

此版本添加了两个新的不稳定 API:

  • Deno.unrefTimer(id: number)
  • Deno.refTimer(id: number)

这些 API 可用于更改计时器(setTimeoutsetInterval)的行为,以阻止或不阻止事件循环退出。

默认情况下,所有计时器都会阻止事件循环退出,直到它们被清除。

在以下示例中,程序将在 5 秒后打印 hello from timeout 后结束。

setTimeout(() => {
  console.log("hello from timeout");
}, 5000);

console.log("hello world!");

然而,有时不希望计时器阻止事件循环退出。这种行为的一个例子是定期收集遥测数据。

// Collect data every 5s
setInterval(() => {
  const data = collectSomeData();
  fetch("https://example.com/telemetry", { method: "POST", body: data })
    .catch(() => {});
}, 5000);

// Main entry point to the program
await longRunningTask();

在上述示例中,我们希望程序在 longRunningTask 完成后退出。为了实现此行为,我们可以“取消引用”该间隔:

// Collect data every 5s
const intervalId = setInterval(() => {
  const data = collectSomeData();
  fetch("https://example.com/telemetry", { body: data }).catch(() => {});
}, 5000);
// Unref the telemetry interval so the program exits immediately, once
// `longRunningTask` finishes.
Deno.unrefTimer(intervalId);

// Main entry point to the program
await longRunningTask();

AbortSignal 中中止原因的更新

上一个版本增加了对以特定中止原因中止 AbortSignal 的支持。现在这些原因通过以下 API 正确传播:

  • Deno.readFile()Deno.readTextFile()
  • Deno.writeFile()Deno.writeTextFile()
  • 所有 WHATWG 流 API(ReadableStreamTransformStreamWritableStream 等)。
  • WebSocketStream
  • fetchRequestResponse

此外,现在我们支持 AbortSignal 上的新 throwIfAborted() 方法,如果信号已中止,该方法可用于同步抛出错误。

const controller = new AbortController();
const signal = controller.signal;

try {
  signal.throwIfAborted();
} catch (err) {
  unreachable(); // the abort signal is not yet aborted
}

controller.abort("Hello World");

try {
  signal.throwIfAborted();
  unreachable(); // the method throws this time
} catch (err) {
  assertEquals(err, "Hello World");
}

感谢 Andreu Botella 贡献这些变更。

Deno 语言服务器的更新

Deno 语言服务器增加了对修订后的模块注册表建议协议的支持。这将允许大型包注册表(如 NPM)提供部分结果集、智能增量搜索以及有关包和模块的文档详细信息,作为编辑体验的一部分。将新功能添加到 deno.land/xdeno.land/std 将在 1.17 发布后不久实现。

另一个幕后改进是,来自包注册表的值将遵循所提供的缓存头。以前,注册表的结果会无限期缓存,需要用户手动清除缓存才能获取新的或更改的结果。

要利用这些功能,请确保编辑器的 Deno 配置中包含在 deno.suggest.imports.hosts 中配置的受支持注册表。例如:

{
  "deno.suggest.imports.hosts": {
    "https://deno.land": true,
    "https://cdn.nest.land": true,
    "https://crux.land": true
  }
}

虽然与 Deno 1.17 版本没有直接关系,但 vscode_deno 正在更新,以默认启用支持注册表协议的已知包注册表。这将增加此功能的可见性,使人们更容易找到在 Deno 下运行的代码。

通过注册表协议的新功能,我们希望与 esm.shSkypackJSPM 等合作,提供智能注册表,以便轻松使用更广泛的 JavaScript 和 TypeScript 生态系统中的代码。

如果您想为自己的注册表添加导入完成支持,可以在此处阅读有关注册表协议的更多信息。

此外,Deno 语言服务器已达到成熟水平,并添加了一些未完成的功能,包括*转到类型定义*支持和*工作区符号搜索*。

deno test 的更新

Deno 内置了一个测试运行器。可以使用 Deno.test() API 注册测试。到目前为止,API 有两种可用的重载——“简写”和基于“定义”的。简写适用于简单的测试,而定义提供了最大的控制,例如允许忽略测试:

import { assertEquals } from "https://deno.land/std@0.118.0/testing/asserts.ts";

Deno.test("My test description", (): void => {
  assertEquals("hello", "hello");
});

Deno.test({
  name: "example test",
  ignore: Deno.build.os == "windows",
  fn(): void {
    assertEquals("world", "world");
  },
});

如果需要忽略/关注一个使用“简写”重载编写的测试,这会变得相当麻烦。需要重写整个测试以使用“定义”重载。为了缓解此问题并提供更灵活的测试编写方式,我们为 Deno.test() API 添加了四个更多重载。

import { assertEquals } from "https://deno.land/std@0.118.0/testing/asserts.ts";

Deno.test(function myTestName(): void {
  assertEquals("hello", "hello");
});

Deno.test("My test description", { permissions: { read: true } }, (): void => {
  assertEquals("hello", "hello");
});

Deno.test(
  { name: "My test description", permissions: { read: true } },
  (): void => {
    assertEquals("hello", "hello");
  },
);

Deno.test({ permissions: { read: true } }, function myTestName(): void {
  assertEquals("hello", "hello");
});

现在可以混合两种形式,并将部分“定义”作为第一个或第二个参数提供。

另一个更新是针对使用不稳定测试步骤 API 的用户。最终的测试报告输出现在将包含测试步骤的数量,并区分通过/忽略/失败的步骤。测试步骤 API 计划在 v1.18 中稳定。

Deno.test("nested failure", async (t) => {
  const success = await t.step("step 1", async (t) => {
    let success = await t.step("inner 1", () => {
      throw new Error("Failed.");
    });
    if (success) throw new Error("Expected failure");

    success = await t.step("inner 2", () => {});
    if (!success) throw new Error("Expected success");
  });

  if (success) throw new Error("Expected failure");
});
$ deno test --unstable test.ts
running 1 test from file:///dev/test.ts
test nested failure ...
  test step 1 ...
    test inner 1 ... FAILED (11ms)
      Error: Failed.
          at file:///dev/test.ts:4:13
          at testStepSanitizer (deno:runtime/js/40_testing.js:187:13)
          at asyncOpSanitizer (deno:runtime/js/40_testing.js:68:15)
          at resourceSanitizer (deno:runtime/js/40_testing.js:138:13)
          at exitSanitizer (deno:runtime/js/40_testing.js:170:15)
          at TestContext.step (deno:runtime/js/40_testing.js:800:19)
          at file:///dev/test.ts:3:27
          at testStepSanitizer (deno:runtime/js/40_testing.js:187:13)
          at asyncOpSanitizer (deno:runtime/js/40_testing.js:68:15)
          at resourceSanitizer (deno:runtime/js/40_testing.js:138:13)
    test inner 2 ... ok (11ms)
  FAILED (32ms)
FAILED (44ms)

failures:

nested failure
Error: 1 test step failed.
    at runTest (deno:runtime/js/40_testing.js:432:11)
    at async Object.runTests (deno:runtime/js/40_testing.js:541:22)

failures:

    nested failure

test result: FAILED. 0 passed (1 step); 1 failed (2 steps); 0 ignored; 0 measured; 0 filtered out (66ms)

文件监视器的更新

Deno 内置了文件监视器,可以使用 --watch 标志激活。

文件监视器根据模块图中包含的文件(对于 deno run 子命令)或作为参数传递的文件路径(对于 deno lintdeno fmt 等子命令)自动发现要监视的文件。

在此版本中,deno run--watch 标志接受一个可选的外部文件列表,这些文件也应监视其更改。

$ deno run --watch=data/external.txt script.ts

感谢 Jasper van den End 贡献此功能。

监视器的一个小而重要的改进是在重新启动时自动清除终端屏幕。

REPL 的更新

模块说明符自动完成

REPL (deno repl) 现在为文件系统上的相对说明符和来自 deno.land 注册表的绝对说明符提供自动完成功能。

可以通过在键入模块说明符时按 <TAB> 键来请求这些自动完成。

Node.js 兼容模式

REPL 现在可以在Node.js 兼容模式下运行;允许访问 Node 的全局变量(例如 process),使用 require() 导入 CommonJS 模块,并提供与 Node.js 兼容的 ESM 解析。要进入兼容模式的 REPL,请使用 deno repl --compat --unstable

$ deno repl --compat --unstable
Deno 1.17.0
exit using ctrl+d or close()
> console.log(process)
process {
  _events: {},
  _eventsCount: 0,
  _maxListeners: undefined,
  ...
}

忽略证书错误

此版本为 deno repl 子命令添加了 --unsafely-ignore-certificate-errors 标志,允许禁用 TLS 证书验证。

请注意,这是一个危险的设置。不应使用此标志来消除证书错误。禁用 TLS 证书验证(忽略证书错误)使 TLS 变得毫无意义,因为它允许 MITM 攻击。如果证书未经验证和信任,通过 TLS 连接发送的数据将不具有机密性。

请在Deno 1.13 发布说明博客文章中阅读更多信息。

感谢 VishnuJin 贡献此功能。

FFI API 的更新

v1.15 中对 FFI API 的改进之后,此版本新增了两个用于跨语言边界处理指针的 API:Deno.UnsafePointerDeno.UnsafePointerView。此外,buffer 参数类型已重命名为 pointer

示例

// test_ffi.rs
static BUFFER: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];

#[no_mangle]
pub extern "C" fn return_buffer() -> *const u8 {
  BUFFER.as_ptr()
}

#[no_mangle]
pub extern "C" fn is_null_ptr(ptr: *const u8) -> u8 {
  ptr.is_null() as u8
}
// test_ffi.js
const [libPrefix, libSuffix] = {
  darwin: ["lib", "dylib"],
  linux: ["lib", "so"],
  windows: ["", "dll"],
}[Deno.build.os];
const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;

const dylib = Deno.dlopen(libPath, {
  "return_buffer": { parameters: [], result: "pointer" },
  "is_null_ptr": { parameters: ["pointer"], result: "u8" },
});

const ptr = dylib.symbols.return_buffer();
dylib.symbols.print_buffer(ptr, 8);
const ptrView = new Deno.UnsafePointerView(ptr);
const into = new Uint8Array(6);
const into2 = new Uint8Array(3);
const into2ptr = Deno.UnsafePointer.of(into2);
const into2ptrView = new Deno.UnsafePointerView(into2ptr);
const into3 = new Uint8Array(3);
ptrView.copyInto(into);
console.log([...into]);
ptrView.copyInto(into2, 3);
console.log([...into2]);
into2ptrView.copyInto(into3);
console.log([...into3]);
const string = new Uint8Array([
  ...new TextEncoder().encode("Hello from pointer!"),
  0,
]);
const stringPtr = Deno.UnsafePointer.of(string);
const stringPtrview = new Deno.UnsafePointerView(stringPtr);
console.log(stringPtrview.getCString());
console.log(stringPtrview.getCString(11));
console.log(Boolean(dylib.symbols.is_null_ptr(ptr)));
console.log(Boolean(dylib.symbols.is_null_ptr(null)));
console.log(Boolean(dylib.symbols.is_null_ptr(Deno.UnsafePointer.of(into))));
$ deno run --allow-ffi --allow-read --unstable test_ffi.js
[1, 2, 3, 4, 5, 6, 7, 8]
[ 1, 2, 3, 4, 5, 6 ]
[ 4, 5, 6 ]
[ 4, 5, 6 ]
Hello from pointer!
pointer!
false
true
false

我们已经看到使用这些新 API 的有趣项目,例如 SQLite C API 的原生绑定:https://deno.land/x/sqlite3

FFI 需要 --allow-ffi--unstable 标志。请注意,启用 FFI 将有效禁用 Deno 中的所有安全保护。

感谢 Elias Sjögreen 贡献此功能。

TypeScript 4.5

Deno 1.17 附带最新稳定版本的 TypeScript。这是启用导入断言能力的一部分。此外,还支持一种新的类型导入方式:

以前,建议以这种方式导入仅在类型位置使用的项:

import { A } from "https://example.com/mod.ts";
import type { B } from "https://example.com/mod.ts";

但现在这些可以合并到一个导入语句中:

import { A, type B } from "https://example.com/mod.ts";

此外,TypeScript 4.5 添加了对 .mts.d.mts 扩展的支持,以及对 .mjs 文件的头等支持,Deno 1.17 中已正确处理这些文件。

有关 TypeScript 新功能的更多信息,请参阅TypeScript 4.5 博客文章

标准库的更新

此版本包含标准库的许多新功能和重大更改。我们正在努力清理标准库,使其达到可以稳定的状态。

重大变更

deno_std 的 0.118.0 版本中,以下 API 经历了重大变更:

  • ws 模块已移除 - 请改用 Deno.upgradeWebSocket() API
  • testing/asserts.ts 中的 assertThrowsAsync 已移除 - 请改用同一模块中的 assertRejects API
  • http/server_legacy.ts 已移除 - 这是 HTTP 服务器的旧版实现,请改用 http/server.ts
  • fs/mod.ts 中的 copy API 已移除 - 请改从 fs/copy.ts 直接导入此 API
  • signals 模块中的 onSignal 已移除 - 请改用 Deno.addSignalListener() API
  • collections 模块中的 findLastfindLastIndex 已移除
  • http 服务器模块不再接受字符串地址,请改用 { host: string, port: number } 接口

新的 HTTP 服务器选项

以前,std/http 模块中的 serve 函数会吞噬 HTTP 处理函数产生的所有错误。此版本更改了该行为,因此服务器将使用 console.error 记录处理程序产生的所有错误。可以通过向 serve 选项传递自定义 onError 函数来自定义该行为。

import { Server } from "https://deno.land/std@0.118.0/http/server.ts";

const port = 4505;
const handler = (request: Request) => {
  const body = `Your user-agent is:\n\n${
    request.headers.get(
      "user-agent",
    ) ?? "Unknown"
  }`;

  return new Response(body, { status: 200 });
};

const onError = (_error: unknown) => {
  return new Response("custom error page", { status: 500 });
};

const server = new Server({ port, handler, onError });

std/collections 的新增功能

std/collections 是我们尝试更高级的数组和可迭代对象迭代函数的试验场。此版本添加了 aggregateGroups。它将给定的聚合器应用于给定分组中的每个组,并返回结果以及相应的组键。

import { aggregateGroups } from "https://deno.land/std@0.118.0/collections/mod.ts";
import { assertEquals } from "https://deno.land/std@0.118.0/testing/asserts.ts";
const foodProperties = {
  "Curry": ["spicy", "vegan"],
  "Omelette": ["creamy", "vegetarian"],
};
const descriptions = aggregateGroups(
  foodProperties,
  (current, key, first, acc) => {
    if (first) {
      return `${key} is ${current}`;
    }
    return `${acc} and ${current}`;
  },
);
assertEquals(descriptions, {
  "Curry": "Curry is spicy and vegan",
  "Omelette": "Omelette is creamy and vegetarian",
});

使用 std/fmt/bytes 进行字节格式化

标准库中添加了一个新的 std/fmt/bytes 模块。它提供了一个函数,用于以人类可读的格式格式化字节:

import { prettyBytes } from "https://deno.land/std@0.118.0/fmt/bytes.ts";
import { assertEquals } from "https://deno.land/std@0.118.0/testing/asserts.ts";

assertEquals(prettyBytes(1024), "1.02 kB");
assertEquals(prettyBytes(1024, { binary: true }), "1 kiB");

此 API 是流行 npm 包 pretty-bytes 的一个移植。