最小化和更新后的 tsc --init
¥Minimal and Updated tsc --init
一段时间以来,TypeScript 编译器一直支持一个 --init 标志,该标志可以在当前目录中创建一个 tsconfig.json。在过去几年中,运行 tsc --init 会生成一个非常复杂的 “full” tsconfig.json,其中包含大量注释掉的设置及其描述。我们设计此功能的目的是为了让用户能够轻松发现并切换这些选项。
¥For a while, the TypeScript compiler has supported an --init flag that can create a tsconfig.json within the current directory.
In the last few years, running tsc --init created a very “full” tsconfig.json, filled with commented-out settings and their descriptions.
We designed this with the intent of making options discoverable and easy to toggle.
然而,根据外部反馈(以及我们自身的经验),我们发现通常的做法是立即删除这些新 tsconfig.json 文件的大部分内容。当用户想要发现新的选项时,我们发现他们依赖于编辑器的自动补齐功能,或者导航到 我们网站上的 tsconfig 参考(生成的 tsconfig.json 链接到 我们网站上的 tsconfig 参考!)。每个设置的具体作用也记录在同一页面上,并且可以通过编辑器悬停/工具提示/快速信息查看。虽然显示一些被注释掉的设置可能很有帮助,但生成的 tsconfig.json 通常被认为是过度设计。
¥However, given external feedback (and our own experience), we found it’s common to immediately delete most of the contents of these new tsconfig.json files.
When users want to discover new options, we find they rely on auto-complete from their editor, or navigate to the tsconfig reference on our website (which the generated tsconfig.json links to!).
What each setting does is also documented on that same page, and can be seen via editor hovers/tooltips/quick info.
While surfacing some commented-out settings might be helpful, the generated tsconfig.json was often considered overkill.
我们也认为,现在是时候让 tsc --init 初始化时包含一些比我们目前启用的设置更具规范性的选项了。我们研究了用户在创建新的 TypeScript 项目时遇到的一些常见痛点和问题。例如,大多数用户编写的是模块(而非全局脚本),而 --moduleDetection 可以强制 TypeScript 将每个实现文件视为一个模块。开发者通常也希望在运行时直接使用最新的 ECMAScript 特性,因此通常可以将 --target 设置为 esnext。JSX 用户经常发现,重新设置 --jsx 会带来不必要的麻烦,而且它的选项也略显复杂。而且,项目通常会从 node_modules/@types 加载比 TypeScript 实际需要的更多的声明文件;但指定一个空的 types 数组有助于限制这种情况。
¥We also felt that it was time that tsc --init initialized with a few more prescriptive settings than we already enable.
We looked at some common pain points and papercuts users have when they create a new TypeScript project.
For example, most users write in modules (not global scripts), and --moduleDetection can force TypeScript to treat every implementation file as a module.
Developers also often want to use the latest ECMAScript features directly in their runtime, so --target can typically be set to esnext.
JSX users often find that going back to set --jsx is needless friction, and its options are slightly confusing.
And often, projects end up loading more declaration files from node_modules/@types than TypeScript actually needs; but specifying an empty types array can help limit this.
在 TypeScript 5.9 中,不带任何其他标志的普通 tsc --init 将生成以下 tsconfig.json:
¥In TypeScript 5.9, a plain tsc --init with no other flags will generate the following tsconfig.json:
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,}}
¥For more details, see the implementing pull request and discussion issue.
支持 import defer
¥Support for import defer
TypeScript 5.9 引入了对 ECMAScript 的延迟模块评估提案 的支持,并使用了新的 import defer 语法。此功能允许你导入模块,而无需立即执行该模块及其依赖,从而更好地控制何时执行操作以及副作用的发生。
¥TypeScript 5.9 introduces support for ECMAScript’s deferred module evaluation proposal using the new import defer syntax.
This feature allows you to import a module without immediately executing the module and its dependencies, providing better control over when work and side-effects occur.
语法仅允许命名空间导入:
¥The syntax only permits namespace imports:
tsimport defer * as feature from "./some-feature.js";
import defer 的主要优点是,模块仅在首次访问其导出项时才会执行。请看以下示例:
¥The key benefit of import defer is that the module is only evaluated when one of its exports is first accessed.
Consider this example:
ts// ./some-feature.tsinitializationWithSideEffects();function initializationWithSideEffects() {// ...specialConstant = 42;console.log("Side effects have occurred!");}export let specialConstant: number;
使用 import defer 时,只有当你实际访问导入命名空间的属性时,才会调用 initializationWithSideEffects() 函数:
¥When using import defer, the initializationWithSideEffects() function will not be called until you actually access a property of the imported namespace:
tsimport defer * as feature from "./some-feature.js";// No side effects have occurred yet// ...// As soon as `specialConstant` is accessed, the contents of the `feature`// module are run and side effects have taken place.console.log(feature.specialConstant); // 42
由于模块的求值会延迟到你访问模块成员时才会进行,因此你不能将命名导入或默认导入与 import defer 一起使用:
¥Because evaluation of the module is deferred until you access a member off of the module, you cannot use named imports or default imports with import defer:
ts// ❌ Not allowedimport defer { doSomething } from "some-module";// ❌ Not allowedimport defer defaultExport from "some-module";// ✅ Only this syntax is supportedimport defer * as feature from "some-module";
请注意,当你编写 import defer 时,模块及其依赖已完全加载并准备就绪。这意味着模块必须存在,并且将从文件系统或网络资源加载。常规 import 和 import defer 的主要区别在于,语句和声明的执行会被延迟,直到你访问导入命名空间的属性。
¥Note that when you write import defer, the module and its dependencies are fully loaded and ready for execution.
That means that the module will need to exist, and will be loaded from the file system or a network resource.
The key difference between a regular import and import defer is that the execution of statements and declarations is deferred until you access a property of the imported namespace.
此功能对于有条件地加载具有昂贵初始化或平台特定初始化的模块尤其有用。它还可以通过将应用功能的模块评估延迟到实际需要时再进行,从而提高启动性能。
¥This feature is particularly useful for conditionally loading modules with expensive or platform-specific initialization. It can also improve startup performance by deferring module evaluation for app features until they are actually needed.
请注意,TypeScript 不会对 import defer 进行任何转换或 “downleveled” 转换。它旨在用于原生支持该功能的运行时,或用于可以应用适当转换的工具(例如打包工具)。这意味着 import defer 只能在 --module 模式 preserve 和 esnext 下工作。
¥Note that import defer is not transformed or “downleveled” at all by TypeScript.
It is intended to be used in runtimes that support the feature natively, or by tools such as bundlers that can apply the appropriate transformation.
That means that import defer will only work under the --module modes preserve and esnext.
我们要感谢 Nicolò Ribaudo,感谢他在 TC39 中提出该提案,并提供了 此功能的实现功能。
¥We’d like to extend our thanks to Nicolò Ribaudo who championed the proposal in TC39 and also provided the implementation for this feature.
支持 --module node20
¥Support for --module node20
TypeScript 为 --module 和 --moduleResolution 设置提供了多个 node* 选项。最近,--module nodenext 支持从 CommonJS 模块导入 ECMAScript 模块,并能正确地拒绝导入断言(转而使用符合标准的 导入属性)。
¥TypeScript provides several node* options for the --module and --moduleResolution settings.
Most recently, --module nodenext has supported the ability to require() ECMAScript modules from CommonJS modules, and correctly rejects import assertions (in favor of the standards-bound import attributes).
TypeScript 5.9 为这些设置引入了一个名为 node20 的稳定选项,旨在模拟 Node.js v20 的行为。与 --module nodenext 或 --moduleResolution nodenext 不同,此选项未来不太可能添加新的行为。与 nodenext 不同,指定 --module node20 将默认包含 --target es2023,除非另有配置。反之,--module nodenext 也隐含了浮点数 --target esnext。
¥TypeScript 5.9 brings a stable option for these settings called node20, intended to model the behavior of Node.js v20.
This option is unlikely to have new behaviors in the future, unlike --module nodenext or --moduleResolution nodenext.
Also unlike nodenext, specifying --module node20 will imply --target es2023 unless otherwise configured.
--module nodenext, on the other hand, implies the floating --target esnext.
更多信息请见 在此处查看实现!
¥For more information, take a look at the implementation here.
DOM API 中的摘要描述
¥Summary Descriptions in DOM APIs
以前,TypeScript 中的许多 DOM API 仅链接到 MDN 文档。这些链接很有用,但它们没有提供 API 功能的快速概述。得益于 Adam Naji 的一些更改,TypeScript 现在包含基于 MDN 文档的许多 DOM API 的摘要描述。你可以在 此处 和 此处 中查看更多此类更改。
¥Previously, many of the DOM APIs in TypeScript only linked to the MDN documentation for the API. These links were useful, but they didn’t provide a quick summary of what the API does. Thanks to a few changes from Adam Naji, TypeScript now includes summary descriptions for many DOM APIs based on the MDN documentation. You can see more of these changes here and here.
可展开的悬停效果(预览版)
¥Expandable Hovers (Preview)
快速信息(也称为 “编辑器工具提示” 和 “hovers”)对于快速查看变量类型或类型别名非常有用,可以了解它们实际指向的内容。不过,人们通常希望深入了解快速信息工具提示中显示的内容。例如,如果我们将鼠标悬停在以下示例中的参数 options 上:
¥Quick Info (also called “editor tooltips” and “hovers”) can be very useful for peeking at variables to see their types, or at type aliases to see what they actually refer to.
Still, it’s common for people to want to go deeper and get details from whatever’s displayed within the quick info tooltip.
For example, if we hover our mouse over the parameter options in the following example:
tsexport function drawButton(options: Options): void
我们最终得到 (parameter) options: Options。
¥We’re left with (parameter) options: Options.

我们真的需要跳转到类型 Options 的定义才能查看此值包含哪些成员吗?
¥Do we really need to jump to the definition of the type Options just to see what members this value has?
以前确实如此。为了更好地实现这一点,TypeScript 5.9 现在预览了一项名为“可展开悬停”(或 “快速信息详细程度”)的功能。如果你使用 VS Code 等编辑器,现在你会在这些悬停提示的左侧看到 + 和 - 按钮。单击 + 按钮会展开更深层次的类型,而单击 - 按钮会折叠到上一个视图。
¥Previously, that was actually the case.
To help here, TypeScript 5.9 is now previewing a feature called expandable hovers, or “quick info verbosity”.
If you use an editor like VS Code, you’ll now see a + and - button on the left of these hover tooltips.
Clicking on the + button will expand out types more deeply, while clicking on the - button will collapse to the last view.
¥
此功能目前处于预览阶段,我们正在征求 TypeScript 和 Visual Studio Code 合作伙伴的反馈。更多详情,请参阅 此功能的 PR 在此处。
¥This feature is currently in preview, and we are seeking feedback for both TypeScript and our partners on Visual Studio Code. For more details, see the PR for this feature here.
可配置的最大悬停长度
¥Configurable Maximum Hover Length
有时,快速提示信息会变得过长,TypeScript 会对其进行截断以提高可读性。缺点是,悬停提示信息中通常会省略最重要的信息,这可能会令人沮丧。为了更好地实现这一点,TypeScript 5.9 的语言服务器支持可配置的悬停长度,你可以通过 VS Code 中的 js/ts.hover.maximumLength 设置进行配置。
¥Occasionally, quick info tooltips can become so long that TypeScript will truncate them to make them more readable.
The downside here is that often the most important information will be omitted from the hover tooltip, which can be frustrating.
To help with this, TypeScript 5.9’s language server supports a configurable hover length, which can be configured in VS Code via the js/ts.hover.maximumLength setting.
另外,新的默认悬停长度比之前的默认值要大得多。这意味着在 TypeScript 5.9 中,默认情况下,你应该会在悬停提示中看到更多信息。更多详情,请参阅 此功能的 PR 在此处 和 Visual Studio Code 的相应变更在此处。
¥Additionally, the new default hover length is substantially larger than the previous default. This means that in TypeScript 5.9, you should see more information in your hover tooltips by default. For more details, see the PR for this feature here and the corresponding change to Visual Studio Code here.
优化
¥Optimizations
缓存映射器实例化
¥Cache Instantiations on Mappers
当 TypeScript 将类型参数替换为特定的类型实参时,它可能会反复实例化许多相同的中间类型。在像 Zod 和 tRPC 这样的复杂库中,这可能会导致性能问题以及与类型实例化深度过大相关的错误。得益于 Mateusz Burzyński 中的 一项变更,TypeScript 5.9 能够在特定类型实例化工作已经开始时缓存许多中间实例化。这反过来又避免了许多不必要的操作和内存分配。
¥When TypeScript replaces type parameters with specific type arguments, it can end up instantiating many of the same intermediate types over and over again. In complex libraries like Zod and tRPC, this could lead to both performance issues and errors reported around excessive type instantiation depth. Thanks to a change from Mateusz Burzyński, TypeScript 5.9 is able to cache many intermediate instantiations when work has already begun on a specific type instantiation. This in turn avoids lots of unnecessary work and allocations.
避免在 fileOrDirectoryExistsUsingSource 中创建闭包
¥Avoiding Closure Creation in fileOrDirectoryExistsUsingSource
在 JavaScript 中,函数表达式通常会分配一个新的函数对象,即使封装函数只是将参数传递给另一个没有捕获变量的函数。在文件存在性检查相关的代码路径中,即使底层函数只接受单个参数,Vincent Bailly 也发现了这些传递函数调用的示例。考虑到大型项目中可能需要进行大量的存在性检查,他指出速度提升了约 11%。查看此处有关此变更的更多信息。
¥In JavaScript, a function expression will typically allocate a new function object, even if the wrapper function is just passing through arguments to another function with no captured variables. In code paths around file existence checks, Vincent Bailly found examples of these pass-through function calls, even though the underlying functions only took single arguments. Given the number of existence checks that could take place in larger projects, he cited a speed-up of around 11%. See more on this change here.
值得注意的行为变更
¥Notable Behavioral Changes
lib.d.ts 变更
¥lib.d.ts Changes
为 DOM 生成的类型可能会对代码库的类型检查产生影响。
¥Types generated for the DOM may have an impact on type-checking your codebase.
此外,一个显著的变化是 ArrayBuffer 的属性已更改,它不再是多个不同 TypedArray 类型的超类型。这还包括 UInt8Array 的子类型,例如 Node.js 中的 Buffer。因此,你会看到如下新错误信息:
¥Additionally, one notable change is that ArrayBuffer has been changed in such a way that it is no longer a supertype of several different TypedArray types.
This also includes subtypes of UInt8Array, such as Buffer from Node.js.
As a result, you’ll see new error messages such as:
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 包。这可能包括运行
¥If you encounter issues with Buffer, you may first want to check that you are using the latest version of the @types/node package.
This might include running
npm update @types/node --save-dev
很多时候,解决方案是指定一个更具体的底层缓冲区类型,而不是使用默认的 ArrayBufferLike(即显式地写出 Uint8Array<ArrayBuffer> 而不是普通的 Uint8Array)。如果将某个 TypedArray(例如 Uint8Array)传递给一个期望接收 ArrayBuffer 或 SharedArrayBuffer 的函数,你也可以尝试访问该 TypedArray 的 buffer 属性,如下例所示:
¥Much of the time, the solution is to specify a more specific underlying buffer type instead of using the default ArrayBufferLike (i.e. explicitly writing out Uint8Array<ArrayBuffer> rather than a plain Uint8Array).
In instances where some TypedArray (like Uint8Array) is passed to a function expecting an ArrayBuffer or SharedArrayBuffer, you can also try accessing the buffer property of that TypedArray like in the following example:
difflet data = new Uint8Array([0, 1, 2, 3, 4]);- someFunc(data)+ someFunc(data.buffer)
类型参数推断变更
¥Type Argument Inference Changes
为了修复类型推断期间类型变量的 “leaks” 问题,TypeScript 5.9 可能会在某些代码库中引入类型更改,并可能引入新的错误。这些情况难以预测,但通常可以通过向泛型函数调用添加类型参数来解决。查看此处更多详细信息。
¥In an effort to fix “leaks” of type variables during inference, TypeScript 5.9 may introduce changes in types and possibly new errors in some codebases. These are hard to predict, but can often be fixed by adding type arguments to generic functions calls. See more details here.