TypeScript 6.0
减少对无 this 函数的上下文敏感性
当参数没有显式类型注解时,TypeScript 通常可以根据预期类型甚至同一函数调用中的其他参数来推断它们。
declare function callIt<T>(obj: {
produce: (x: number) => T,
consume: (y: T) => void,
}): void;
// 正常,没问题。
callIt({
produce: (x: number) => x * 2,
consume: y => y.toFixed(),
});
// 正常,没问题,即使属性的顺序颠倒了。
callIt({
consume: y => y.toFixed(),
produce: (x: number) => x * 2,
});在这里,TypeScript 可以根据从 produce 函数推断出的 T 来推断 consume 函数中 y 的类型,无论属性的顺序如何。 但是,如果这些函数是使用方法语法而不是箭头函数语法编写的呢?
declare function callIt<T>(obj: {
produce: (x: number) => T,
consume: (y: T) => void,
}): void;
// 正常,`x` 被推断为 number 类型。
callIt({
produce(x: number) { return x * 2; },
consume(y) { return y.toFixed(); },
});
callIt({
consume(y) { return y.toFixed(); },
// ~
// 错误:'y' 的类型为 'unknown'。
produce(x: number) { return x * 2; },
});奇怪的是,对 callIt 的第二次调用导致了错误,因为 TypeScript 无法推断 consume 方法中 y 的类型。 这里发生的情况是,当 TypeScript 试图寻找 T 的候选类型时,它会首先跳过那些参数没有显式类型的函数。 它这样做是因为某些函数可能需要推断出的 T 类型才能被正确检查——在我们的例子中,我们需要知道 T 的类型来分析我们的 consume 函数。
这些函数被称为上下文敏感函数——基本上,就是那些参数没有显式类型的函数。 最终类型系统需要为这些参数找出类型——但这与泛型函数中类型推断的工作方式有些矛盾,因为两者对类型的“拉扯”方向是相反的。
function callFunc<T>(callback: (x: T) => void, value: T) {
return callback(value);
}
callFunc(x => x.toFixed(), 42);
// ^
// 我们需要弄清楚 `x` 的类型,
// 但我们也需要弄清楚 `T` 的类型来检查回调。为了解决这个问题,TypeScript 在类型参数推断期间会跳过上下文敏感函数,而是首先从其他参数进行检查和推断。 如果跳过上下文敏感函数不起作用,推断过程会继续检查任何未检查的参数,在参数列表中从左到右进行。 在上面的例子中,TypeScript 在推断 T 时会跳过回调,然后查看第二个参数 42,推断出 T 是 number。 然后,当它回来检查回调时,它将有一个 (x: number) => void 的上下文类型,这允许它也将 x 推断为 number 类型。
那么,我们之前的例子中发生了什么?
// 箭头语法 - 没有错误。
callIt({
consume: y => y.toFixed(),
produce: (x: number) => x * 2,
});
// 方法语法 - 报错!
callIt({
consume(y) { return y.toFixed(); },
// ~
// 错误:'y' 的类型为 'unknown'。
produce(x: number) { return x * 2; },
});在两个例子中,produce 都被赋予了一个带有显式类型 x 参数的函数。 难道它们不应该被完全一致地检查吗?
问题很微妙:大多数函数(比如使用方法语法的函数)都有一个隐式的 this 参数,而箭头函数没有。 任何对 this 的使用都可能需要“拉动” T 的类型——例如,知道包含对象字面量的类型反过来可能需要用到使用了 T 的 consume 的类型。
但是我们并没有使用 this! 当然,该函数在运行时可能有一个 this 值,但它从未被使用过!
TypeScript 6.0 在判断一个函数是否为上下文敏感函数时考虑了这一点。 如果 this 在函数中从未被实际使用,那么它就不被视为上下文敏感函数。 这意味着这些函数在类型推断时将被视为具有更高的优先级,并且我们上面的所有例子现在都能正常工作了!
此项改进 归功于 Mateusz Burzyński 的工作。
以 #/ 开头的子路径导入
当 Node.js 添加对模块的支持时,它引入了一个称为 "子路径导入" 的特性。 这本质上是 一个名为 imports 的字段,它允许包在其内部为模块创建别名。
{
"name": "my-package",
"type": "module",
"imports": {
"#root/*": "./dist/*"
}
}这允许 my-package 中的模块从以 #root/ 开头的路径导入
import * as utils from "#root/utils.js";而不是使用像下面这样的相对路径。
import * as utils from "../../utils.js";这个特性的一个小烦恼是,开发人员在指定子路径导入时,总是必须在 # 后面写上一些东西。 在这里,我们使用了 root,但由于我们只映射到 ./dist/,这有点多余。
习惯了使用打包工具的开发者也习惯于使用路径映射来避免过长的相对路径。 一个常见的打包工具惯例是使用简单的 @/ 作为前缀。 不幸的是,子路径导入完全不能以 #/ 开头,这导致许多试图在项目中采用它们的开发人员感到困惑。
但最近,Node.js 增加了对以 #/ 开头的子路径导入的支持。 这允许包在子路径导入中使用简单的 #/ 前缀,而无需添加额外的路径段。
{
"name": "my-package",
"type": "module",
"imports": {
"#/*": "./dist/*"
}
}较新的 Node.js 20 版本支持此功能,因此 TypeScript 现在在 --moduleResolution 设置为 nodenext 和 bundler 时支持它。
这项工作由 magic-akari 完成,实现拉取请求可以在这里找到。
将 --moduleResolution bundler 与 --module commonjs 结合使用
以前,TypeScript 的 --moduleResolution bundler 设置只能与 --module esnext 或 --module preserve 一起使用; 然而,随着 --moduleResolution node(即 --moduleResolution node10)的弃用,这种新的组合通常是许多项目最合适的升级路径。
项目通常希望根据项目类型(例如,打包的 Web 应用、Bun 应用或 Node.js 应用)规划迁移到以下两种方式之一:
--module preserve和--moduleResolution bundler--module nodenext
更多信息可以在这个实现拉取请求中找到。
--stableTypeOrdering 标志
作为我们正在进行TypeScript 原生移植工作的一部分,我们引入了一个名为 --stableTypeOrdering 的新标志,旨在帮助从 6.0 迁移到 7.0。
目前,TypeScript 会按照类型被遇到的顺序为它们分配类型 ID(内部跟踪编号),并使用这些 ID 以一致的方式对联合类型进行排序。 属性的处理过程也类似。 因此,程序中声明的顺序可能会对声明文件生成等产生意想不到的影响。
例如,考虑此文件中的声明生成:
// 输入:some-file.ts
export function foo(condition: boolean) {
return condition ? 100 : 500;
}
// 输出:some-file.d.ts
export declare function foo(condition: boolean): 100 | 500;
// ^^^^^^^^^
// 注意此联合类型的顺序:先是 100,然后是 500。如果我们在 foo 上面添加一个不相关的 const,声明生成会发生变化:
// 输入:some-file.ts
const x = 500;
export function foo(condition: boolean) {
return condition ? 100 : 500;
}
// 输出:some-file.d.ts
export declare function foo(condition: boolean): 500 | 100;
// ^^^^^^^^^
// 注意这里的顺序发生了变化。发生这种情况是因为,在分析 const x 声明时,字面量类型 500 比 100 更早被处理,因此获得了更低的类型 ID。 在极少数情况下,这种顺序的改变甚至可能导致错误根据程序处理顺序而出现或消失,但通常,您最可能注意到这种顺序变化的地方是在生成的声明文件中,或者在编辑器中类型的显示方式上。
TypeScript 7 的一个主要架构改进是并行类型检查,这大大提高了整体检查时间。 然而,并行性引入了一个挑战:当不同的类型检查器以不同的顺序访问节点、类型和符号时,分配给这些构造的内部 ID 将变得不确定。 这反过来又会导致令人困惑的非确定性输出,即同一程序中两个内容相同的文件可能产生不同的声明文件,甚至在分析同一文件时计算出不同的错误。 为了解决这个问题,TypeScript 7.0 会根据基于对象内容的确定性算法对其内部对象(例如类型和符号)进行排序。 这确保了无论检查器如何以及何时创建它们,所有检查器都遇到相同的对象顺序。 因此,在给定的例子中,TypeScript 7 将始终打印 100 | 500,完全消除了排序不稳定的问题。
这意味着 TypeScript 6 和 7 有时会显示不同的顺序。 虽然这些顺序更改几乎总是无害的,但如果您在运行之间比较编译器输出(例如,检查 6.0 与 7.0 中发出的声明文件),这些不同的顺序可能会产生大量干扰,使评估正确性变得困难。 但有时,您可能会遇到顺序更改导致类型错误出现或消失的情况,这可能更令人困惑。
为了帮助解决这种情况,在 6.0 中,您可以指定新的 --stableTypeOrdering 标志。 这使 6.0 的类型排序行为与 7.0 相匹配,减少了两个代码库之间的差异数量。 请注意,我们并不鼓励一直使用此标志,因为它可能会显着降低类型检查速度(根据代码库不同,最多可降低 25%)。
如果您在使用 --stableTypeOrdering 时遇到类型错误,这通常是由于推断差异造成的。 之前没有 --stableTypeOrdering 时的推断恰好基于程序中类型的当前顺序而有效。 为了解决这个问题,通常在某处提供一个显式类型会有所帮助。 通常,这将是一个类型参数
- someFunctionCall(/*...*/);
+ someFunctionCall<SomeExplicitType>(/*...*/);或者为您打算传递给调用的参数添加一个变量注解。
- const someVariable = { /*... some complex object ...*/ };
+ const someVariable: SomeExplicitType = { /*... some complex object ...*/ };
someFunctionCall(someVariable);请注意,此标志仅用于帮助诊断 6.0 和 7.0 之间的差异——它不应作为长期功能使用
target 和 lib 的 es2025 选项
TypeScript 6.0 为 target 和 lib 添加了对 es2025 选项的支持。 虽然 ES2025 中没有新的 JavaScript 语言特性,但这个新目标为内置 API(例如 RegExp.escape)添加了新的类型,并将一些声明从 esnext 移到了 es2025(例如 Promise.try、Iterator 方法和 Set 方法)。 启用新目标的工作由 Kenta Moriuchi 贡献。
Temporal 的新类型
期待已久的 Temporal 提案 已达到阶段 4,将成为未来 ECMAScript 标准的一部分。 TypeScript 6.0 现在包含了 Temporal API 的内置类型,因此您可以通过 --target esnext 或 "lib": ["esnext"](或更细粒度的 esnext.temporal)在 TypeScript 代码中立即开始使用它。
let yesterday = Temporal.Now.instant().subtract({
hours: 24,
});
let tomorrow = Temporal.Now.instant().add({
hours: 24,
});
console.log(`昨天:${yesterday}`);
console.log(`明天:${tomorrow}`);Temporal 已经在多个运行时中可用,并且随着阶段 4 的状态,它现在是 JavaScript 语言的正式部分。 MDN 上提供了有关 Temporal API 的文档。
这项工作 由 GitHub 用户 Renegade334 贡献。
"upsert" 方法的新类型(即 getOrInsert)
使用 Map 时一个常见的模式是检查某个键是否存在,如果不存在,则设置并获取一个默认值。
function processOptions(compilerOptions: Map<string, unknown>) {
let strictValue: unknown;
if (compilerOptions.has("strict")) {
strictValue = compilerOptions.get("strict");
}
else {
strictValue = true;
compilerOptions.set("strict", strictValue);
}
// ...
}这种模式可能很繁琐。 ECMAScript 的 "upsert" 提案 最近达到了阶段 4,并为 Map 和 WeakMap 引入了两个新方法:
getOrInsertgetOrInsertComputed
这些方法已添加到 esnext 库中,以便您可以在 TypeScript 6.0 中立即开始使用它们。
使用 getOrInsert,我们可以将上面的代码替换为以下内容:
function processOptions(compilerOptions: Map<string, unknown>) {
let strictValue = compilerOptions.getOrInsert("strict", true);
// ...
}getOrInsertComputed 的工作方式类似,但适用于默认值计算成本高昂的情况(例如,需要大量计算、分配或执行长时间运行的同步 I/O)。 相反,它接受一个回调函数,该回调函数仅在键不存在时才会被调用。
someMap.getOrInsertComputed("someKey", () => {
return computeSomeExpensiveValue(/*...*/);
});此回调还会将键作为参数传入,这在默认值基于键的情况下很有用。
someMap.getOrInsertComputed(someKey, computeSomeExpensiveDefaultValue);
function computeSomeExpensiveValue(key: string) {
// ...
}此更新 由 GitHub 用户 Renegade334 贡献。
RegExp.escape
在构造要在正则表达式中匹配的某些字符串字面量时,转义特殊的正则表达式字符(如 *、+、?、(、) 等)非常重要。 RegExp Escaping ECMAScript 提案 已达到阶段 4,并引入了一个新的 RegExp.escape 函数,可以为您处理此问题。
function matchWholeWord(word: string, text: string) {
const escapedWord = RegExp.escape(word);
const regex = new RegExp(`\\b${escapedWord}\\b`, "g");
return text.match(regex);
}RegExp.escape 在 es2025 库中可用,因此您可以在 TypeScript 6.0 中立即开始使用它。
这项工作 由 Kenta Moriuchi 贡献。
dom 库现在包含 dom.iterable 和 dom.asynciterable
TypeScript 的 lib 选项允许您指定目标运行时拥有的全局声明。 其中一个选项是 dom,用于表示 Web 环境(即实现 DOM API 的浏览器)。 以前,DOM API 被部分拆分到 dom.iterable 和 dom.asynciterable 中,用于不支持 Iterable 和 AsyncIterable 的环境。 这意味着您必须显式添加 dom.iterable 才能在 NodeList 或 HTMLCollection 等 DOM 集合上使用迭代方法。
在 TypeScript 6.0 中,lib.dom.iterable.d.ts 和 lib.dom.asynciterable.d.ts 的内容已完全包含在 lib.dom.d.ts 中。 您仍然可以在配置文件的 "lib" 数组中引用 dom.iterable 和 dom.asynciterable,但它们现在只是空文件。
// 在 TypeScript 6.0 之前,这需要 "lib": ["dom", "dom.iterable"]
// 现在只需 "lib": ["dom"] 即可工作
for (const element of document.querySelectorAll("div")) {
console.log(element.textContent);
}这是一个生活质量改进,消除了一个常见的困惑点,因为现在没有主流现代浏览器缺乏这些功能。 如果您之前同时包含了 dom 和 dom.iterable,现在可以简化为仅使用 dom。
TypeScript 6.0 中的重大更改和弃用
TypeScript 6.0 作为一个重要的过渡版本发布,旨在为开发人员迎接 TypeScript 7.0(即将推出的 TypeScript 编译器的原生移植)做准备。 虽然 TypeScript 6.0 保持与您现有的 TypeScript 知识完全兼容,并且与 TypeScript 5.9 的 API 保持兼容,但此版本引入了一些重大更改和弃用,反映了不断发展的 JavaScript 生态系统,并为 TypeScript 7.0 奠定了基础。
在 TypeScript 5.0 发布后的两年里,我们看到了开发人员编写和交付 JavaScript 方式的持续转变:
- 实际上,每个运行时环境现在都是"常青"的。真正的遗留环境(ES5)已极为罕见。
- 打包工具和 ESM 已成为新项目最常见的模块目标,尽管 CommonJS 仍然是一个主要目标。AMD 和其他浏览器内用户模块系统比 2012 年时罕见得多。
- 几乎所有的包都可以通过某种模块系统使用。UMD 包仍然存在,但几乎没有新代码仅作为全局变量提供。
tsconfig.json几乎已成为通用的配置机制。- 对"更严格"类型的需求持续增长。
- TypeScript 的构建性能是首要考虑因素。尽管 TypeScript 7 带来了性能提升,但性能必须始终是一个关键目标,无法以高性能方式支持的选项需要有更强的理由。
因此,TypeScript 6.0 和 7.0 的设计考虑到了这些现实情况。对于 TypeScript 6.0,您可以在 tsconfig 中设置 "ignoreDeprecations": "6.0" 来忽略这些弃用;但是,请注意,TypeScript 7.0 将不支持任何这些已弃用的选项。
一些必要的调整可以通过代码修改工具或工具自动执行。 例如,实验性的 ts5to6 工具 可以自动调整整个代码库中的 baseUrl 和 rootDir。
预先调整
我们将在下面介绍具体的调整,但我们必须注意,某些弃用和行为更改不一定有直接指向根本问题的错误消息。 因此,我们预先指出,许多项目将需要至少执行以下操作之一:
在 tsconfig 中设置
"types"数组,通常设置为"types": ["node"]。"types": ["*"]将恢复 5.9 的行为,但我们建议使用显式数组以提高构建性能和可预测性。如果您看到大量与缺失标识符或未解析的内置模块相关的类型错误,通常会知道这是问题所在。
如果您以前依赖推断结果,请设置
"rootDir": "./src"如果您看到文件被写入
./dist/src/index.js而不是./dist/index.js,通常会知道这是问题所在。
简单的默认值更改
几个编译器选项现在具有更新的默认值,以更好地反映现代开发实践。
strict现在默认为true: 对更严格类型的需求持续增长,我们发现大多数新项目都希望启用strict模式。 如果您已经在使用"strict": true,那么对您来说没有变化。 如果您依赖之前默认的false,则需要在tsconfig.json中显式设置"strict": false。module默认为esnext: 同样,新的默认module是esnext,承认 ESM 现在是主导模块格式。target默认为当年的 ES 版本: 新的默认target是最新支持的 ECMAScript 规范版本(本质上是一个浮动目标)。 目前,该目标是es2025。 这反映了大多数开发人员交付给常青运行时,不需要编译到较旧的 ECMAScript 版本。noUncheckedSideEffectImports现在默认为true: 这有助于捕获仅副作用导入中的拼写错误。libReplacement现在默认为false: 此标志以前每次运行时都会导致大量失败的模块解析,这反过来又增加了我们在--watch和编辑器场景下需要监视的位置数量。 在一个新项目中,libReplacement在未进行其他显式配置之前不会做任何事情,因此为了默认获得更好的性能,关闭此功能是有意义的。
如果这些新默认值破坏了您的项目,您可以在 tsconfig.json 中显式指定以前的值。
rootDir 现在默认为 .
rootDir 控制输出文件相对于输出目录的目录结构。 以前,如果您未指定 rootDir,它会根据所有非声明输入文件的公共目录进行推断。 但这通常意味着如果不尝试加载和解析该项目,就无法知道某个文件是否属于该项目。 这也意味着 TypeScript 必须通过分析程序中的每个文件路径来花费更多时间推断该公共源目录。
在 TypeScript 6.0 中,默认的 rootDir 将始终是包含 tsconfig.json 文件的目录。 只有在没有 tsconfig.json 文件的情况下从命令行使用 tsc 时,才会推断 rootDir。
如果您的源文件层级比 tsconfig.json 目录更深,并且您依赖 TypeScript 推断源文件的公共根目录,则需要显式设置 rootDir:
{
"compilerOptions": {
// ...
+ "rootDir": "./src"
},
"include": ["./src"]
}同样,如果您的 tsconfig.json 引用了包含它的 tsconfig.json 之外的文件,您需要调整 rootDir 以包含这些文件。
{
"compilerOptions": {
// ...
+ "rootDir": "../src"
},
"include": ["../src/**/*.tests.ts"]
}types 现在默认为 []
在 tsconfig.json 中,compilerOptions 的 types 字段指定了在编译期间要包含在全局作用域中的包名列表。 通常,node_modules 中的包会通过源代码中的导入自动包含; 但为了方便,TypeScript 默认也会包含 node_modules/@types 中的所有包,这样您就可以直接使用 @types/node 中的 process 或 "fs" 模块,或者 @types/jest 中的 describe 和 it,而无需直接导入它们。
从某种意义上说,types 值之前默认是"枚举 node_modules/@types 中的所有内容"。 这可能非常昂贵,因为现在一个普通的仓库设置可能会传递性地引入数百个 @types 包,尤其是在具有扁平 node_modules 的多项目工作区中。 现代项目几乎总是只需要 @types/node、@types/jest 或其他几个常见的全局影响包。
在 TypeScript 6.0 中,默认的 types 值将是 [](一个空数组)。 此更改可防止项目在构建时无意中引入数百甚至数千个不需要的声明文件。 我们查看的许多项目仅通过适当设置 types 就将构建时间提高了 20-50%。
这将影响许多项目。 您可能需要添加 "types": ["node"] 或其他几个:
{
"compilerOptions": {
// 显式列出您需要的 @types 包
+ "types": ["node", "jest"]
}
}您还可以指定 * 条目以重新启用旧的枚举行为:
{
"compilerOptions": {
// 加载所有类型 - TypeScript 5.9 及之前的默认行为。
+ "types": ["*"]
}
}如果您最终遇到如下新的错误消息:
Cannot find module '...' or its corresponding type declarations.
Cannot find name 'fs'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
Cannot find name 'path'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
Cannot find name 'Bun'. Do you need to install type definitions for Bun? Try `npm i --save-dev @types/bun` and then add 'bun' to the types field in your tsconfig.
Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha` and then add 'jest' or 'mocha' to the types field in your tsconfig.这很可能意味着您需要在 types 字段中添加一些条目。
已弃用:target: es5
ECMAScript 5 目标对于支持旧版浏览器很长一段时间都很重要;但它的继任者 ECMAScript 2015 (ES6) 已在十多年前发布,所有现代浏览器都已支持它多年。 随着 Internet Explorer 的退役以及常青浏览器的普及,如今 ES5 输出的用例非常少。
TypeScript 的最低目标现在将是 ES2015,并且 target: es5 选项已被弃用。如果您正在使用 target: es5,则需要迁移到较新的目标或使用外部编译器。 如果您仍然需要 ES5 输出,我们建议使用外部编译器直接编译您的 TypeScript 源代码,或对 TypeScript 的输出进行后处理。
已弃用:--downlevelIteration
--downlevelIteration 仅对 ES5 输出有影响,并且由于 --target es5 已被弃用,--downlevelIteration 不再有任何用途。 微妙的是,在 TypeScript 5.9 及更早版本中,将 --downlevelIteration false 与 --target es2015 一起使用并不会报错,即使它没有效果。 在 TypeScript 6.0 中,设置 --downlevelIteration 将导致弃用错误。
请参阅此处的实现。
已弃用:--moduleResolution node(即 --moduleResolution node10)
--moduleResolution node 编码了 Node.js 模块解析算法的特定版本,最准确地反映了 Node.js 10 的行为。 不幸的是,这个目标(及其名称)忽略了此后 Node.js 解析算法的许多更新,并且不再是现代 Node.js 版本行为的良好代表。
在 TypeScript 6.0 中,--moduleResolution node(具体来说,--moduleResolution node10)已被弃用。 如果计划直接针对 Node.js,使用 --moduleResolution node 的用户通常应迁移到 --moduleResolution nodenext;如果计划使用打包工具或 Bun,则应迁移到 --moduleResolution bundler。
已弃用:module 的 amd、umd 和 systemjs 值
以下标志值不再受支持
--module amd--module umd--module systemjs--module none
AMD、UMD 和 SystemJS 在 JavaScript 模块早期很重要,当时浏览器缺乏原生模块支持。 "none" 的语义从未明确定义,并且常常导致混淆。 如今,ESM 在浏览器和 Node.js 中得到普遍支持,并且导入映射和打包工具已成为填补空白的首选方式。 如果您仍然针对这些模块系统,请考虑迁移到适当的 ECMAScript 模块输出目标,采用打包工具或不同的编译器,或继续使用 TypeScript 5.x 直到您可以迁移。
这也意味着放弃对 amd-module 指令的支持,该指令将不再有任何效果。
已弃用:--baseUrl
baseUrl 选项最常与 paths 结合使用,通常用作 paths 中每个值的前缀。 不幸的是,baseUrl 也被视为模块解析的查找根。
例如,给定以下 tsconfig.json
{
"compilerOptions": {
// ...
"baseUrl": "./src",
"paths": {
"@app/*": ["app/*"],
"@lib/*": ["lib/*"]
}
}
}以及像这样的导入
import * as someModule from "someModule.js";TypeScript 可能会将其解析为 src/someModule.js,即使开发人员只想为以 @app/ 和 @lib/ 开头的模块添加映射。
在最好的情况下,这也常常导致打包工具会忽略的"看起来更糟"的路径; 但这通常意味着许多在运行时永远无法工作的导入路径被 TypeScript 认为是"正常的"。
path 映射很长时间以来都不需要指定 baseUrl,实际上,大多数使用 baseUrl 的项目仅将其用作 paths 条目的前缀。 在 TypeScript 6.0 中,baseUrl 已被弃用,并且将不再被视为模块解析的查找根。
将 baseUrl 用作路径映射条目前缀的开发人员可以简单地删除 baseUrl 并将前缀添加到其 paths 条目中:
{
"compilerOptions": {
// ...
- "baseUrl": "./src",
"paths": {
- "@app/*": ["app/*"],
- "@lib/*": ["lib/*"]
+ "@app/*": ["./src/app/*"],
+ "@lib/*": ["./src/lib/*"]
}
}
}确实将 baseUrl 用作查找根的开发人员也可以添加显式路径映射以保留旧行为:
{
"compilerOptions": {
// ...
"paths": {
// 一个新的全能匹配项,替代 baseUrl:
"*": ["./src/*"],
// 现在每个其他路径都有一个显式的公共前缀:
"@app/*": ["./src/app/*"],
"@lib/*": ["./src/lib/*"],
}
}
}然而,这种情况极为罕见。我们建议大多数开发人员直接删除 baseUrl 并为其 paths 条目添加适当的前缀。
已弃用:--moduleResolution classic
moduleResolution: classic 设置已被移除。 classic 解析策略是 TypeScript 最初的模块解析算法,早于 Node.js 解析算法成为事实标准之前。 如今,所有实际用例都可以由 nodenext 或 bundler 提供服务。 如果您正在使用 classic,请迁移到这些现代解析策略之一。
已弃用:--esModuleInterop false 和 --allowSyntheticDefaultImports false
以下设置不能再设置为 false:
esModuleInteropallowSyntheticDefaultImports
esModuleInterop 和 allowSyntheticDefaultImports 最初是可选的,以避免破坏现有项目。 然而,它们启用的行为多年来一直是推荐的默认值。 将它们设置为 false 常常导致从 ESM 使用 CommonJS 模块时出现微妙的运行时问题。 在 TypeScript 6.0 中,始终启用更安全的互操作行为。
如果您有依赖于旧行为的导入,您可能需要调整它们:
// 之前(使用 esModuleInterop: false)
import * as express from "express";
// 之后(esModuleInterop 始终启用)
import express from "express";已弃用:--alwaysStrict false
alwaysStrict 标志指的是 "use strict"; 指令的推断和输出。 在 TypeScript 6.0 中,所有代码都将假定处于 JavaScript 严格模式,这是一组 JavaScript 语义,最显着地影响有关保留字的语法边缘情况。 如果您有使用保留字(如 await、static、private 或 public)作为常规标识符的"非严格模式"代码,则需要重命名它们。 如果您依赖非严格代码中 this 含义的细微语义,则可能需要调整代码。
已弃用:outFile
--outFile 选项已从 TypeScript 6.0 中移除。此选项最初旨在将多个输入文件连接成单个输出文件。然而,像 Webpack、Rollup、esbuild、Vite、Parcel 等外部打包工具现在可以更快、更好地完成这项工作,并且配置性更强。移除此选项简化了实现,使我们能够专注于 TypeScript 最擅长的领域:类型检查和声明生成。如果您当前正在使用 --outFile,则需要迁移到外部打包工具。大多数现代打包工具开箱即用地支持 TypeScript。
已弃用:用于命名空间的遗留 module 语法
TypeScript 的早期版本使用 module 关键字声明命名空间:
// ❌ 已弃用的语法 - 现在会报错
module Foo {
export const bar = 10;
}此语法后来被别名为现代首选形式,使用 namespace 关键字:
// ✅ 正确的语法
namespace Foo {
export const bar = 10;
}当引入 namespace 时,module 语法仅被不鼓励使用。 几年前,TypeScript 语言服务开始将该关键字标记为已弃用,建议使用 namespace 代替。
在 TypeScript 6.0 中,在预期使用 namespace 的地方使用 module 现在是硬性弃用。 此更改是必要的,因为 module 块是一个潜在的 ECMAScript 提案,会与遗留的 TypeScript 语法冲突。
环境模块声明形式仍然完全受支持:
// ✅ 仍然完美运行
declare module "some-module" {
export function doSomething(): void;
}已弃用:导入中的 asserts 关键字
asserts 关键字通过导入断言提案被提议引入 JavaScript 语言; 然而,该提案最终演变为导入属性提案,它使用 with 关键字而不是 asserts。
因此,asserts 语法在 TypeScript 6.0 中已被弃用,使用它将导致错误:
// ❌ 已弃用的语法 - 现在会报错。
import blob from "./blahb.json" asserts { type: "json" }
// ~~~~~~~
// 错误:导入断言已被导入属性取代。请使用 'with' 代替 'asserts'。相反,请对导入属性使用 with 语法:
// ✅ 适用于新的导入属性语法。
import blob from "./blahb.json" with { type: "json" }已弃用:no-default-lib 指令
/// <reference no-default-lib="true"/> 指令在很大程度上被误解和误用。 在 TypeScript 6.0 中,不再支持此指令。 如果您正在使用它,请考虑使用 --noLib 或 --libReplacement 代替。
当存在 tsconfig.json 时指定命令行文件现在会报错
目前,如果您在存在 tsconfig.json 的文件夹中运行 tsc foo.ts,配置文件将被完全忽略。 如果您期望检查与输出选项应用于输入文件,这通常非常令人困惑。
在 TypeScript 6.0 中,如果您在包含 tsconfig.json 的目录中使用文件参数运行 tsc,将发出错误以使此行为明确:
error TS5112: tsconfig.json is present but will not be loaded if files are specified on commandline. Use '--ignoreConfig' to skip this error.如果您确实希望忽略 tsconfig.json 并使用 TypeScript 的默认值编译 foo.ts,您可以使用新的 --ignoreConfig 标志。
tsc --ignoreConfig foo.ts为 TypeScript 7.0 做准备
TypeScript 6.0 被设计为一个过渡版本。 虽然在设置了 "ignoreDeprecations": "6.0" 的情况下,TypeScript 6.0 中弃用的选项将继续工作且不会报错,但这些选项将在 TypeScript 7.0(原生 TypeScript 移植版)中被完全移除。 如果您在升级到 TypeScript 6.0 后看到弃用警告,我们强烈建议在采用 TypeScript 7.0(或尝试原生预览版)之前解决这些问题。
