Deno 1.40: Temporal API

2,201 阅读8分钟

原文:deno.com/blog/v1.40

作者:Bartek Iwańczuk、Leo Kettmeir等 2024年1月25日


我们很高兴宣布发布 Deno 1.40,这是 Deno 进化的重要一步。这个新版本充满了增强 Deno 使用体验的功能,引入了强大的 Temporal API,用于高级日期和时间操作,并采用了最新的 装饰器语法 以实现更富有表现力的代码。除此之外,我们还实现了一系列的 弃用、稳定性改进和移除,旨在简化 Deno 的功能,并为 Deno 2 的到来做好准备。

如果你已经安装了 Deno,可以在终端中使用以下命令升级到 1.40 版本:

deno upgrade

如果你还没有安装 Deno,可以使用以下其中一条命令进行安装,或者 其他方式

# MacOS / Linux 安装
curl -fsSL https://deno.land/install.sh | sh

# Windows 安装
irm https://deno.land/install.ps1 | iex

以下是 Deno 1.40 的新功能概览:

  • Temporal API
  • import.meta.filenameimport.meta.dirname
  • 装饰器
  • 更简单的 deno.json 中的 imports
  • 弃用、稳定性改进和移除
  • Web API: rejectionhandled 事件
  • WebGPU 窗口管理 / “自带窗口”
  • Node.js API 更新
  • LSP 改进
  • 更美观的诊断信息
  • deno lint 更新
  • 对不稳定功能处理的变更

Temporal API

Temporal API 旨在解决 JavaScript 中现有 Date 对象存在的一些缺点和复杂性。

Temporal 提案已经被所有主要的 JavaScript 引擎积极实现,并我们很高兴地宣布它现在在 Deno 中通过 --unstable-temporal 标志可用。

const birthday = Temporal.PlainMonthDay.from("12-15");
const birthdayIn2030 = birthday.toPlainDate({ year: 2030 });
console.log(birthdayIn2030.toString());

console.log("day of week", birthdayIn2030.dayOfWeek);

Temporal 的用户友好型 API 使得解析和操作日期和时间变得简单。

Temporal API 不太可能发生更改,我们的目标是在 Deno 2 中稳定它。我们鼓励你探索 Temporal API 文档。

Deno 现在支持 import.meta.filenameimport.meta.dirname 属性。

这些属性镜像了 CommonJS 模块系统中的 __filename__dirname 属性:

  • import.meta.filename 提供当前模块文件的绝对路径
  • import.meta.dirname 提供包含当前模块文件的目录的绝对路径

这两个属性意识到了 OS 特定的路径分隔符,并为当前平台提供适当的分隔符:

console.log(import.meta.filename);
console.log(import.meta.dirname);

在 Unix 中:

$ deno run /dev/my_module.ts
/dev/my_module.ts
/dev/

而在 Windows 中:

$ deno run C:\dev\my_module.ts
C:\dev\my_module.ts 
C:\dev\

这些属性仅对本地模块(即从文件系统加载的模块)可用,并对远程模块(从 http://https:// 导入的模块)为 undefined

装饰器

Deno 现在支持 TC39 阶段 3 的 装饰器提案,这将很快在所有浏览器中实现。

装饰器是一种用于扩展 JavaScript 类的提案,广泛在转译环境中得到采用,被广泛关注于标准化。TC39 在过去的五年里一直在迭代装饰器提案。本文描述了基于过去所有提案的元素的一个新的装饰器提案。

此功能在 .ts.jsx.tsx 文件中可用。纯 JavaScript 的支持正在等待在 V8中的实现。

以下是一个 @trace 装饰器的示例,该装饰器在每次调用函数时记录日志,以及当它返回时:

function trace(fn: any, ctx: ClassMethodDecoratorContext) {
  return function (...args: unknown[]) {
    console.log("ENTERED", ctx.name);
    const v = fn(...args);
    console.log("EXITED", ctx.name);
    return v;
  };
}

class App {
  @trace
  static start() {
    console.log("Hello World!");
  }
}

App.start();

如果你依赖于旧的“实验性 TypeScript 装饰器”,你仍然可以在配置文件中使用以下配置来使用它们:

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

注意: 在 v1.40.0 版本之后发现了一个 bug,TypeScript 的 experimentalDecorators 仍然在 LSP 中打开。请升级到 v1.40.1 版本。

更简单的 deno.json 中的 imports

deno.json 中的 imports 字段现在支持更简单的语法来指定具有子路径导出的依赖项。以前,如果想要使用 npm 中的 preact,你必须将以下内容添加到 deno.jsonimports 对象中:

{
  "imports": {
    "preact": "npm:preact@10.5.13",
    "preact/": "npm:/preact@10.5.13/"
  }
}

这允许你同时导入 preactpreact/hooks 等子路径导出。

在这个版本中,我们简化了这一点,现在你只需:

{
  "imports": {
    "preact": "npm:preact@10.5.13"
  }
}

在 Deno 1.40.0 版本中,这将允许你从 npm 中导入 preactpreact/hooks

以前,Deno 将 deno.json 中的 imports 字段视为普通的 import map。现在,我们在 deno.json 中的 imports 字段中预处理,并将右侧带有 npm: 前缀的条目展开为我们用于解析的内部 import map 中的 两个 条目。

弃用、稳定性改进和移除

随着我们为 Deno 2 做准备,我们致力于优化运行时,同时确保从 Deno 1 平滑过渡。虽然大多数 Deno 1 代码仍然兼容,但我们正在简化某些 API 以保证平台的长期健康。

弃用

我们引入了弃用来逐步淘汰旧的API,并提供警告来协助您的迁移:

  • window – 全局变量 window 在整个 JavaScript 生态系统中经常用于测试代码是否在浏览器中运行。使用 globalThisself 来替代它是一个简单的修复方法。目前尚无此弃用的运行时警告,但将在此版本的 deno lint 中显示。
  • Deno.run() – 这是旧的且容易出错的子进程API。我们已经引入了更灵活的 [Deno.Command](https://deno.land/api?s=Deno.Command "Deno.Command") API,一年前已经稳定化
  • Deno.serveHttp() – 已被更快速、更简单的 Deno.serve() 替代。文档在这里
  • Deno.metrics() – 为了关注性能,我们正在转向更有针对性的命令行标志和API。通过 op_metrics_factory_fn 和新的 --strace-ops 标志进行运行时操作跟踪,可以使用自定义的度量实现。
  • 与流相关的函数:为了过渡到 Web Streams,我们正在弃用几个 Deno.Reader/Deno.Writer 流函数。这些已弃用的函数仍然可以通过 Deno 标准库访问:
  • Deno.FsWatcher.return() – 为了与其他异步迭代器对齐,我们正在弃用这个,推荐使用显式关闭方法。
  • Deno.customInspect – 为了鼓励与浏览器兼容的代码,我们已切换到 Symbol.for("Deno.customInspect")

在 Deno 2 中,我们将移除“资源ID”的概念。资源ID是对在 JavaScript 之外管理的套接字、文件或其他资源的整数引用。它们类似于文件描述符。然而,大多数用户不直接操作这些,因此我们希望开始引入由本机 JavaScript 对象引用的资源。因此,我们正在弃用以下API:

  • Deno.isatty() – 使用 isTerminal() 方法来替代,例如 Deno.stdout.isTerminal()
  • Deno.close() – 此API也对 rid 进行操作。建议用户在相关对象上使用 .close() 方法,例如 file.close() 而不是 Deno.close(file.rid)
  • Deno.resources() – 此API还公开资源ID,并且实用性很小。
  • Deno.ftruncate()Deno.ftruncateSync() – 这些函数现在在 Deno.FsFile 上作为 truncate()truncateSync() 提供。

所有 rid 属性现在都已被弃用,并将在 Deno 2.0 中删除:

  • Deno.Conn.rid
  • Deno.FsWatcher.rid
  • Deno.TcpConn.rid
  • Deno.TlsConn.rid
  • Deno.UnixConn.rid
  • Deno.Listener.rid

从文件加载证书现已弃用,请自行读取:

  • Deno.ListenTlsOptions.certFile - 使用 Deno.ListenTlsOptions.certDeno.readTextFile 代替。
  • Deno.ListenTlsOptions.keyFile - 使用 Deno.ListenTlsOptions.keyDeno.readTextFile 代替。
  • Deno.ConnectTlsOptions.certFile - 使用 Deno.ConnectTlsOptions.certDeno.readTextFile 代替。

稳定化

以下 Deno API 已经稳定化并取消标记:

  • Deno.Conn.ref()
  • Deno.Conn.unref()
  • Deno.connect() 用于 unix 传输
  • Deno.connectTls
  • Deno.stderr.isTerminal()
  • Deno.stdin.isTerminal()
  • Deno.stdout.isTerminal()
  • Deno.TlsConn.handshake()

移除

最后,不稳定的 API Deno.upgradeHttp 已被移除。这个 API 容易出错且容易被滥用。我们鼓励每个人使用 Deno.serve()Deno.upgradeWebsocket()

Web API: rejectionhandled 事件

我们添加了对 rejectionhandled事件 的支持,该事件在已经拒绝的 Promise 上附加了 .catch() 处理程序时触发。此外,只有在您有一个调用 event.preventDefault()unhandledrejection 事件监听器时,该事件才会触发(否则 Promise 将被拒绝并且进程将以错误退出)。

globalThis.addEventListener("unhandledrejection", (event) => {
  // Call `preventDefault()` to prevent the process from exiting.
  event.preventDefault();
  console.log("unhandledrejection", event.reason);
});

globalThis.addEventListener("rejectionhandled", (event) => {
  console.log(
    "A .catch() handler was added to the promise after it has already rejected.",
    event.reason,
  );
});

// Create a rejected promise...
const a = Promise.reject(new Error("boom!"));

// ...then attach a `.catch()` handler to after a timeout.
setTimeout(async () => {
  a.catch(() => console.log("Added catch handler to the promise"));
}, 10);
$ deno run main.js
unhandledrejection Error: boom!
    at file:///dev/main.js:12:26
Added catch handler to the promise
A .catch() handler was added to the promise after it has already rejected. Error: boom!
    at file:///dev/main.js:12:26

WebGPU 窗口化 / “自带窗口”

我们引入了一个新的不稳定的 Deno.UnsafeWindowSurface API 来处理 Deno 中的窗口。我们的目标是为 WebGPU 提供一个窗口化的解决方案,而无需链接到像 X11 这样的本机窗口系统。

这是一个可以被 FFI 窗口库(例如 sdl2glfwraylibwinit 等)使用的低级API,可以使用本机窗口和显示句柄创建一个 WebGPU 表面。

这里是使用 deno.land/x/sdl2 的示例:

import {
  EventType,
  WindowBuilder,
} from "https://deno.land/x/sdl2@0.8.0/mod.ts";

const win = new WindowBuilder("Hello, World!", 800, 600).build();

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();

/* Returns a Deno.UnsafeWindowSurface */
const surface = win.windowSurface();
/* Returns a WebGPU GPUCanvasContext */
const context = surface.getContext("webgpu");

context.configure({/* ... */});

for (const event of win.events()) {
  if (event.type == EventType.Quit) break;

  // Sine wave
  const r = Math.sin(Date.now() / 1000) / 2 + 0.5;
  const g = Math.sin(Date.now() / 1000 + 2) / 2 + 0.5;
  const b = Math.sin(Date.now() / 1000 + 4) / 2 + 0.5;

  const textureView = context.getCurrentTexture().createView();
  const renderPassDescriptor = {
    colorAttachments: [
      {
        view: textureView,
        clearValue: { r, g, b, a: 1.0 },
        loadOp: "clear",
        storeOp: "store",
      },
    ],
  };

  const commandEncoder = device.createCommandEncoder();
  const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
  passEncoder.end();

  device.queue.submit([commandEncoder.finish()]);
  surface.present();
}

使用 deno run --allow-ffi --unstable-webgpu --unstable-ffi 运行:

这是使用此 API 构建的演示音乐播放器应用程序的示例:github.com/littledivy/…

有关低级用法的更多详细信息,请查看 PR:github.com/denoland/de…

阅读有关 GPUCanvasContext 的更多信息 here

Node.js API 更新

以下内置的 Node API 现在可用:

  • crypto.pseudoRandomBytes()
  • fs.contants
  • fs.cp()
  • fs.cpSync()
  • fs.promises.cp()
  • net.ClientRequest.setNoDelay()
  • net.UdpSocket.ref()net.UdpSocket.unref()
  • net.WriteStream.isTTY
  • os.cpus()
  • os.machine()
  • process.abort()
  • process.on("rejectionHandled")

此外,我们在已支持的 Node.js API 中修复了一些错误:

  • 在 Windows 上修复了 child_process.ChildProcess.send()
  • crypto.createDeciperiv() 现在支持 aes-192-ecbaes-256-ecb
  • 修复了 fs.promises.readFile()
  • http.ClientRequest.socket.remoteAddress
  • http.ClientRequest.socket.remotePort
  • querystring.stringify()
  • test 模块支持嵌套测试
  • zlib.brotliCompress()
  • zlib.brotliCompressSync()
  • zlib.gzipSync()
  • zlib.unzipSync()

LSP 改进

自从 v1.39.0 以来,我们加强了与 TypeScript 的 Language Service API 的集成,以实现显著的性能提升和一些错误修复。由于更智能的项目状态同步和缓存,Rust 和 TypeScript 隔离之间的数据交换更加高效和不频繁。

对于 jsxImportSource 编译选项的用户来说,这是一个生活质量的提升:保存包含 deno.json 文件时,指定的远程资源将自动缓存。这是必要的,因为与未缓存的导入不同,由于缺少此缺失的资源而导致的诊断是模糊的,无法指向问题的原因(无论从用户还是语言服务器的快速修复生成器的角度)。

自动导入完成将更加一致地工作。由于 TypeScript 隔离中状态保存得更好,一些静默错误解决的情况得以解决。带有子路径的导入映射的 NPM 规范符正确地替换为预期的别名。

更好看的诊断

deno lintdeno doc 引入了新的诊断打印机。我们将在下一个版本中扩展到其他子命令。

deno lint 更新

deno lint 中有三个新规则:

  • no-console
  • no-self-compare
  • no-window

no-window 规则默认启用,而其他两个需要在配置文件中启用:

{
  "lint": {
    "rules": ["no-console", "no-self-compare"]
  }
}

保持代码质量对项目的成功至关重要,我们都曾经处于需要抑制来自 linter 的警告的情况。

在大多数情况下,我们只是忽略一个警告然后继续,但这种方法对于团队成员或未来的自己来说并不帮助理解为什么要抑制特定的警告。

deno lint 现在支持在 // deno-lint-ignore// deno-lint-ignore-file 指令中提供额外的解释:

// deno-lint-ignore-file -- This file is autogenerated, no need to lint it.

var __global$ = globalThis || (typeof window !== "undefined" ? window : self);
var cu=Object.create;var R=Object.defineProperty;var lu=Object.getOwnPropertyDescriptor;var iu=Object.getOwnPropertyNames;var nu=Object.getPrototypeOf...
// deno-lint-ignore no-empty -- I really need this fn have no body
export function noop() {}

对不稳定特性处理的变更

我们正在改进我们处理不稳定特性的方法。--unstable 标志虽然有用,但有点不够精确,同时激活所有不稳定特性。在 Deno 1.38 中,我们引入了更细粒度的标志,使您更精细地控制特定的不稳定特性,例如 --unstable-webgpu 启用了新的 WebGPU API。基于此,Deno 1.40 标志着广泛的 --unstable 标志进入弃用阶段的开始,为其在 Deno 2 中的移除做好准备。

此外,我们在 deno check 和 LSP 中改进了与不稳定 API 相关的类型检查。现在,在类型检查期间自动包括稳定和不稳定 API 的类型定义。这消除了访问不稳定 API 类型定义时必须指定 --unstable 的需要。但是,在运行程序时记得启用特定的不稳定特性标志。省略这些标志仍将导致错误,确保您知道正在使用的不稳定特性。

这一变更简化了开发,提供了对 Deno 特性集的更清晰和更精细的控制。

感谢我们的贡献者!

没有我们社区的帮助,我们无法构建 Deno!无论是在我们的社区 Discord 服务器 中回答问题还是 报告错误,我们对您的支持感到非常感激。特别感谢以下人士对 Deno 1.40 的贡献:Anwesh,Dean Srebnik,Joel Walker,Jovi De Croock,Julien Cayzac,Jérôme Benoit,Kenta Moriuchi,Kitson Kelly,Lino Le Van,Raashid Anwar,cions,king8fisher,nokazn,林炳权。

想成为 Deno 贡献者吗?在这里查看我们的贡献文档,我们希望下次在名单上见到您。

信不信由你,上面列出的变更仍然不能告诉您 1.40 中的一切变得更好了什么。您可以在 GitHub 上查看 Deno 1.40 合并的所有拉取请求的完整列表

感谢您了解我们的 1.40 版本,并希望您喜欢使用 Deno 构建!