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

Deno 1.19 已发布,其中包含以下新功能和变更

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

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 vendor

在过去几个月里,我们收到了一些反馈,用户希望能够将程序的依赖项引入到他们的代码库中。如果您希望确保只有某些特定代码由您的应用程序执行,那么这是一个非常有用的功能。

之前,我们建议用户将他们的 DENO_DIR 检查到代码库中以进行依赖项引入。虽然这通常有效,但用户体验并不理想。DENO_DIR 中的文件具有非直观的名称,这些名称是由十六进制字符串组成的,在版本控制中看起来不太好。

在此版本中,我们引入了一种改进的依赖项引入方法:deno vendor。此子命令可以与一个或多个要引入的模块的入口点一起调用。然后,Deno 将通过分析模块的所有导入和导出来构建一个模块图。生成的模块列表将写入 vendor/ 文件夹,并尽可能接近原始模块名称。有时,我们必须从名称中去除或追加一些字符以使标识符成为有效的文件系统路径。然后,在 vendor 文件夹内,我们生成一个 导入映射,将所有引入的远程模块映射到本地 vendor 目录。

然后,要使用引入的依赖项,您只需将 --import-map=vendor/import_map.json 添加到您的 Deno 调用中即可。您还可以将 --no-remote 添加到您的调用中以完全禁用远程模块的获取(只允许导入 vendor 目录中的模块)。

$ deno vendor main.ts
$ tree
.
├── main.ts
└── vendor
    ├── deno.land
    ├── import_map.json
    └── raw.githubusercontent.com
$ deno run --no-remote --import-map=vendor/import_map.json main.ts

应该将 vendor 目录检查到版本控制中。文件名在 deno vendor 的多次运行之间保持一致性,以最大程度地减少膨胀的 Git 差异。

另一个很棒的用例是允许您在调试时临时向依赖代码添加一些 console.log 条目或类似条目。为此,您只需引入您想要修改的特定模块,然后编辑 vendor 目录中的源代码。完成调试后,您可以再次删除 vendor 目录并继续正常运行。

我们很乐意听到您对该功能的反馈。我们知道 一些问题,我们将在接下来的几周内解决,但在大多数情况下,它运行良好。

默认情况下启用权限提示

一个抱怨是 Deno 需要太多命令行标志。这些标志通常是 --allow-* 权限许可。在此版本之前,如果权限检查失败,Deno 将抛出一个异常,要求用户每次都指定这些标志。现在,在 Deno 1.19 中,如果未授予访问权限,命令行提示将允许用户以交互方式接受或拒绝每个单独的访问检查。

# deno run https://deno.land/std/http/file_server.ts
⚠️  ️Deno requests read access to <CWD>. Run again with --allow-read to bypass this prompt.

Allow? [y/n (y = yes allow, n = no deny)] y
⚠️  ️Deno requests net access to "0.0.0.0:4507". Run again with --allow-net to bypass this prompt.

Allow? [y/n (y = yes allow, n = no deny)] y
HTTP server listening on https://127.0.0.1:4507/

此功能长期以来一直通过使用 --prompt 标志实现,但现在默认情况下已启用。要禁用提示,请使用 --no-prompt。这些权限提示只有在您连接到 TTY 时才会出现,因此例如在 CI 脚本中,您无需提供 --no-prompt

文件、网络套接字和 stdio 现在是原生 Web 流

Deno.FsFileDeno.Conn 接口现在具有 readablewritable 属性,类型分别为 ReadableStreamWritableStream。这使得它们能够很好地与使用 Web 流的其他 Web API 集成。以下是一些示例

// Download a file from the web, and stream it into a file on disk.
const file = await Deno.create("./example.html");
const resp = await fetch("https://example.com");
await resp.body.pipeTo(file.writable);
// Read from stdin, and stream it to a remote server (streaming upload).
// Would be used like this: `cat file.txt | deno run --allow-net=example.com main.ts`
const resp = await fetch("https://example.com", {
  method: "POST",
  body: Deno.stdin.readable,
});
console.log("Upload succeeded?", resp.ok);

由于 Deno 中的所有 API 现在都开箱即用地支持 Web 流,包括我们的原生 HTTP 服务器,因此将所有这些 API 组合在一起现在变得非常简单

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

serve(async (req) => {
  if (req.method === "POST") {
    // Save the incoming request body to a file on disk
    const path = await Deno.makeTempFile();
    const file = await Deno.create(path);
    await req.body.pipeTo(file.writable);
    return new Response(`Saved file to ${path}`);
  } else {
    // Serve a file from disk
    const path = "./example.html";
    const file = await Deno.open(path);
    return new Response(file.readable, {
      headers: { "content-type": "text/html" },
    });
  }
});

新的 readablewritable 属性还可以与内置的流转换器(如 TextEncoderStreamCompressionStream)组合使用。以下是一个从 stdin 读取、执行 gzip 压缩然后将结果写入 stdout 的程序示例

await Deno.stdin.readable
  .pipeThrough(new CompressionStream("gzip"))
  .pipeTo(Deno.stdout.writable);

CompressionStreamDecompressionStream 现在支持

此版本添加了两个新的内置流转换器,称为 CompressionStreamDecompressionStream。此 Web 标准 API 允许您以多种文件格式压缩和解压缩数据(目前为 gzipdeflate)。

该 API 已在 Chrome 中发布,希望很快就会在其他浏览器中发布。

以下是如何执行 .gz 文件的流解压缩的示例

const input = await Deno.open("./file.txt.gz");
const output = await Deno.create("./file.txt");
await input.readable
  .pipeThrough(new DecompressionStream("gzip"))
  .pipeTo(output.writable);

我们目前正在与 Web 标准组合作,将 brotli 压缩算法添加到 CompressionStreamDecompressionStream API 中。您可以关注 此问题 以获取更新。

改进了针对操作和资源清理器的错误,适用于 Deno.test

自从 deno test 的第一个稳定版本发布以来,它一直具有资源和操作清理器。这些清理器会检查测试是否不会泄漏资源(如打开的文件句柄)或异步操作(如计时器)。之所以这样做,是因为泄漏资源或异步操作会导致难以调试的测试失败或故障,并且它们通常表明代码中存在逻辑错误。

此版本完全改造了这些清理器的错误消息,使它们更易于使用,并让新用户更容易理解。例如,考虑这个泄漏了 Deno.FsFile 资源的测试

Deno.test("leaky", () => {
  const file = Deno.openSync("/etc/passwd");
});

它以前会返回以下错误

running 1 test from ./test.ts
test leaky ... FAILED (4ms)

failures:

leaky
AssertionError: Test case is leaking resources.
Before: {
  "0": "stdin",
  "1": "stdout",
  "2": "stderr"
}
After: {
  "0": "stdin",
  "1": "stdout",
  "2": "stderr",
  "3": "fsFile"
}

Make sure to close all open resource handles returned from Deno APIs before
finishing test case.
    at assert (deno:runtime/js/06_util.js:41:13)
    at resourceSanitizer (deno:runtime/js/40_testing.js:153:7)
    at async Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:169:9)
    at async runTest (deno:runtime/js/40_testing.js:427:7)
    at async Object.runTests (deno:runtime/js/40_testing.js:540:22)

failures:

        leaky

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (11ms)

现在,它会返回一个更简洁、更有用的消息

running 1 test from ./test.ts
test leaky ... FAILED (4ms)

failures:

leaky
AssertionError: Test case is leaking 1 resource:

 - A file (rid 3) was opened during the test, but not closed during the test. Close the file handle by calling `file.close()`.

    at assert (deno:runtime/js/06_util.js:46:13)
    at resourceSanitizer (deno:runtime/js/40_testing.js:313:7)
    at async Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:329:9)
    at async runTest (deno:runtime/js/40_testing.js:587:7)
    at async Object.runTests (deno:runtime/js/40_testing.js:700:22)

failures:

        leaky

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (10ms)

异步操作清理器错误也进行了相同的处理。这个测试泄漏了一个睡眠操作

Deno.test("leaky", () => {
  setTimeout(() => {}, 1000);
});

以前,它会返回以下错误

running 1 test from ./test.ts
test leaky ... FAILED (4ms)

failures:

leaky
AssertionError: Test case is leaking async ops.
Before:
  - dispatched: 0
  - completed: 0
After:
  - dispatched: 2
  - completed: 1
Ops:
  op_sleep:
    Before:
      - dispatched: 0
      - completed: 0
    After:
      - dispatched: 2
      - completed: 1

Make sure to await all promises returned from Deno APIs before
finishing test case.
    at assert (deno:runtime/js/06_util.js:41:13)
    at asyncOpSanitizer (deno:runtime/js/40_testing.js:121:7)
    at async resourceSanitizer (deno:runtime/js/40_testing.js:137:7)
    at async Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:169:9)
    at async runTest (deno:runtime/js/40_testing.js:427:7)
    at async Object.runTests (deno:runtime/js/40_testing.js:540:22)

failures:

        leaky

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (9ms)

新的错误包含一个关于导致此问题的提示,甚至包含一个关于异步操作启动位置的堆栈跟踪,因此您无需使用 console.log 手动尝试查找它

running 1 test from ./test.ts
test leaky ... FAILED (8ms)

failures:

leaky
Test case is leaking async ops.

- 1 async operation to sleep for a duration was started in this test, but never completed. This is often caused by not cancelling a `setTimeout` or `setInterval` call. The operation was started here:
    at Object.opAsync (deno:core/01_core.js:155:42)
    at runAfterTimeout (deno:ext/timers/01_timers.js:234:31)
    at initializeTimer (deno:ext/timers/01_timers.js:200:5)
    at setTimeout (deno:ext/timers/01_timers.js:337:12)
    at ./test.ts:2:3
    at testStepSanitizer (deno:runtime/js/40_testing.js:432:13)
    at asyncOpSanitizer (deno:runtime/js/40_testing.js:145:15)
    at resourceSanitizer (deno:runtime/js/40_testing.js:360:13)
    at Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:415:15)
    at runTest (deno:runtime/js/40_testing.js:673:18)

failures:

        leaky

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (17ms)

console.log 现在会在记录的对象中显示循环引用

以前,循环对象的检查只会告诉您存在某种循环关系,但不会告诉您哪些对象被引用。通过此次变更,我们现在提供了导致循环引用的对象的计数器和引用指示器。

示例

const x = { a: {}, b: {}, foo: { deno: "land", bar: 1 } };
x.a.x = x;
x.b.y = x;
console.log(x);
<ref *1> { a: { x: [Circular *1] }, b: { y: [Circular *1] }, foo: { deno: "land", bar: 1 } }

这不仅适用于对象,也适用于Error.causes

const x = new Error("foo");
const y = new Error("bar", { cause: x });
x.cause = y;
console.log(y);
<ref *1> Error: bar
    at file:///dev/denoland/dotcom/test.ts:2:11
Caused by Error: foo
    at file:///dev/denoland/dotcom/test.ts:1:11
Caused by [Circular *1]

Deno.File 已重命名为 Deno.FsFile

Deno 对文件系统文件的抽象以前称为 Deno.File。不幸的是,这个名字会导致用户因在浏览器和 Deno 中全局可用的 File Web API 而产生很多困惑。

为了避免将来造成混淆,我们决定弃用 Deno.File 并将其替换为 Deno.FsFile,这样可以更好地传达它抽象文件系统上文件的含义。Deno.File API 将在 Deno 2.0 之前可用,但我们建议您立即迁移现有代码。

这纯粹是一个重命名 - Deno.File 类上的所有方法都保持不变。只有名称从 Deno.File 更改为 Deno.FsFile

deno lint 中添加了一个新的 lint,它将帮助捕获 Deno.File 的使用并建议更改以避免使用已弃用的 API。

deno compile 现在运行更可靠

以前,deno compile 会在编译期间将您的整个 JS 程序捆绑到一个 ES 模块中。这种捆绑有时会导致代码的行为与捆绑前不完全相同:堆栈跟踪可能不正确,import.meta.url 不正确,执行顺序可能与捆绑前略有不同。

为了解决这个问题,deno compile 现在将您的 ES 模块图序列化到生成的二进制文件中“按原样”,而无需捆绑。这意味着代码的执行顺序保持正确,并且诸如 import.meta.url 之类的东西保持完整。

为了做到这一点,我们正在使用我们的 eszip 库和文件格式,它允许对 ES 模块图进行非常快的序列化和反序列化。

感谢 @williamtetlow 为此功能做出贡献。

允许在文件观察程序重新启动时禁用清除屏幕

Deno 附带一个内置的文件观察程序,当文件更改时,它会重新启动正在运行的进程;可以通过传递 --watch 标志将其与多个子命令一起使用。Deno 会自动发现哪些文件应该被观察,但您还可以额外传递您希望被观察的路径(deno run --watch=file1.ts,file2.js script.ts)。

文件观察程序会在每次重新启动时自动清除您的终端屏幕,但用户报告说在某些情况下,这不可取。在此版本中,我们添加了 --no-clear-screen 标志,它可以与 --watch 标志一起使用,以告诉 Deno 不要清除终端屏幕。

deno lint --watch --no-clear-screen

感谢 @ah-yu 为此功能做出贡献。

deno coverage 获取 --output 标志

此版本在 deno coverage 子命令中添加了 --output 标志,可以在生成 lcov 报告时使用。

在此版本之前,报告被打印到标准输出,并且可以手动管道到文件。

deno coverage --lcov cov/ > cov.profile

从本版本开始,您可以使用 --output 标志直接写入文件。

deno coverage --lcov --output=cov.profile cov/

感谢 @VishnuJin 为此功能做出贡献。

信号监听器 API 的稳定性

我们在本版本中稳定了信号监听器 API。这些 API 可用于使用自定义逻辑拦截和处理诸如 SIGINT(在您的 shell 中为 Ctrl-C)之类的信号。您不再需要传递 --unstable 标志来使用这些 API。

const listener = () => {
  console.log("Got SIGTERM!");
};

// Starts listening for SIGTERM
Deno.addSignalListener("SIGTERM", listener);

// Stops listening for SIGTERM
Deno.removeSignalListener("SIGTERM", listener);

注意:虽然此 API 现在已稳定,但此 API 尚未在 Windows 上提供。请关注问题 #10236 以获取有关此问题的更新。这里稳定意味着 API 表面在将来的次要版本中不会更改。

通过 Unix 套接字提供 HTTP 服务

Deno 的 HTTP 服务器 API 现在除了 TCP 之外还支持通过 Unix 套接字建立的连接。

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

const listener = Deno.listen({ transport: "unix", path: "/path/to/socket" });

serveListener(listener, (req) => {
  return new Response("Hello World");
});

与一般的 Unix 套接字一样,此 API 仍然不稳定。您需要传递 --unstable 标志才能使用它。

感谢 @ylxdzsw 为此功能做出贡献。

新的不稳定 API:Deno.Conn#setNoDelay()Deno.Conn#setKeepAlive()

两个新的 API 被添加到 Deno.Conn

  • Deno.Conn.setNoDelay()
  • Deno.Conn.setKeepAlive()

这些 API 允许为 TCP 连接配置使用 Nagle 算法保持活动 功能。

示例

const conn = await Deno.connect({ hostname: "127.0.0.1", port: 3500 });

// Enable Nagle's algorithm
conn.setNoDelay(false);

// Disable Nagle's algorithm
conn.setNoDelay(true);

// Enable keep-alive
conn.setKeepAlive(true);

// Disable keep-alive
conn.setKeepAlive(false);

注意:API 仍然不稳定。您需要传递 --unstable 标志才能使用它。

感谢 @yos1p 为此功能做出贡献。

新的不稳定 API:Deno.getUid

Deno 1.19 添加了一个新的 Deno.getUid API。调用它后,它会在 Linux 和 macOS 上返回 Deno 进程的用户 ID。您需要传递 --allow-env 权限标志才能使用此 API。

const uid = Deno.getUid();
console.log(uid); // => Prints the user ID
$ deno run --unstable --allow-env getuid.ts
501

注意:API 仍然不稳定。您需要传递 --unstable 标志才能使用它。

新的不稳定 API:Deno.networkInterfaces

此版本添加了一个新的 Deno.networkInterfaces API。此 API 返回一个对象数组,其中包含有关可用网络接口的信息。您需要传递 --allow-env 权限才能使用此 API。

const interfaces = Deno.networkInterfaces();
console.log(interfaces); // => Prints network interfaces
$ deno run --unstable --allow-env network_ifs.js
[
  {
    family: "IPv4",
    name: "lo0",
    address: "127.0.0.1",
    netmask: "255.0.0.0",
    scopeid: null,
    cidr: "127.0.0.1/8",
    mac: "00:00:00:00:00:00"
  },
  ...
]

注意:API 仍然不稳定。您需要传递 --unstable 标志才能使用它。

LSP 的改进

与往常一样,此版本对 LSP 进行了新一轮改进。

代码操作以替换被重定向的说明符

从诸如 https://deno.land/x 之类的注册表导入模块时,如果未指定版本,注册表将自动将请求重定向到该模块的最新版本。如果发生这种情况,LSP 现在将提供用重定向到的说明符替换说明符的选项。结合对未版本化导入显示的警告,这将使用户更容易在其代码库中使用版本化导入。

导入完成现在会考虑导入映射

导入补全现在与导入映射很好地交互。您导入映射中指定的说明符将作为补全提供。

Deno LSP 提供的悬停卡现在正确地将 JSDoc 注释中的 @link 标签渲染为指向符号的可点击链接。以下是在弃用消息中链接到符号的示例

V8 9.9

此版本的 Deno 将 V8 升级到 9.9,其中添加了一些新的 Intl 功能

Intl.Locale 扩展

此版本在 Intl.Locale 对象中添加了七个新属性:calendarscollationshourCyclesnumberingSystemstimeZonestextInfoweekInfo

这些可用于确定给定语言的这些属性的有效值。例如,以下是埃及语支持的所有日历

const arabicEgyptLocale = new Intl.Locale("ar-EG");
arabicEgyptLocale.calendars;
// ['gregory', 'coptic', 'islamic', 'islamic-civil', 'islamic-tbla']

Intl 枚举

此版本还添加了一个新的 Intl.supportedValuesOf(code) 函数,该函数返回 V8 中 Intl API 支持的标识符列表。例如,要获取 Intl API 支持的所有日历列表

Intl.supportedValuesOf("calendar");
// ['buddhist', 'chinese', 'coptic', 'dangi', ...]

或所有支持的货币

Intl.supportedValuesOf("currency");
// ['ADP', 'AED', 'AFA', 'AFN', 'ALK', 'ALL', 'AMD', ...]




HN 评论