不允许的空值和真值检查
🌐 Disallowed Nullish and Truthy Checks
也许你写了一个正则表达式,但忘记在它上面调用 .test(...):
🌐 Maybe you’ve written a regex and forgotten to call .test(...) on it:
tsif (/0x[0-9a-f]/) {// Oops! This block always runs.// ...}
或者你可能不小心写成了 =>(这会创建一个箭头函数),而不是 >=(大于等于运算符):
🌐 or maybe you’ve accidentally written => (which creates an arrow function) instead of >= (the greater-than-or-equal-to operator):
tsif (x => 0) {// Oops! This block always runs.// ...}
或者你可能尝试使用 ?? 的默认值,但混淆了 ?? 和类似 < 的比较运算符的优先级:
🌐 or maybe you’ve tried to use a default value with ??, but mixed up the precedence of ?? and a comparison operator like <:
tsfunction isValid(value: string | number, options: any, strictness: "strict" | "loose") {if (strictness === "loose") {value = +value}return value < options.max ?? 100;// Oops! This is parsed as (value < options.max) ?? 100}
或者你可能在一个复杂的表达式中放错了括号:
🌐 or maybe you’ve misplaced a parenthesis in a complex expression:
tsif (isValid(primaryValue, "strict") || isValid(secondaryValue, "strict") ||isValid(primaryValue, "loose" || isValid(secondaryValue, "loose"))) {// ^^^^ 👀 Did we forget a closing ')'?}
这些例子都没有达到作者的预期目的,但它们都是有效的 JavaScript 代码。以前 TypeScript 也会悄悄地接受这些例子。
🌐 None of these examples do what the author intended, but they’re all valid JavaScript code. Previously TypeScript also quietly accepted these examples.
但是通过一点实验,我们发现,通过标记像上面这样的可疑示例,可以捕获许多许多错误。在 TypeScript 5.6 中,当编译器能够从语法上确定某个真值或空值检查总是以特定方式求值时,它现在会报错。因此,在上面的示例中,你会开始看到错误提示:
🌐 But with a little bit of experimentation, we found that many many bugs could be caught from flagging down suspicious examples like above. In TypeScript 5.6, the compiler now errors when it can syntactically determine a truthy or nullish check will always evaluate in a specific way. So in the above examples, you’ll start to see errors:
tsif (/0x[0-9a-f]/) {// ~~~~~~~~~~~~// error: This kind of expression is always truthy.}if (x => 0) {// ~~~~~~// error: This kind of expression is always truthy.}function isValid(value: string | number, options: any, strictness: "strict" | "loose") {if (strictness === "loose") {value = +value}return value < options.max ?? 100;// ~~~~~~~~~~~~~~~~~~~// error: Right operand of ?? is unreachable because the left operand is never nullish.}if (isValid(primaryValue, "strict") || isValid(secondaryValue, "strict") ||isValid(primaryValue, "loose" || isValid(secondaryValue, "loose"))) {// ~~~~~~~// error: This kind of expression is always truthy.}
通过启用 ESLint 的 no-constant-binary-expression 规则可以得到类似的结果,你可以在他们的博客文章中看到他们取得的一些成果;但是,TypeScript 执行的新检查与 ESLint 规则并不完全重合,我们也认为将这些检查集成到 TypeScript 本身中具有很大价值。
🌐 Similar results can be achieved by enabling the ESLint no-constant-binary-expression rule, and you can see some of the results they achieved in their blog post;
but the new checks TypeScript performs does not have perfect overlap with the ESLint rule, and we also believe there is a lot of value in having these checks built into TypeScript itself.
请注意,某些表达式仍然是允许的,即使它们总是为真值或空值。具体来说,true、false、0 和 1 仍然是允许的,尽管它们总是为真值或假值,因为像下面这样的代码:
🌐 Note that certain expressions are still allowed, even if they are always truthy or nullish.
Specifically, true, false, 0, and 1 are all still allowed despite always being truthy or falsy, since code like the following:
tswhile (true) {doStuff();if (something()) {break;}doOtherStuff();}
仍然是惯用且有用的,并且像下面这样的代码:
🌐 is still idiomatic and useful, and code like the following:
tsif (true || inDebuggingOrDevelopmentEnvironment()) {// ...}
在迭代/调试代码时很有用。
🌐 is useful while iterating/debugging code.
如果你对实现方式或它能捕捉到的错误类型感到好奇,可以看看实现此功能的拉取请求。
🌐 If you’re curious about the implementation or the sorts of bugs it catches, take a look at the pull request that implemented this feature.
迭代器辅助方法
🌐 Iterator Helper Methods
JavaScript 有 可迭代(可通过调用 ’Symbol.iterator’ 得到迭代器来迭代的事物)和 迭代者(具有 ‘next()’ 方法的事物,我们可以调用它来尝试在迭代中获得下一个值)。 总体来说,当你把它们扔进“for”/“of”循环,或者把它们放进“[…spread]”到新数组时,通常不需要考虑这些问题。 但TypeScript确实用’Iterable’和’Iterator’类型(甚至’IterableIterator’,它既是两个类型!)来建模这些类型,这些类型描述了像’for’/‘of’这样的构造所需的最小成员集合。
🌐 JavaScript has a notion of iterables (things which we can iterate over by calling a [Symbol.iterator]() and getting an iterator) and iterators (things which have a next() method which we can call to try to get the next value as we iterate).
By and large, you don’t typically have to think about these things when you toss them into a for/of loop, or [...spread] them into a new array.
But TypeScript does model these with the types Iterable and Iterator (and even IterableIterator which acts as both!), and these types describe the minimal set of members you need for constructs like for/of to work on them.
Iterable(和 IterableIterator)很方便,因为它们可以在 JavaScript 的各种地方使用——但很多人发现自己会想用 Array 上的方法,比如 map、filter,以及出于某种原因的 reduce。
这就是为什么 最近在 ECMAScript 提出了一项提案,旨在将 Array 上的许多方法(以及更多)添加到 JavaScript 中生成的大多数 IterableIterator 上。
例如,现在每个生成器都会生成一个对象,该对象还具有 map 方法和 take 方法。
🌐 For example, every generator now produces an object that also has a map method and a take method.
tsfunction* positiveIntegers() {let i = 1;while (true) {yield i;i++;}}const evenNumbers = positiveIntegers().map(x => x * 2);// Output:// 2// 4// 6// 8// 10for (const value of evenNumbers.take(5)) {console.log(value);}
对于 Map 和 Set 上的 keys()、values() 和 entries() 等方法也是如此。
🌐 The same is true for methods like keys(), values(), and entries() on Maps and Sets.
tsfunction invertKeysAndValues<K, V>(map: Map<K, V>): Map<V, K> {return new Map(map.entries().map(([k, v]) => [v, k]));}
你也可以扩展新的 Iterator 对象:
🌐 You can also extend the new Iterator object:
ts/*** Provides an endless stream of `0`s.*/class Zeroes extends Iterator<number> {next() {return { value: 0, done: false } as const;}}const zeroes = new Zeroes();// Transform into an endless stream of `1`s.const ones = zeroes.map(x => x + 1);
你可以使用 Iterator.from 将任何现有的 Iterable 或 Iterator 转换成这种新类型:
🌐 And you can adapt any existing Iterables or Iterators into this new type with Iterator.from:
tsIterator.from(...).filter(someFunction);
现在,我们必须讨论命名。
🌐 Now, we have to talk about naming.
之前我们提到过 TypeScript 有 Iterable 和 Iterator 的类型;然而,正如我们所说,这些类型有点像“协议”,用来确保某些操作可以正常工作。这意味着,并非每个在 TypeScript 中声明为 Iterable 或 Iterator 的值都具备上述方法。
🌐 Earlier we mentioned that TypeScript has types for Iterable and Iterator;
however, like we mentioned, these act sort of like “protocols” to ensure certain operations work.
That means that not every value that is declared Iterable or Iterator in TypeScript will have those methods we mentioned above.
但是仍然有一个新的 运行时值 叫做 Iterator。
你可以在 JavaScript 中把 Iterator 和 Iterator.prototype 当作实际值来引用。
这有点尴尬,因为 TypeScript 已经定义了一个名为 Iterator 的东西,仅用于类型检查。
因此,由于这个不幸的名字冲突,TypeScript 需要引入一个单独的类型来描述这些原生/内置的可迭代迭代器。
🌐 But there is still a new runtime value called Iterator.
You can reference Iterator, as well as Iterator.prototype, as actual values in JavaScript.
This is a bit awkward since TypeScript already defines its own thing called Iterator purely for type-checking.
So due to this unfortunate name clash, TypeScript needs to introduce a separate type to describe these native/built-in iterable iterators.
TypeScript 5.6 引入了一种名为 IteratorObject 的新类型。定义如下:
🌐 TypeScript 5.6 introduces a new type called IteratorObject.
It is defined as follows:
tsinterface IteratorObject<T, TReturn = unknown, TNext = unknown> extends Iterator<T, TReturn, TNext> {[Symbol.iterator](): IteratorObject<T, TReturn, TNext>;}
许多内置集合和方法会生成 IteratorObject 的子类型(如 ArrayIterator、SetIterator、MapIterator 等),lib.d.ts 中的核心 JavaScript 和 DOM 类型,以及 @types/node 都已更新为使用这种新类型。
🌐 Lots of built-in collections and methods produce subtypes of IteratorObjects (like ArrayIterator, SetIterator, MapIterator, and more), and both the core JavaScript and DOM types in lib.d.ts, along with @types/node, have been updated to use this new type.
同样,也有一个用于奇偶性的 AsyncIteratorObject 类型。AsyncIterator 在 JavaScript 中尚不存在作为运行时值,用于为 AsyncIterable 提供相同的方法,但它是一个正在进行中的提案,这个新类型为其做好了准备。
🌐 Similarly, there is a AsyncIteratorObject type for parity.
AsyncIterator does not yet exist as a runtime value in JavaScript that brings the same methods for AsyncIterables, but it is an active proposal and this new type prepares for it.
我们想感谢 Kevin Gibbons 对这些类型的改动所做的贡献,他也是 提案 的共同作者之一。
🌐 We’d like to thank Kevin Gibbons who contributed the changes for these types, and who is one of the co-authors of the proposal.
严格的内置迭代器检查(以及 --strictBuiltinIteratorReturn)
🌐 Strict Builtin Iterator Checks (and --strictBuiltinIteratorReturn)
当你在 Iterator<T, TReturn> 上调用 next() 方法时,它会返回一个带有 value 和 done 属性的对象。这个对象的类型是 IteratorResult。
🌐 When you call the next() method on an Iterator<T, TReturn>, it returns an object with a value and a done property.
This is modeled with the type IteratorResult.
tstype IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;interface IteratorYieldResult<TYield> {done?: false;value: TYield;}interface IteratorReturnResult<TReturn> {done: true;value: TReturn;}
这里的命名灵感来自生成器函数的工作方式。生成器函数可以 yield 值,然后 return 一个最终值——但两者之间的类型可以是无关的。
🌐 The naming here is inspired by the way a generator function works.
Generator functions can yield values, and then return a final value - but the types between the two can be unrelated.
tsfunction abc123() {yield "a";yield "b";yield "c";return 123;}const iter = abc123();iter.next(); // { value: "a", done: false }iter.next(); // { value: "b", done: false }iter.next(); // { value: "c", done: false }iter.next(); // { value: 123, done: true }
使用新的 IteratorObject 类型时,我们发现实现安全的 IteratorObject 存在一些困难。与此同时,IteratorResult 在 TReturn 为 any(默认值!)的情况下长期存在不安全问题。例如,假设我们有一个 IteratorResult<string, any>。如果我们最终访问了这种类型的 value,我们将得到 string | any,它只是 any。
🌐 With the new IteratorObject type, we discovered some difficulties in allowing safe implementations of IteratorObjects.
At the same time, there’s been a long standing unsafety with IteratorResult in cases where TReturn was any (the default!).
For example, let’s say we have an IteratorResult<string, any>.
If we end up reaching for the value of this type, we’ll end up with string | any, which is just any.
tsfunction* uppercase(iter: Iterator<string, any>) {while (true) {const { value, done } = iter.next();yield value.toUppercase(); // oops! forgot to check for `done` first and misspelled `toUpperCase`if (done) {return;}}}
今天在每个 Iterator 上修复这个问题可能很困难,因为这样会引入很多故障,但我们至少可以修复大多数创建的 IteratorObject。
🌐 It would be hard to fix this on every Iterator today without introducing a lot of breaks, but we can at least fix it with most IteratorObjects that get created.
TypeScript 5.6 引入了一种新的内置类型,称为 BuiltinIteratorReturn,以及一个新的 --strict 模式标志,称为 --strictBuiltinIteratorReturn。
每当在像 lib.d.ts 这样的地方使用 IteratorObject 时,它们总是以 TReturn 的 BuiltinIteratorReturn 类型书写(尽管你更常看到具体的 MapIterator、ArrayIterator、SetIterator)。
🌐 TypeScript 5.6 introduces a new intrinsic type called BuiltinIteratorReturn and a new --strict-mode flag called --strictBuiltinIteratorReturn.
Whenever IteratorObjects are used in places like lib.d.ts, they are always written with BuiltinIteratorReturn type for TReturn (though you’ll see the more-specific MapIterator, ArrayIterator, SetIterator more often).
tsinterface MapIterator<T> extends IteratorObject<T, BuiltinIteratorReturn, unknown> {[Symbol.iterator](): MapIterator<T>;}// ...interface Map<K, V> {// .../*** Returns an iterable of key, value pairs for every entry in the map.*/entries(): MapIterator<[K, V]>;/*** Returns an iterable of keys in the map*/keys(): MapIterator<K>;/*** Returns an iterable of values in the map*/values(): MapIterator<V>;}
默认情况下,BuiltinIteratorReturn 是 any,但当启用 --strictBuiltinIteratorReturn(可能通过 --strict)时,它就是 undefined。在这种新模式下,如果我们使用 BuiltinIteratorReturn,之前的例子现在会正确报错:
🌐 By default, BuiltinIteratorReturn is any, but when --strictBuiltinIteratorReturn is enabled (possibly via --strict), it is undefined.
Under this new mode, if we use BuiltinIteratorReturn, our earlier example now correctly errors:
tsfunction* uppercase(iter: Iterator<string, BuiltinIteratorReturn>) {while (true) {const { value, done } = iter.next();yield value.toUppercase();// ~~~~~ ~~~~~~~~~~~// error! ┃ ┃// ┃ ┗━ Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?// ┃// ┗━ 'value' is possibly 'undefined'.if (done) {return;}}}
你通常会在整个 lib.d.ts 中看到 BuiltinIteratorReturn 与 IteratorObject 配对使用。一般来说,我们建议在自己的代码中尽可能对 TReturn 更加明确。
🌐 You’ll typically see BuiltinIteratorReturn paired up with IteratorObject throughout lib.d.ts.
In general, we recommend being more explicit around the TReturn in your own code when possible.
欲了解更多信息,你可以在这里阅读有关该功能的详细介绍。
🌐 For more information, you can read up on the feature here.
支持任意模块标识符
🌐 Support for Arbitrary Module Identifiers
JavaScript 允许模块将具有无效标识符名称的绑定导出为字符串字面量:
🌐 JavaScript allows modules to export bindings with invalid identifier names as string literals:
tsconst banana = "🍌";export { banana as "🍌" };
同样,它允许模块获取具有以下任意名称的导入,并将它们绑定到有效的标识符:
🌐 Likewise, it allows modules to grab imports with these arbitrary names and bind them to valid identifiers:
tsimport { "🍌" as banana } from "./foo"/*** om nom nom*/function eat(food: string) {console.log("Eating", food);};eat(banana);
这看起来像是一个有趣的派对技巧(如果你和我们一样喜欢在派对上玩乐的话),但它在与其他语言的互操作性方面确实有用(通常通过 JavaScript/WebAssembly 边界),因为其他语言对有效标识符的规则可能不同。
它对生成代码的工具也很有用,比如 esbuild 使用其 inject 功能。
🌐 This seems like a cute party trick (if you’re as fun as we are at parties), but it has its uses for interoperability with other languages (typically via JavaScript/WebAssembly boundaries), since other languages may have different rules for what constitutes a valid identifier.
It can also be useful for tools that generate code, like esbuild with its inject feature.
TypeScript 5.6 现在允许你在代码中使用这些任意的模块标识符! 我们要感谢 Evan Wallace 对 TypeScript 的这次 贡献!
🌐 TypeScript 5.6 now allows you to use these arbitrary module identifiers in your code! We’d like to thank Evan Wallace who contributed this change to TypeScript!
--noUncheckedSideEffectImports 选项
🌐 The --noUncheckedSideEffectImports Option
在 JavaScript 中,可以在不实际从模块中导入任何值的情况下 import 一个模块。
🌐 In JavaScript it’s possible to import a module without actually importing any values from it.
tsimport "some-module";
这些导入通常被称为副作用导入,因为它们唯一有用的行为就是执行某些副作用(比如注册全局变量,或者向原型添加一个 polyfill)。
🌐 These imports are often called side effect imports because the only useful behavior they can provide is by executing some side effect (like registering a global variable, or adding a polyfill to a prototype).
在 TypeScript 中,这种语法有一个相当奇怪的特点:如果 import 可以解析为一个有效的源文件,那么 TypeScript 就会加载并检查该文件。另一方面,如果找不到任何源文件,TypeScript 则会默默地忽略 import!
🌐 In TypeScript, this syntax has had a pretty strange quirk: if the import could be resolved to a valid source file, then TypeScript would load and check the file.
On the other hand, if no source file could be found, TypeScript would silently ignore the import!
这是令人惊讶的行为,但它部分源于 JavaScript 生态系统中的建模模式。例如,这种语法也曾在打包工具中与特殊加载器一起使用,以加载 CSS 或其他资源。你的打包工具可能被配置成可以通过如下方式包含特定的 .css 文件:
🌐 This is surprising behavior, but it partially stems from modeling patterns in the JavaScript ecosystem.
For example, this syntax has also been used with special loaders in bundlers to load CSS or other assets.
Your bundler might be configured in such a way where you can include specific .css files by writing something like the following:
tsximport "./button-component.css";export function Button() {// ...}
不过,这掩盖了副作用导入中可能存在的拼写错误。这就是为什么 TypeScript 5.6 引入了一个名为 --noUncheckedSideEffectImports 的新编译器选项,用于捕获这些情况。当启用 --noUncheckedSideEffectImports 时,如果 TypeScript 找不到副作用导入的源文件,它现在会报错。
🌐 Still, this masks potential typos on side effect imports.
That’s why TypeScript 5.6 introduces a new compiler option called --noUncheckedSideEffectImports, to catch these cases.
When --noUncheckedSideEffectImports is enabled, TypeScript will now error if it can’t find a source file for a side effect import.
tsimport "oops-this-module-does-not-exist";// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// error: Cannot find module 'oops-this-module-does-not-exist' or its corresponding type declarations.
启用此选项时,一些原本可用的代码可能会出现错误,就像上面的 CSS 示例一样。
为了解决这个问题,那些只想为资源编写副作用 import 的用户可能更适合编写所谓的带有通配符指定符的环境模块声明。
它将放在全局文件中,看起来大致如下:
🌐 When enabling this option, some working code may now receive an error, like in the CSS example above.
To work around this, users who want to just write side effect imports for assets might be better served by writing what’s called an ambient module declaration with a wildcard specifier.
It would go in a global file and look something like the following:
ts// ./src/globals.d.ts// Recognize all CSS files as module imports.declare module "*.css" {}
事实上,你的项目中可能已经有这样的文件了!
例如,运行类似 vite init 的命令可能会创建一个类似的 vite-env.d.ts。
🌐 In fact, you might already have a file like this in your project!
For example, running something like vite init might create a similar vite-env.d.ts.
虽然此选项目前默认关闭,但我们鼓励用户尝试一下!
🌐 While this option is currently off by default, we encourage users to give it a try!
欲了解更多信息,请查看此处的实现。
🌐 For more information, check out the implementation here.
--noCheck 选项
🌐 The --noCheck Option
TypeScript 5.6 引入了一个新的编译器选项 --noCheck,它允许你跳过对所有输入文件的类型检查。这可以在执行生成输出文件所需的任何语义分析时避免不必要的类型检查。
🌐 TypeScript 5.6 introduces a new compiler option, --noCheck, which allows you to skip type checking for all input files.
This avoids unnecessary type-checking when performing any semantic analysis necessary for emitting output files.
一种情景是将 JavaScript 文件生成与类型检查分开,以便两者可以作为独立阶段运行。
例如,你可以在迭代过程中运行 tsc --noCheck,然后运行 tsc --noEmit 进行彻底的类型检查。
你也可以在并行模式下运行这两个任务,即使在 --watch 模式下也是如此,不过需要注意,如果你确实同时运行它们,可能需要指定一个单独的 --tsBuildInfoFile 路径。
🌐 One scenario for this is to separate JavaScript file generation from type-checking so that the two can be run as separate phases.
For example, you could run tsc --noCheck while iterating, and then tsc --noEmit for a thorough type check.
You could also run the two tasks in parallel, even in --watch mode, though note you’d probably want to specify a separate --tsBuildInfoFile path if you’re truly running them at the same time.
--noCheck 也可以用于以类似的方式生成声明文件。在一个在 --isolatedDeclarations 规范下指定了 --noCheck 的项目中,TypeScript 可以在不进行类型检查的情况下快速生成声明文件。生成的声明文件将完全依赖于快速的语法转换。
请注意,在指定了 --noCheck 的情况下,但项目未使用 --isolatedDeclarations 时,TypeScript 仍可能执行必要的类型检查以生成 .d.ts 文件。
从这个意义上说,--noCheck 有点名不副实;然而,该过程将比完整的类型检查更为懒惰,只计算未注释声明的类型。
这应该比完整的类型检查快得多。
🌐 Note that in cases where --noCheck is specified, but a project does not use --isolatedDeclarations, TypeScript may still perform as much type-checking as necessary to generate .d.ts files.
In this sense, --noCheck is a bit of a misnomer; however, the process will be lazier than a full type-check, only calculating the types of unannotated declarations.
This should be much faster than a full type-check.
noCheck 也可以通过 TypeScript API 作为标准选项使用。
在内部,transpileModule 和 transpileDeclaration 已经使用了 noCheck 来提高速度(至少从 TypeScript 5.5 开始)。
现在,任何构建工具都应该能够利用这个标志,采用各种自定义策略来协调和加快构建速度。
有关更多信息,请参见在 TypeScript 5.5 中为内部增强 noCheck 所做的工作,以及使其在命令行上公开可用的相关工作
🌐 For more information, see the work done in TypeScript 5.5 to power up noCheck internally, along with the relevant work to make it publicly available on the command line and
允许 --build 出现中间错误
🌐 Allow --build with Intermediate Errors
TypeScript 的 项目引用 概念允许你将代码库组织成多个项目,并在它们之间创建依赖。在 --build 模式(简称 tsc -b)下运行 TypeScript 编译器,是在项目间实际进行构建并确定哪些项目和文件需要编译的内置方式。
🌐 TypeScript’s concept of project references allows you to organize your codebase into multiple projects and create dependencies between them.
Running the TypeScript compiler in --build mode (or tsc -b for short) is the built-in way of actually conducting that build across projects and figuring out which projects and files need to be compiled.
以前,使用 --build 模式会假设 --noEmitOnError,并且一旦遇到任何错误就立即停止构建。这意味着,如果其“上游”依赖存在构建错误,“下游”项目将无法被检查和构建。理论上,这是一个很合理的方法——如果一个项目存在错误,它的依赖不一定处于一致的状态。
🌐 Previously, using --build mode would assume --noEmitOnError and immediately stop the build if any errors were encountered.
This meant that “downstream” projects could never be checked and built if any of their “upstream” dependencies had build errors.
In theory, this is a very cromulent approach - if a project has errors, it is not necessarily in a coherent state for its dependencies.
实际上,这种僵化使升级之类的事情变得很麻烦。例如,如果 projectB 依赖于 projectA,那么更熟悉 projectB 的人就无法主动升级他们的代码,直到他们依赖的内容被升级。他们会被必须先升级 projectA 的工作所阻碍。
🌐 In reality, this sort of rigidity made things like upgrades a pain.
For example, if projectB depends on projectA, then people more familiar with projectB can’t proactively upgrade their code until their dependencies are upgraded.
They are blocked by work on upgrading projectA first.
从 TypeScript 5.6 开始,即使依赖中存在中间错误,--build 模式也会继续构建项目。在遇到中间错误时,它们将被一致地报告,并且输出文件将在尽力的基础上生成;然而,构建将继续直到指定项目完成。
🌐 As of TypeScript 5.6, --build mode will continue to build projects even if there are intermediate errors in dependencies.
In the face of intermediate errors, they will be reported consistently and output files will be generated on a best-effort basis;
however, the build will continue to completion on the specified project.
如果你想在第一个出现错误的项目上停止构建,可以使用一个名为 --stopOnBuildErrors 的新标志。这在 CI 环境中运行,或者在对被其他项目严重依赖的项目进行迭代时非常有用。
🌐 If you want to stop the build on the first project with errors, you can use a new flag called --stopOnBuildErrors.
This can be useful when running in a CI environment, or when iterating on a project that’s heavily depended upon by other projects.
请注意,为了实现这一点,TypeScript 现在在任何 --build 调用的项目中始终会生成一个 .tsbuildinfo 文件(即使未指定 --incremental/--composite)。
这是为了跟踪 --build 的调用状态以及未来需要执行的工作。
🌐 Note that to accomplish this, TypeScript now always emits a .tsbuildinfo file for any project in a --build invocation (even if --incremental/--composite is not specified).
This is to keep track of the state of how --build was invoked and what work needs to be performed in the future.
你可以在此处关于实现的内容了解更多信息。
🌐 You can read more about this change here on the implementation.
编辑器中的区域优先诊断
🌐 Region-Prioritized Diagnostics in Editors
当 TypeScript 的语言服务被要求提供某个文件的诊断(例如错误、建议和弃用信息)时,通常需要检查整个文件。大多数时候这是没问题的,但在非常大的文件中可能会产生延迟。这会让人感到沮丧,因为修复一个拼写错误本应是一个快速操作,但在足够大的文件中可能需要几秒钟。
🌐 When TypeScript’s language service is asked for the diagnostics for a file (things like errors, suggestions, and deprecations), it would typically require checking the entire file. Most of the time this is fine, but in extremely large files it can incur a delay. That can be frustrating because fixing a typo should feel like a quick operation, but can take seconds in a big-enough file.
为了解决这个问题,TypeScript 5.6 引入了一个新的功能,称为 区域优先诊断 或 区域优先检查。不仅可以为一组文件请求诊断,编辑器现在还可以提供一个文件的相关区域——通常指当前用户可见的文件区域。TypeScript 语言服务器随后可以选择提供两组诊断:一组针对该区域,另一组针对整个文件。这使得在大型文件中编辑时感觉更加 高响应,不必等待红色波浪线消失那么久。
🌐 To address this, TypeScript 5.6 introduces a new feature called region-prioritized diagnostics or region-prioritized checking. Instead of just requesting diagnostics for a set of files, editors can now also provide a relevant region of a given file - and the intent is that this will typically be the region of the file that is currently visible to a user. The TypeScript language server can then choose to provide two sets of diagnostics: one for the region, and one for the file in its entirety. This allows editing to feel way more responsive in large files so you’re not waiting as long for thoes red squiggles to disappear.
对于一些特定的数字,在我们的测试中 在 TypeScript 自身的 checker.ts,完整的语义诊断响应耗时 3330 毫秒。相比之下,第一个基于区域的诊断响应只用了 143 毫秒!而剩下的整个文件响应大约需要 3200 毫秒,这对于快速编辑来说可能有很大的差异。
🌐 For some specific numbers, in our testing on TypeScript’s own checker.ts, a full semantic diagnostics response took 3330ms.
In contrast, the response for the first region-based diagnostics response took 143ms!
While the remaining whole-file response took about 3200ms, this can make a huge difference for quick edits.
此功能还包括相当多的工作,以使诊断在整个使用体验中报告得更加一致。由于我们的类型检查器利用缓存来避免重复工作,相同类型之间的后续检查通常可能会产生不同(通常更短)的错误信息。从技术上讲,延迟的非顺序检查可能会导致编辑器中两个位置之间的诊断报告不同——即使在此功能推出之前也是如此——但我们不想加剧这个问题。通过最近的工作,我们已经解决了许多此类错误不一致的问题。
🌐 This feature also includes quite a bit of work to also make diagnostics report more consistently throughout your experience. Due the way our type-checker leverages caching to avoid work, subsequent checks between the same types could often have a different (typically shorter) error message. Technically, lazy out-of-order checking could cause diagnostics to report differently between two locations in an editor - even before this feature - but we didn’t want to exacerbate the issue. With recent work, we’ve ironed out many of these error inconsistencies.
目前,此功能在 Visual Studio Code 中适用于 TypeScript 5.6 及更高版本。
🌐 Currently, this functionality is available in Visual Studio Code for TypeScript 5.6 and later.
欲了解更多详细信息,请查看此处的实现和说明。
🌐 For more detailed information, take a look at the implementation and write-up here.
粒度提交字符
🌐 Granular Commit Characters
TypeScript 的语言服务现在为每个完成项提供自己的提交字符。提交字符是指当输入这些特定字符时,会自动确认当前建议的完成项的字符。
🌐 TypeScript’s language service now provides its own commit characters for each completion item. Commit characters are specific characters that, when typed, will automatically commit the currently-suggested completion item.
这意味着随着时间的推移,当你输入某些字符时,你的编辑器现在会更频繁地确认当前建议的完成项。例如,请看以下代码:
🌐 What this means is that over time your editor will now more frequently commit to the currently-suggested completion item when you type certain characters. For example, take the following code:
tsdeclare let food: {eat(): any;}let f = (foo/**/
如果我们的光标在 /**/ 位置,我们不清楚我们正在编写的代码会是类似 let f = (food.eat()) 还是 let f = (foo, bar) => foo + bar。
你可以想象,根据我们接下来输入的字符,编辑器可能会有不同的自动补全功能。
例如,如果我们输入句点字符 (.),我们可能希望编辑器补全变量 food;
但如果我们输入逗号字符 (,),我们可能正在写一个箭头函数的参数。
🌐 If our cursor is at /**/, it’s unclear if the code we’re writing is going to be something like let f = (food.eat()) or let f = (foo, bar) => foo + bar.
You could imagine that the editor might be able to auto-complete differently depending on which character we type out next.
For instance, if we type in the period/dot character (.), we probably want the editor to complete with the variable food;
but if we type the comma character (,), we might be writing out a parameter in an arrow function.
不幸的是,以前 TypeScript 只是向编辑器提示当前文本可能定义一个新的参数名称,因此没有任何提交字符是安全的。所以即使很明显编辑器应该使用单词 food 自动补齐,按下 . 也不会有任何效果。
🌐 Unfortunately, previously TypeScript just signaled to editors that the current text might define a new parameter name so that no commit characters were safe.
So hitting a . wouldn’t do anything even if it was “obvious” that the editor should auto-complete with the word food.
TypeScript 现在明确列出了每个补全项可安全提交的字符。
虽然这不会立即改变你日常的使用体验,但支持这些提交字符的编辑器随着时间推移应会看到行为上的改进。
若想立即看到这些改进,你现在可以使用 TypeScript 夜间版扩展 与 Visual Studio Code Insiders。
在上面的代码中按下 . 会正确地自动补齐 food。
🌐 TypeScript now explicitly lists which characters are safe to commit for each completion item.
While this won’t immediately change your day-to-day experience, editors that support these commit characters should see behavioral improvements over time.
To see those improvements right now, you can now use the TypeScript nightly extension with Visual Studio Code Insiders.
Hitting . in the code above correctly auto-completes with food.
欲了解更多信息,请参阅 添加提交字符的拉取请求 以及我们关于 根据上下文调整提交字符的说明。
🌐 For more information, see the pull request that added commit characters along with our adjustments to commit characters depending on context.
自动导入的排除模式
🌐 Exclude Patterns for Auto-Imports
TypeScript 的语言服务现在允许你指定一组正则表达式模式,这些模式可以过滤掉来自某些指定源的自动导入建议。例如,如果你想排除来自像 lodash 这样的包的所有“深度”导入,你可以在 Visual Studio Code 中配置以下首选项:
🌐 TypeScript’s language service now allows you to specify a list of regular expression patterns which will filter away auto-import suggestions from certain specifiers.
For example, if you want to exclude all “deep” imports from a package like lodash, you could configure the following preference in Visual Studio Code:
json{"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^lodash/.*$"]}
或者反过来,你可能希望禁止从包的入口点导入:
🌐 Or going the other way, you might want to disallow importing from the entry-point of a package:
json{"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^lodash$"]}
甚至可以通过使用以下设置来避免 node: 导入:
🌐 One could even avoid node: imports by using the following setting:
json{"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^node:"]}
请注意,如果你想指定某些标志,如 i 或 u,你需要用斜杠将你的正则表达式包围起来。在提供包围斜杠时,你需要对内部的其他斜杠进行转义。
🌐 Note that if you want to specify certain flags like i or u, you will need to surround your regular expression with slashes.
When providing surrounding slashes, you’ll need to escape other inner slashes.
json{"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^./lib/internal", // no escaping needed"/^.\\/lib\\/internal/", // escaping needed - note the leading and trailing slashes"/^.\\/lib\\/internal/i" // escaping needed - we needed slashes to provide the 'i' regex flag]}
在 Visual Studio Code 中,相同的设置也可以通过 javascript.preferences.autoImportSpecifierExcludeRegexes 应用于 JavaScript。
🌐 In Visual Studio Code, the same settings can be applied for JavaScript through javascript.preferences.autoImportSpecifierExcludeRegexes.
欲了解更多信息,请在此查看实现。
🌐 For more information, see the implementation here.
值得注意的行为变更
🌐 Notable Behavioral Changes
本节重点介绍了一些值得注意的变化,这些变化在任何升级中都应予以认可和理解。有时,它会高亮已弃用、移除和新的限制。它还可能包含作为功能性改进的错误修复,但这些修复也可能通过引入新的错误影响现有构建。
🌐 This section highlights a set of noteworthy changes that should be acknowledged and understood as part of any upgrade. Sometimes it will highlight deprecations, removals, and new restrictions. It can also contain bug fixes that are functionally improvements, but which can also affect an existing build by introducing new errors.
lib.d.ts
为 DOM 生成的类型可能会影响对代码库的类型检查。
更多信息,请参见与 DOM 和此版本 TypeScript 的 lib.d.ts 更新相关的链接问题。
🌐 Types generated for the DOM may have an impact on type-checking your codebase.
For more information, see linked issues related to DOM and lib.d.ts updates for this version of TypeScript.
.tsbuildinfo 总是被写成
🌐 .tsbuildinfo is Always Written
为了使 --build 即使在依赖中存在中间错误也能继续构建项目,并且支持命令行上的 --noCheck,TypeScript 现在总是为任何在 --build 调用中的项目生成一个 .tsbuildinfo 文件。
无论 --incremental 是否实际开启,这种情况都会发生。
在此查看更多信息。
🌐 To enable --build to continue building projects even if there are intermediate errors in dependencies, and to support --noCheck on the command line, TypeScript now always emits a .tsbuildinfo file for any project in a --build invocation.
This happens regardless of whether --incremental is actually on.
See more information here.
在 node_modules 中尊重文件扩展名和 package.json
🌐 Respecting File Extensions and package.json from within node_modules
在 Node.js 在 v12 中实现对 ECMAScript 模块的支持之前,TypeScript 从未有过一个很好的方法来判断它在 node_modules 中找到的 .d.ts 文件是以 CommonJS 还是 ECMAScript 模块编写的 JavaScript 文件。
当绝大多数 npm 包只使用 CommonJS 时,这并不会引起太多问题——如果不确定,TypeScript 可以假定所有内容都像 CommonJS 一样运行。
不幸的是,如果该假设错误,则可能导致不安全的导入:
🌐 Before Node.js implemented support for ECMAScript modules in v12, there was never a good way for TypeScript to know whether .d.ts files it found in node_modules represented JavaScript files authored as CommonJS or ECMAScript modules.
When the vast majority of npm was CommonJS-only, this didn’t cause many problems - if in doubt, TypeScript could just assume that everything behaved like CommonJS.
Unfortunately, if that assumption was wrong it could allow unsafe imports:
ts// node_modules/dep/index.d.tsexport declare function doSomething(): void;// index.ts// Okay if "dep" is a CommonJS module, but fails if// it's an ECMAScript module - even in bundlers!import dep from "dep";dep.doSomething();
实际上,这种情况并不常见。 但自从 Node.js 开始支持 ECMAScript 模块以来,npm 上 ESM 的占比有所增加。 幸运的是,Node.js 还引入了一种机制,可以帮助 TypeScript 判断文件是 ECMAScript 模块还是 CommonJS 模块:.mjs 和 .cjs 文件扩展名以及 package.json "type" 字段。 TypeScript 4.7 增加了对这些指示符的支持,以及编写 .mts 和 .cts 文件的功能; 然而,TypeScript 仅会在 --module node16 和 --module nodenext 下读取这些指示符,因此上面不安全的导入对于使用 --module esnext 和 --moduleResolution bundler 的人来说仍然是一个问题,例如。
🌐 In practice, this didn’t come up very often.
But in the years since Node.js started supporting ECMAScript modules, the share of ESM on npm has grown.
Fortunately, Node.js also introduced a mechanism that can help TypeScript determine if a file is an ECMAScript module or a CommonJS module: the .mjs and .cjs file extensions and the package.json "type" field.
TypeScript 4.7 added support for understanding these indicators, as well as authoring .mts and .cts files;
however, TypeScript would only read those indicators under --module node16 and --module nodenext, so the unsafe import above was still a problem for anyone using --module esnext and --moduleResolution bundler, for example.
为了解决这个问题,TypeScript 5.6 收集模块格式信息,并使用它在所有 module 模式下(除了 amd、umd 和 system)解决示例中出现的歧义。无论它们出现在何处,都将遵循特定格式的文件扩展名(.mts 和 .cts),并且在 node_modules 依赖中会参考 package.json "type" 字段,无论 module 设置如何。以前,从技术上讲,可以将 CommonJS 输出生成到 .mjs 文件中,反之亦然:
🌐 To solve this, TypeScript 5.6 collects module format information and uses it to resolve ambiguities like the one in the example above in all module modes (except amd, umd, and system).
Format-specific file extensions (.mts and .cts) are respected anywhere they’re found, and the package.json "type" field is consulted inside node_modules dependencies, regardless of the module setting.
Previously, it was technically possible to produce CommonJS output into a .mjs file or vice versa:
ts// main.mtsexport default "oops";// $ tsc --module commonjs main.mts// main.mjsObject.defineProperty(exports, "__esModule", { value: true });exports.default = "oops";
现在,.mts 文件永远不会输出 CommonJS,而 .cts 文件永远不会输出 ESM。
🌐 Now, .mts files never emit CommonJS output, and .cts files never emit ESM output.
请注意,这种行为的大部分在 TypeScript 5.5 的预发布版本中已经提供(实现细节见此),但在 5.6 中,这种行为仅扩展到 node_modules 内的文件。
🌐 Note that much of this behavior was provided in pre-release versions of TypeScript 5.5 (implementation details here), but in 5.6 this behavior is only extended to files within node_modules.
更多详细信息请参见此处的更改。
🌐 More details are available on the change here.
对计算属性进行正确的 override 检查
🌐 Correct override Checks on Computed Properties
以前,标记为 override 的计算属性无法正确检查基类成员的存在情况。类似地,如果你使用 noImplicitOverride,当你忘记在计算属性中添加 override 修饰符时,不会出现错误。
🌐 Previously, computed properties marked with override did not correctly check for the existence of a base class member.
Similarly, if you used noImplicitOverride, you would not get an error if you forgot to add an override modifier to a computed property.
TypeScript 5.6 现在可以在两种情况下正确检查计算属性。
🌐 TypeScript 5.6 now correctly checks computed properties in both cases.
tsconst foo = Symbol("foo");const bar = Symbol("bar");class Base {[bar]() {}}class Derived extends Base {override [foo]() {}// ~~~~~// error: This member cannot have an 'override' modifier because it is not declared in the base class 'Base'.[bar]() {}// ~~~~~// error under noImplicitOverride: This member must have an 'override' modifier because it overrides a member in the base class 'Base'.}
这个修复由 Oleksandr Tarasiuk 贡献,见 这个拉取请求。
🌐 This fix was contributed thanks to Oleksandr Tarasiuk in this pull request.