TypeScript 5.0 已发布!看看增加了什么新功能2023-03-21 02:03:03

自 Beta 版本以来,有几个显著的更改
其中一个新变化是 TypeScript 允许在 export 和 export default 之前或之后放置装饰器。这一变化反映了 TC39(ECMAScript/JavaScript 的标准组织)内的讨论和共识。
另一个变化是,新的模块解析选项(moduleResolution)“bundler” 现在只能在将 –module 选项设置为 esnext 时使用。这是为了确保在 bundler 解析 import 语句之前,不管 bundler 或加载器(loader)是否使用 TypeScript 的模块选项,输入文件中编写的 import 语句都不会转换为 require 调用。在这些发行说明中,我们也提供了一些上下文信息,建议大多数库作者使用 node16 或 nodenext。
尽管 TypeScript 5.0 Beta 版本中已经具备了此功能,但我们没有为支持编辑器场景中不区分大小写的导入排序编写文档。这在一定程度上是因为自定义 UX 仍在讨论中,但是默认情况下,TypeScript 现在应该与您的其他工具更好地配合使用。具体介绍在后面。
自我们发布 RC 版本以来,最显着的变化是 TypeScript 5.0 现在在 package.json 中指定了 Node.js 的最低版本为 12.20。我们还发布了一篇有关 TypeScript 5.0 迁移到模块的文章,并链接到了它。
自 TypeScript 5.0 Beta 和 RC 发布以来,速度基准测试和包大小差异的具体数字也已经进行了调整,尽管噪声是运行的一个因素。一些基准测试的名称也已经进行了调整以提高清晰度,并且包大小的改进已经移动到一个单独的图表中。
BreakChanges And Deprecations
运行时要求
要求 Node.js 10.x 以上。
lib.d.ts变更
例行环节。具体变更在这:lib.d.ts change
后面实际项目中如果有遇到一些常用的用法报错了,需要手动更改代码的 case,笔者会在这里补充,目前还没发现。
关系运算符中禁止隐式类型转换
5.0 之前 TypeScript 只会检查 + – * /运算符的隐式类型转换,提示类型报错。
function func(ns: number | string) {
return ns * 4; // Error, possible implicit coercion
}
复制代码
5.0 之后关系运算符 > < <= >=也检查。
function func(ns: number | string) {
return ns > 4; // Now also an error
}
复制代码
经典操作,可以通过 + 运算符来进行进行转换。
function func(ns: number | string) {
return +ns > 4; // OK
}
复制代码
enum 类型检修
自从 TypeScript 支持 enum 枚举类型以来,一直都有些长期存在的奇怪的问题。官方说在 5.0,他们正在处理解决这些问题。
给 enum 类型变量赋值字面量时,如果超出了 enum 定义范围,会报错
enum SomeEvenDigit {
Zero = 0,
Two = 2,
Four = 4
}

// Now correctly an error
let m: SomeEvenDigit = 1;
复制代码
注意是字面量。下面这种情况还是不会报错:
enum SomeEvenDigit {
Zero = 0,
Two = 2,
Four = 4
}
const test = {
number: 2,
};

let m: SomeEvenDigit = test.number;
复制代码
修复之前的一个问题:由多个 enum 类型组成的混合 enum 类型,enum 的成员都会是 number 类型
官方的例子:
enum Letters {
A = “a”
}
enum Numbers {
one = 1,
two = Letters.A
}

// 5.0 之前,这个语句是不会报错的,因为enum的成员都被错误地认为是number类型
// 5.0 之后,这个语句会报类型错误,
// Type ‘Numbers’ is not assignable to type ‘number’.ts(2322)
const t: number = Numbers.two;
复制代码
修饰器
ECMASCript 的修饰器标准已经进到了 Stage3 阶段了,TypeScript 5.0 落地了 ECMASCript 的修饰器标准。
修饰器简单来说是一个函数,可以用于修饰类、类的成员方法/属性。
修饰器使用说明
官方以一个类方法修饰器来举了个例子:
function loggedMethod(originalMethod: any, context: ClassMethodDecoratorContext) {
const methodName = String(context.name);

function replacementMethod(this: any, ...args: any[]) {
    console.log(`LOG: Entering method '${methodName}'.`)
    const result = originalMethod.call(this, ...args);
    console.log(`LOG: Exiting method '${methodName}'.`)
    return result;
}

return replacementMethod;

}

class Person {
name: string;
constructor(name: string) {
this.name = name;
}

@loggedMethod
greet() {
    console.log(`Hello, my name is ${this.name}.`);
}

}

const p = new Person(“Ray”);
p.greet();
复制代码
代码中,loggedMethod就是一个类方法修饰器。它是一个函数,函数的第一个入参是被修饰的greeet方法的初始值,第二个入参context是上下文信息。修饰器最后返回的是replacementMethod函数,当被修饰的方法greet被调用时,执行的就是replacementMethod函数。我们可以把一些可复用的代码逻辑放到loggedMethod修饰器中,那就可以基于修饰器去复用代码了。
修饰器上下文
非常值得关注的是,第二个入参context提供了一个上下文信息对象。根据修饰器的种类不同有不同的类型:
类修饰器:ClassDecoratorContext、类方法修饰器:ClassMethodDecoratorContext、类属性getter修饰器:ClassGetterDecoratorContext、类属性setter修饰器:ClassSetterDecoratorContext、类属性修饰器:ClassFieldDecoratorContext、类accessor修饰器:ClassAccessorDecoratorContext
context的类型定义大致是这样的:
{
// 不同的类型有不同的值。
// ClassDecoratorContext ==> class
// ClassMethodDecoratorContext ==> method
// ClassGetterDecoratorContext ==> getter
// ClassSetterDecoratorContext ==> setter
// ClassFieldDecoratorContext ==> accessor
// ClassFieldDecoratorContext ==> field
readonly kind: string;

// 被修饰的类的名称 或者 类成员的名称
readonly name: string;

// 用于添加 类的构造函数执行前 的逻辑
addInitializer(initializer: () => void): void
}
复制代码
addInitializer(新语法)
上下文中的addInitializer方法是标准中的新语法。
官方文档解释:

It’s a way to hook into the beginning of the constructor (or the initialization of the class itself if we’re working with statics)

当修饰器用来修饰非 static 属性/方法时,可以通过这个方法在实例初始化时,构造函数执行之前指定执行逻辑。
当修饰器用来修饰 static 属性/方法时,可以通过这个方法类初始化时,指定执行逻辑。
举个例子就清晰很多:
function classDecorator(target: any, context: ClassDecoratorContext) {
context.addInitializer(() => {
console.log(‘classDecorator addInitializer here’, target);
})
}

function staticFiledDecorator(target: any, context: ClassFieldDecoratorContext) {
context.addInitializer(() => {
console.log(‘staticFiledDecorator addInitializer here’, target);
})
}

function staticMethodDecorator(target: any, context: ClassMethodDecoratorContext) {
context.addInitializer(() => {
console.log(‘staticMethodDecorator addInitializer here’, target);
})
}

function instanceFiledDecorator(target: any, context: ClassFieldDecoratorContext) {
context.addInitializer(() => {
console.log(‘instanceFiledDecorator addInitializer here’, target);
})
}

function instanceMethodDecorator(target: any, context: ClassMethodDecoratorContext) {
context.addInitializer(() => {
console.log(‘instanceMethod addInitializer here’, target);
})

function replacementMethod(this: any, ...args: any[]) {
    const result = target.call(this, ...args);
    return result;
}

return replacementMethod;

}

@classDecorator
class Person {
@staticFiledDecorator
static age: number = 23;

@staticMethodDecorator
static run() {
    console.log('run');
}
constructor(name: string) {
    console.log('constructor');
    this.name = name;
}

@instanceFiledDecorator
name: string = 'Forest';
@instanceMethodDecorator
eat() {
    console.log('eat sth');
}

}

const p = new Person(“Ray”);

// 最终的输出
// staticMethodDecorator addInitializer here [Function: run]
// staticFiledDecorator addInitializer here undefined
// classDecorator addInitializer here [class Person] { age: 23 }
// instanceMethod addInitializer here [Function: eat]
// instanceFiledDecorator addInitializer here undefined
// constructor
复制代码
Initializer方法执行时机
类的静态属性/方法初始化,在类初始化过程中。
staticMethodDecorator、staticFiledDecorator中通过addInitializer方法增加的初始化函数,先执行。
classFiledDecorator中通过addInitializer方法增加的初始化函数,在类初始化之后执行。
类进行实例化
const p = new Person(“Ray”);
复制代码
instanceMethod、instanceFiledDecorator中通过addInitializer方法增加的初始化函数,在
实例初始化时,构造函数执行之前执行。
Initializer方法的应用
官网例子:
使用addInitializer()绑定this。
function bound(originalMethod: any, context: ClassMethodDecoratorContext) {
const methodName = context.name;
if (context.private) {
throw new Error('bound' cannot decorate private properties like ${methodName as string}.);
}
context.addInitializer(function () {
this[methodName] = this[methodName].bind(this);
});
}

class Person {
name: string;
constructor(name: string) {
this.name = name;
}

@bound
@loggedMethod
greet() {
    console.log(`Hello, my name is ${this.name}.`);
}

}

const p = new Person(“Ray”);
const greet = p.greet;

// Works!
greet();
复制代码
与之前版本实验性的修饰器的不同
之前版本的 TypeScript 也支持修饰器,需要增加–experimentalDecorators编译选项。
TypeScript5.0 的修饰器标准跟之前的修饰器是不兼容的。旧版的 –experimentalDecorators 选项将会仍然保留,如果启用此配置,则仍然会将装饰器视为旧版,新版的装饰器无需任何配置就能够默认启用。
TypeScript5.0 的修饰器标准跟之前的元数据反射是不兼容的。
写类型完备的修饰器
推荐写类型完备的修饰器。如果写类型完备的修饰器,不免会用到很多泛型、类型参数,这样也会影响代码的可读性。怎么写修饰器后面会有更多的文档出来。先推荐了一篇文章:《JavaScript metaprogramming with the 2022-03 decorators API》
const Type Parameters
提供const修饰符,对泛型的类型参数进行修饰。用于解决之前 需要 增加as const断言才能实现的类型推导。
5.0 之前对于泛型的类型参数的类型推导,TypeScript 往往只能推导到基础数据类型,这样做是保证变量类型的可变。
例子:
function getConstValue(arg: T): T {
return arg;
}
// 这里推导出 names 是 string[]
const names = getConstValue([“Jack”, “Bob”, “Eve”]);
// 这里推导出 result 是 { name: string, frd: string}
const result = getConstValue({ name: ‘Jack’, frd: [‘Bob’, ‘Eve’] });

// 不会报类型错误
names[0] = ‘1’;
// 不会报类型错误
result.frd[0] = ‘Sophia’;

复制代码
但是,从getConstValue函数真实的意图–得到一个不可变的常量,来说这样的类型推导是不完备的。之前我们往往这么做:
function getConstValue(arg: T): T {
return arg;
}
// 这里推导出 names 是 readonly [“Alice”, “Bob”, “Eve”]
const names = getConstValue([“Jack”, “Bob”, “Eve”] as const);
// 这里推导出 result 是 {
// readonly name: “linbudu”;
// readonly techs: readonly [“nodejs”, “typescript”, “graphql”];
// }
const result = getConstValue({ name: ‘Jack’, frd: [‘Bob’, ‘Eve’] } as const);

// 报类型错误
names[0] = ‘1’;
// 报类型错误
result.frd[0] = ‘Sophia’;
复制代码
在5.0版本中,可以不需要这样的骚操作了,把const修饰符加上作用于泛型的类型参数即可。优雅!
function getConstValue(arg: T): T {
return arg;
}
// 这里推导出 names 是 readonly [“Alice”, “Bob”, “Eve”]
const names = getConstValue([“Jack”, “Bob”, “Eve”]);
// 这里推导出 result 是 {
// readonly name: “linbudu”;
// readonly techs: readonly [“nodejs”, “typescript”, “graphql”];
// }
const result = getConstValue({ name: ‘Jack’, frd: [‘Bob’, ‘Eve’] });

// 报类型错误
names[0] = ‘1’;
// 报类型错误
result.frd[0] = ‘Sophia’;
复制代码
与泛型约束一起使用
注意当const修饰符修饰的类型变量,后面带有泛型约束extends时,如泛型约束不是常量,那么类型推导的结果遵循泛型约束,而不是常量。
官网的例子:
declare function fnBad(args: T): void;

// 此时推断出来T的类型是 string[],而不是 readonly [“a”, “b”, “c”]
fnBad([“a”, “b” ,”c”]);

// 泛型约束 后面跟着的是 readonly string[]
declare function fnGood(args: T): void;

// 此时推断出来T的类型是 [“a”, “b”, “c”]
fnGood([“a”, “b” ,”c”]);

复制代码
作用范围
const修饰符的类型推导生效范围:函数调用时,参数是对象、数组或表达式。如果函数调用时,参数是一个变量,上面讲述的类型推导不会生效。
官网例子:
declare function fnGood(args: T): void;
const arr = [“a”, “b” ,”c”];

// ‘T’ is still ‘string[]’– the ‘const’ modifier has no effect here
fnGood(arr);
复制代码
compilerOptions 的 extends 配置支持多文件
{
“compilerOptions”: {
“extends”: [“./tsconfig1.json”, “./tsconfig2.json”]
}
}
复制代码
枚举
TypeScript 5.0 之前的枚举,枚举分为数字枚举和字符串枚举。
TypeScript 5.0 将所有枚举合并为统一的一种枚举类型(Union enums),其含义就是枚举类型是其所有枚举成员类型组成的联合类型。
这样做带来什么改变呢?下面列举了一些 5.0 之前,在使用枚举时,会碰到的一些很神奇诡异的规则,然后跟 5.0 之后做一下对比,看有什么改变。
前后对比

5.0 之前:把枚举成员用作类型,所有枚举成员必须使用字面量初始化。
5.0 之后:没有这个约束了。

github 相关的#27976
// 5.0之前
// it‘s ok
enum UserResponse {
// constant member
No = 0,
// computed member
Yes = num,
// constant member
NotSure = 1 + 1
}

// throws an error : Enum type has members with initializers that are not literals
type aType = UserResponse.Yes;
// ^^^^^^^^^^^^^^^^^
// throws an error : Enum type has members with initializers that are not literals
type bType = UserResponse.NotSure;
// ^^^^^^^^^^^^^^^^^^^^^
// throws an error : Enum type has members with initializers that are not literals
type cType = UserResponse.No;
// ^^^^^^^^^^^^^^^^
// throws an error : Enum type has members with initializers that are not literals

// it‘s ok
enum UserResponse_1 {
// constant member initialized by literal
No = 0,
// constant member initialized by literal
Yes = 1,
// constant member initialized by literal
NotSure = 2,
}

// it’s ok
type aType_1 = UserResponse_1.Yes;
type bType_1 = UserResponse_1.NotSure;
复制代码
UserResponse枚举中,存在UserResponse.Yes“UserResponse.NotSure两个用非字面量初始化的成员,那么使用成员去当TypeScript中的类型使用时,就会报错:
Enum type ‘UserResponse’ has members with initializers that are not literals.(2535)
必须把枚举成员全部使用字面量来初始化,可以对比着UserResponse_1来看。

5.0 之前:字符串枚举成员只能是常量枚举成员。比如,无法使用字符串变量或者模版字符串给枚举成员赋值。
5.0 之后:字符串枚举成员可以是计算枚举成员。

// 5.0之前
const string_var = ‘jack’;

enum String_E {
// it‘s ok
a = ‘a’
}

enum String_E2 {
aa = string_var,
// ^^^^^^^^^^
// throws an error :Only numeric enums can have computed members

bb = `${String_E.a}`
//    ^^^^^^^^^^^^^
// throws an error  :Only numeric enums can have computed members

}

复制代码

5.0 之前:枚举成员的初始化,存在一些约束。
5.0 之后:约束的第二条由「数字字面量」放宽到「数字字面量和数字常量」。其他的计算枚举成员还是会报错。

(感觉规则又变复杂了。不过用起来会方便一点。)
// 5.0之前
const num = 50;
enum E {
a = num,
b,
// ^
// throws an error : Enum member must have initializer.
}

// 5.0之后
enum E {
a = num,
// 不报错,b的值为 51
b,
}

function getNumber() {
return 50;
}

enum E_1 {
a = num + 1,
// 不报错,b的值为 52
b,
c = num << 1,
// 不报错,d的值为 52
d,
e = getNumber(),
f,
//^
// throws an error : Enum member must have initializer
}

复制代码
疑问
疑问一
const num = 50;
enum E_1 {
a = num + 1,
// 不报错,b的值为 52
b,
c = num << 1,
// 不报错,d的值为 52
d,
e = getNumber(),
f,
//^
// throws an error : Enum member must have initializer
}
复制代码
那问题来了,5.0 版本之后,类似E_1.a、E_1.c这样(使用数字常量初始化或带有数字常量表达式)的枚举成员,是「计算枚举成员」呢还是「常量枚举成员」。

按照「计算枚举成员」和「常量枚举成员」的定义,它是「计算枚举成员」
按照枚举成员的初始化约束,它又是「常量枚举成员」

感觉「计算枚举成员」和「常量枚举成员」的定义 要修改,到目前官方文档还没修改。
moduleResolution配置新增bundler支持
TypeScript 4.7 为 –module 和 –moduleResolution增加了node16和nodenext,从而更好地在 Nodejs 中支持 ESM 标准。但是在这种模式下,会有很多限制。
比如,在 Nodejs 中,ESM(ECMAScript module)要求在import相对路径的依赖时,需要显示写文件的扩展名。
// entry.mjs
import * as utils from “./utils”; // wrong – we need to include the file extension.

import * as utils from “./utils.mjs”; // works
复制代码
这样做的原因是为了在文件服务器中有更好的文件搜寻速度。对比使用其他构建工具,node16/nodenext模式下的这些限制还是太麻烦了,甚至说默认的node模式更好。
但是,默认的node模式已经过时了,大多数现代构建工具混合着使用ESM(ECMAScript module)和CommonJS两种模块标准的模块解析策略。
于是,5.0 中 TypeScript 提供了新的moduleResolution配置选项bundler,它同时兼容 ESM(ECMAScript module) 和 CommonJS 两种模块标准的模块解析策略,但是又没有 ESM 在 Nodejs 中的限制。
跟 moduleResolution 相关的几个配置项
allowImportingTsExtensions
配置启用后,import其他模块时允许携带.ts, .mts和 .tsx这三种扩展名。
这个配置启用必须同时与 –noEmit –emitDeclarationOnly这两个配置一起启用。
当–moduleResolution是node16、nodenext、bundler是,配置默认启用。
resolvePackageJsonExports与resolvePackageJsonImports
配置启用后,import来自node_modules中的模块时,TypeScript 会去解析模块对应的package.json中的exports和imports字段。这块可以去看一下 Conditional exports 的知识。
当–moduleResolution是node16、nodenext、bundler是,配置默认启用。
allowArbitraryExtensions
允许任意的后缀名。当在代码里 import 的模块扩展名不是.js.jsx.ts“.tsx,编译器会按以下的规则去查找该模块的类型定义文件:{file basename}.d.{extension}.ts。
官网例子:
/* app.css */
.cookie-banner {
display: none;
}
复制代码
// app.d.css.ts
declare const css: {
cookieBanner: string;
};
export default css;
复制代码
// App.tsx
import styles from “./app.css”;

styles.cookieBanner; // string
复制代码
默认的话,TypeScript 会提示一个错误,让你知道 TypeScript 无法解析这种文件类型,代码运行时可能无法正确地导入。但是如果你在 bundler 中正确地配置,可以加上–allowArbitraryExtensions这个新的编译选项来阻止错误的提示。
在前端编写 CSS Modules 的时候,以前是这样处理 import css/less 的。
declare module ‘.less’; declare module ‘.css’;
复制代码
现在通过这个编译选项可以增加上类型支持了。但是,针对 css/less 等样式文件,我可能还是会这样处理,感觉加上类型没太大必要。
当–moduleResolution是node16、nodenext、bundler是,配置默认启用。
customConditions
Conditional exports中还支持自定义conditions。通过这个配置进行支持。
当我们需要import一个模块es-module-package,它的package.json是下面这样定义的话
{
// …
“exports”: {
“.”: {
“my-condition”: “./foo.mjs”,
“node”: “./bar.mjs”,
“import”: “./baz.mjs”,
“require”: “./biz.mjs”
}
}
}
复制代码
import feature from ‘es-module-package/my-condition’;
// 期待加载./node_modules/es-module-package/foo.mjs
复制代码
那么tsconfig.json可以这么写
{
“compilerOptions”: {
“target”: “es2022”,
“moduleResolution”: “bundler”,
“customConditions”: [“my-condition”]
}
}
复制代码
当–moduleResolution是node16、nodenext、bundler是,配置默认启用。
verbatimModuleSyntax
提供了一个新的配置项,用于简化之前 TypeScript 的引用省略功能涉及importsNotUsedAsValues、preserveValueImports、isolatedModules三个配置项的配置复杂问题。
支持 export type *
支持使用export * from “module”和export * as ns from “module”这样的语句进行类型导出。
JSDoc 支持 @satisfies
TypeScript 4.9 的时候支持了satisfies操作符。
现在在 JSDoc 上也支持@satisfies注释,因为有部分开发者是通过 JSDoc 注释来给 Javascript 提供类型检查的。
JSDoc 支持 @overload
TypeScript 中你可以定义同一个函数的不同的入参类型或者不同的返回值类型。现在在 JSDoc 上支持通过@overload注释来满足这个需求。
tsc build 模式下支持设置以下几个标志位
–declaration
–emitDeclarationOnly
–declarationMap
–soureMap
–inlineSourceMap
编辑器中的不区分大小写的导入排序
在类似于 Visual Studio 和 VS Code 这样的编辑器中,TypeScript 为组织和排序导入和导出提供支持。不过,对于何时排序的列表会有不同的解释。
例如,以下导入列表是否已排序?
import {
Toggle,
freeze,
toBoolean,
} from “./utils”;
复制代码
令人惊讶的是,答案可能是“取决于情况”。如果我们不关心大小写,那么这个列表显然没有排序。字母 “f” 在 “t” 和 “T” 之前。
但在大多数编程语言中,排序默认比较字符串的字节值。JavaScript 比较字符串的方式意味着 “Toggle” 总是排在 “freeze” 之前,因为根据 ASCII 字符编码,大写字母在小写字母之前。因此,从这个角度来看,导入列表已排序。
TypeScript 以前认为导入列表已排序,因为它进行了基本的区分大小写排序。这可能是开发人员的痛点,因为他们更喜欢不区分大小写的排序方式,或者使用像 ESLint 这样的工具默认需要不区分大小写的排序方式。
TypeScript 现在默认检测大小写敏感性。这意味着 TypeScript 和类似 ESLint 的工具通常不会因为如何最好地排序导入而“争执”。
我们的团队还在尝试更多的排序策略,您可以在此处阅读更多信息。这些选项可能最终可以由编辑器进行配置。目前,它们仍然是不稳定和实验性的,您可以通过在 VS Code 中使用 JSON 选项中的 typeScript.unstable 条目来选择它们。以下是您可以尝试的所有选项(默认设置):
{
“typescript.unstable”: {
// Should sorting be case-sensitive? Can be:
// – true
// – false
// – “auto” (auto-detect)
“organizeImportsIgnoreCase”: “auto”,

    // Should sorting be "ordinal" and use code points or consider Unicode rules? Can be:
    // - "ordinal"
    // - "unicode"
    "organizeImportsCollation": "ordinal",

    // Under `"organizeImportsCollation": "unicode"`,
    // what is the current locale? Can be:
    // - [any other locale code]
    // - "auto" (use the editor's locale)
    "organizeImportsLocale": "en",

    // Under `"organizeImportsCollation": "unicode"`,
    // should upper-case letters or lower-case letters come first? Can be:
    // - false (locale-specific)
    // - "upper"
    // - "lower"
    "organizeImportsCaseFirst": false,

    // Under `"organizeImportsCollation": "unicode"`,
    // do runs of numbers get compared numerically (i.e. "a1" < "a2" < "a100")? Can be:
    // - true
    // - false
    "organizeImportsNumericCollation": true,

    // Under `"organizeImportsCollation": "unicode"`,
    // do letters with accent marks/diacritics get sorted distinctly
    // from their "base" letter (i.e. is é different from e)? Can be
    // - true
    // - false
    "organizeImportsAccentCollation": true
},
"javascript.unstable": {
    // same options valid here...
},

}
复制代码
完善的 switch/case 代码补全

速度、内存和包体积的优化
官方称 5.0 版本,不管是代码结构、数据结构还是算法实现都进行了很“强大”的改变。在使用 TypeScript 的时候,感觉会变得更快,不仅仅是在运行 TypeScript 的时候,甚至在安装它的时候都会觉得更快。
举了两个例子:

使用 TypeScript 5.0 Beta 构建 VS Code 花的时间只占了使用 4.9 去构建的 81%。
TypeScript 5.0 Beta(37.3MB) 包体积是 4.9(63.8MB)的 58%。

TypeScript 5.0 更新了一些值得注意的改进,下面我们会逐一介绍。
首先,我们最近将 TypeScript 从命名空间转移到了模块中,这使我们能够利用现代构建工具来执行优化,如作用域提升。使用这些工具,重新审视我们的打包策略,并删除一些废弃的代码,使 TypeScript 4.9 的63.8 MB 包大小减小了约 26.4 MB。这也通过直接函数调用带来了显着的加速。我们在这里写了一篇关于我们迁移到模块的详细说明。
TypeScript 还增加了对编译器内部对象类型的更一致性,并在一些对象类型上减少了存储的数据。这减少了多态操作,同时平衡了由于使我们的对象结构更统一而带来的内存使用增加。
我们还对序列化信息到字符串时进行了一些缓存。类型显示,可能会作为错误报告、声明发出、代码完成等的一部分发生,可能会相当昂贵。TypeScript现在对一些常用的机制进行了缓存,以便在这些操作之间重复使用。
我们所做的另一个值得注意的改变是,利用var关键字偶尔规避在闭包中使用let和const所产生的成本,这提高了我们的一些解析性能。
总的来说,我们期望大多数代码库应该会从 TypeScript 5.0 中看到速度的提升,并且一直能够重复获得 10% 到 20% 的胜利。当然,这将取决于硬件和代码库的特征,但我们鼓励您今天就在您的代码库上尝试它!
有关更多信息,请参见我们的一些值得注意的优化:

迁移到模块
节点单态化
Symbol 单态化
标识符大小减小
Printer 缓存
有限使用 var

参考

《Announcing TypeScript 5.0》

《Announcing TypeScript 5.0 Beta》

« »