TypeScript-5-9(译)
前言 🔗
原文地址:Announcing TypeScript 5.9
正文 🔗
精简 tsc --init 生成结果 🔗
TypeScript 编译器支持通过 --init 标志来在当前文件夹下创建 tsconfig.json 配置文件这一功能已经有一段时间了,在过去的几年,执行 tsc --init 会创建一个 “完整” 的 tsconfig.json 文件,包含注释掉的配置以及这些配置的说明。这样设计的初衷是让这些配置易于寻找并且容易切换。
然而,通过外部的反馈(以及我们自身的经验),我们发现在用户创建 tsconfig.json 后立即删除大部分的内容是一种很普遍的情况。当用户想要寻找某些新的配置时,我们发现他们会依赖编辑器的自动完成功能,或者去官网的 TSConfig Reference 站点翻阅(生成的 tsconfig.json 文件会关联这个链接)。这个页面上也记录了每个设置的作用,也能通过编辑器的 悬停/提示/快速信息 来查看。虽然展示一些被注释的设置可能会有帮助,但生成的 tsconfig.json 总感觉设计过头了。
我们觉得是时候让 tsc --init 初始化时生成比已启用的设置多一些更规范的设置了。我们审视了一些用户在创建 TypeScript 项目时的痛点和槽点,比如:
- 大多数用户编写模块化脚本(非全局脚本),
--moduledetection可以让 TypeScript 强制将每个实现的文件当作一个模块。 - 由于开发者很多时候想要在运行时直接使用最新的 ECMAScript 特性,所以
--target通常情况下可以设置为esnext。 - JSX 用户经常在重新设置
--jsx上产生不必要的摩擦,它的选项有轻微的矛盾。 - 很多时候,项目会从
node_modules/@types下加载比 TypeScript 实际需要的更多的声明文件,但通过将types设置为一个空的数组可以限制这种情况。
在 TypeScript 5.9 ,执行不带其他任何标志的 tsc --init 会生成如下的 tsconfig.json 文件:
{
// Visit https://aka.ms/tsconfig to read more about this file
"compilerOptions": {
// File Layout
// "rootDir": "./src",
// "outDir": "./dist",
// Environment Settings
// See also https://aka.ms/tsconfig_modules
"module": "nodenext",
"target": "esnext",
"types": [],
// For nodejs:
// "lib": ["esnext"],
// "types": ["node"],
// and npm install -D @types/node
// Other Outputs
"sourceMap": true,
"declaration": true,
"declarationMap": true,
// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,
// Recommended Options
"strict": true,
"jsx": "react-jsx",
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true
}
}支持 import defer 🔗
TypeScript 5.9 通过使用新的 import defer 语法引入了对 模块延迟计算提案 的支持。
这个特性允许你在导入一个模块的时候无需立即执行该模块以及它的依赖,这能更好地控制任务执行和副作用产生的时机。
该语法只允许通过命名空间导入:
import defer * as feature from "./some-feature.js";import defer 的主要优点是只有第一次使用到模块的导出项时它才会执行。考虑以下例子:
// ./some-feature.ts
initializationWithSideEffects();
function initializationWithSideEffects() {
// ...
specialConstant = 42;
console.log("Side effects have occurred!");
}
export let specialConstant: number;当使用 import defer 导入时, initializationWithSideEffects 函数不会执行,直到你实际上访问了导入命名空间上的属性。
import defer * as feature from "./some-feature.js";
// 副作用还未执行
// ...
// 一旦 `specialConstant` 被访问, feature 模块就立刻加载执行副作用
console.log(feature.specialConstant); // 42由于模块的计算会被延迟到获取模块成员的那一刻,所以使用 import defer 时无法使用具名导入或默认导入:
// ❌ 不允许
import defer { doSomething } from "some-module";
// ❌ 不允许
import defer defaultExport from "some-module";
// ✅ 只允许该语法
import defer * as feature from "some-module";请注意,使用 import defer 时,模块以及它的依赖会被完整地加载,准备完成后就会执行。这意味着模块需要存在,可以是从文件系统加载,也可以是从网络加载。常规的 import 导入和 import defer 导入的关键不同是语句和声明的执行会延迟到从导入的命名空间获取属性的时候。
这个特性对于那些有条件地加载昂贵的模块或者执行特定平台的初始化的情况格外有用。它也可以通过延迟模块加载直到这些应用功能实际需要用到时候,以此提升启动性能。
请注意,TypeScript 完全不会转化或者 “降级” import defer 语法。它的目的是让原生支持该特性的运行时处理,或者通过类似捆绑器的工具来进行合适的转化。这意味着 import defer 只能在 --module 为 preserve 和 esnext 下工作。
支持 --module node20 🔗
TypeScript 为 --module 和 --moduleResolution 设置提供了一些 node* 选项。最近, --module nodenext 支持了在 CommonJS 模块中 require ECMAScript 模块,并正确地拒绝导入断言(支持符合标准的 import attributes)。
TypeScript 5.9 为这些设置带来了一个稳定的选项 node20 ,其目的是模拟 Nodejs v20 的行为。这个选项与 --module nodenext 或者 -- moduleResolution nodenext 不同,它在未来不太可能会有新的行为。同时,与 nodenext 不同的是,指定 --module node20 会默认将 --target 设置为 es2023 ,除非你在其他地方配置。另一方面,--module nodenext 则会默认设置 --target esnext (该 esnext 的特性会动态更新)
更多信息,请翻阅这个 PR。
为 DOM API 添加简要描述 🔗
以前,TypeScript 内许多的 DOM API 只是简单链接到 MDN 对应的 API 文档上。这些链接有用,但是无法简要的描述 API 的作用。感谢 Adam Naji , TypeScript 现在基于 MDN 文档,为许多的 DOM API 提供了简要的描述。
可展开的悬停提示(预览功能) 🔗
快速信息(也叫编辑器提示或者悬浮)对观察变量的类型,或者它们实际关联的类型别名非常有用。不过,对于用户来说,想了解快速信息提示更深入的细节是很常见的需求。比如,在下面的例子中,如果我们在 options 参数上悬停鼠标:
export function drawButton(options: Options): void我们最终会得到 (parameter) options: Options 这样的提示。
我们真的需要跳到 Options 类型的定义处才能观察到它包含的成员吗?
在之前,我们确实需要这么做。为了更好的体验, TypeScript 5.9 提供了一个名叫可展开悬停提示,或者叫 “详情的快速信息” 的预览功能。如果你使用 VS Code 这样的编辑器,你就可以在这些悬停提示内容的左侧看到一个 + 和 - 的按钮。点击 + 按钮会展开更深层的类型,而点击 - 按钮则会收起它。
这个特性目前处于预览状态,我们会收集 TypeScript 以及我们在 VS Code 中成员的反馈。更多详情,查看这个 PR 。
可配置的最长悬停长度 🔗
某些时候,快速信息提示会变得非常的长, TypeScript 会裁剪这些提示使得它们更加可读。这里带来的缺点是很多时候许多重要的信息在悬停提示中被省略了,用户可能会感到沮丧。为了改善这种情况, TypeScript 5.9 语言服务支持了一个可配置的悬停长度配置,可以通过 VS Code 的 js/ts.hover.maximumLength 来设置。
并且,新的默认悬停长度的值比先前设置的默认值要大的多。这意味着在 TypeScript 5.9 ,默认情况下你可以看到更多悬停信息。更多细节可以查看这个 PR 以及 VS Code 对应的变更 PR 。
优化 🔗
缓存 Mappers 上的实例 🔗
当 TypeScript 将形参替换为特定的实参时,它最终会反复地实例化许多相同的中间类型。在类似 Zod 和 tRPC 这样复杂的库中,这会导致性能问题,继而引发由于过多类型实例化深度产生的错误。 TypeScript 5.9 能够在一个特定类型实例化工作开始时缓存许多中间的实例。这样就能避免许多不必要的工作和资源分配。
避免在 fileOrDirectoryExistsUsingSource 中创建闭包 🔗
在 JavaScript 中,一个函数表达式通常情况下会分配一个新的函数对象,即使包装函数不捕获变量,只是将参数传递给另一个函数。在文件存在性检查的相关代码路径中, Vincent Bailly 发现了这些透传的例子,即使底层函数只接受了单个参数。考虑到大型项目中可能需要执行大量的存在性检查,他指出删除这些透传代码大约能提升 11% 的速度,相关变更请查看这个 PR 。
需要注意的行为变更 🔗
lib.d.ts 🔗
DOM 的类型在可能会影响代码库的类型检查。
此外,有个需要注意的变更是, ArrayBuffer 不再是几种不同 TypedArray 类型的超类(父类)。这也包含了 UInt8Array 的子类,比如 Node.js 的 Buffer 。因此,你会看到如下的新错误信息:
error TS2345: Argument of type 'ArrayBufferLike' is not assignable to parameter of type 'BufferSource'.
error TS2322: Type 'ArrayBufferLike' is not assignable to type 'ArrayBuffer'.
error TS2322: Type 'Buffer' is not assignable to type 'Uint8Array<ArrayBufferLike>'.
error TS2322: Type 'Buffer' is not assignable to type 'ArrayBuffer'.
error TS2345: Argument of type 'Buffer' is not assignable to parameter of type 'string | Uint8Array<ArrayBufferLike>'.如果你遇到了 Buffer 相关的问题,首先可以检查是否正在使用罪行版本的 @types/node 包。如果需要,可以执行:
npm update @types/node --save-dev很多时候,可以通过指定一个更具体的底层 buffer 类型而非使用默认的 ArrayBufferLike (比如,显式使用 Uint8Array<ArrayBuffer> 而不是一个普通的 Uint8Array )来解决。在某些情况下,比如向一个期望 ArrayBuffer 或者 SharedArrayBuffer 的函数传递 TypedArray (比如 UInt8Array ) 时,你也可以尝试像下面这个例子一样获取 buffer 属性:
let data = new Uint8Array([0, 1, 2, 3, 4]);
someFunc(data)
someFunc(data.buffer)类型参数推断变更 🔗
为了修复推理过程中类型变量的“泄露”问题, TypeScript 5.9 引入了一些变更,这些变更可能在某些代码库上会产生错误。这种情况虽然很难预测,但可以通过添加类型参数泛型化函数调用来解决。更多细节查看这个 PR 。
