专栏名称: 全栈修仙之路
专注分享 TS、Vue3、前端架构和源码解析等技术干货。
目录
相关文章推荐
传媒招聘那些事儿  ·  【简历提升】挖掘亮点:提升眼界思路,优化简历! ·  2 天前  
传媒招聘那些事儿  ·  小红书:蒲公英渠道销售经理(北/上) ·  3 天前  
传媒招聘那些事儿  ·  淘天集团:淘宝秒杀-母婴/美妆行业商品运营 ·  2 天前  
51好读  ›  专栏  ›  全栈修仙之路

TypeScript 5.8 beta 来了,支持 require ESM

全栈修仙之路  · 公众号  ·  · 2025-02-03 08:09

正文

TypeScript 5.8 beta 版本来了,你可以在 5.8 Iteration Plan 查看所有被包含的 Issue 与 PR。如果想要抢先体验新特性,执行:

$ npm install typescript@beta

来安装 beta 版本的 TypeScript,或在 VS Code 中安装 JavaScript and TypeScript Nightly ,并选择为项目使用 VS Code 的 TypeScript 版本(cmd + shift + p, 输入 select typescript version),来更新内置的 TypeScript 支持。

函数类型推导优化

在 TypeScript 中,我们经常遇到函数声明的返回值类型依赖于参数类型的情况,如以下的示例:

enum ValueKind {
    Single,
    Multiple,
}

async function getValue(
    value: ValueKind,
): Promise<string | string[]> 
{
    if (value === ValueKind.Single) {
        return '';
    }
    else {
        return [];
    }
}

我们希望,getValue 的返回值类型取决于参数 value 的类型。这在逻辑层是很好实现的,但在类型层面则需要一些额外的处理,比如你可能会首先想到重载:

async function getValue(
    value: ValueKind.Single,
): Promise<string>
;
asyncfunction getValue(
    value: ValueKind.Multiple,
): Promise<string[]>
;
asyncfunction getValue(
    value: ValueKind,
): Promise<string | string[]> 
{
    if (value === ValueKind.Single) {
        return'';
    }
    else {
        return [];
    }
}

getValue(ValueKind.Single) // string;
getValue(ValueKind.Multiple) // string[];

然而重载有两个比较显著的问题,首先,它并不会在函数实现内检查每个重载实现的返回值,比如:

if (value === ValueKind.Single) {
-        return '';
+        return [];
}

这里的返回值实际上并不符合我们指定的重载声明,但 TypeScript 目前并不会检查其有效性。

另外一个问题则是,在参数类型-返回值类型对应较多的情况下,密密麻麻的重载会导致函数签名难以阅读,同时也不能很好处理参数类型-返回值类型对应关系需要通过进一步计算得到,而不是直接枚举的情况。

另外一个实现这种情况的方式则是使用条件类型:

type ReturnValueextends ValueKind> =
    S extends ValueKind.Multiple ? string[] :
    string;

async function getValue<S extends ValueKind>(
    value: S,
): Promise<ReturnValue<S>> 
{
    if (value === ValueKind.Single) {
        return ''// Type 'string' is not assignable to type 'ReturnValue'
    }
    else {
        return []; // Type 'string[]' is not assignable to type 'ReturnValue'
    }
}

但这种使用也会带来新的问题,即如上的报错。这是因为 TypeScript 会直接将返回语句的类型,与函数返回值的类型进行比较,即 string vs S extends ValueKind.Multiple ? string[] :     string ,而在这个比较过程中,类型参数 S 是还没有被填充的,因此这个类型比较必定不兼容——类似于重载的情况。

你当然可以通过额外的类型断言来修正这一情况,但这样就变成是我们在指导 TypeScript 类型,而不是由它自己来推导类型并确保类型安全了。

很容易想到,既然我们都已经走到能够进行类型收窄的语句内了,如果能对应填充下参数类型,岂不是就解决这个问题了?在 TypeScript 4.8 版本中就支持了这一能力:


type ReturnValueextends ValueKind> =
    S extends ValueKind.Multiple ? string[] :
    S extends ValueKind.Single ? string :
    never;

asyncfunction getValue<S extends ValueKind>(
    value: S,
): Promise<ReturnValue<S>> 
{
    if (value === ValueKind.Single) {
        return'';
    }
    else {
        return [];
    }
}

要使用这个能力,我们需要更新一下 ReturnValue 工具类型的实现,让我们预期的所有参数类型的匹配结果都显式标注出来(语句块内对条件类型进行类型收窄时,只会在完全命中一个 extends 条件时生效)。

require ESM 支持

说实话我每次看到 CJS 和 ESM 的『你中有我,我中有你』时都感到一丝蛋疼,估计 Ryan 也没想到这么多年过去了,社区还在打补丁,就好像在努力挽回一段破碎的婚姻一样。

此前我们可以在 ESM 中加载 CJS 模块,但反之则行不通,所以当你必须兼容 CJS 时,又遇到只发布了 ESM 产物的 npm 包,那得是多么头大的情况。而 NodeJS 22 版本实验性质地支持了使用 require 加载 ESM 文件( loading-ecmascript-modules-using-require ),能够在 CJS 中加载同步(即不包括 top-level await)的 ESM 文件。

TS 5.8 版本现在能够在启用 --module nodenext 配置时支持这一行为,需要注意的是由于这个功能特性可能会反向移植到更早的 NodeJs 版本,比如 Node 20,因此还不会提供具体的如 --module node18 这样精确的版本控制。

NodeJs TS 支持 --erasableSyntaxOnly

NodeJs 22.6.0 版本开始通过实验性的 --experimental-strip-types 选项支持直接执行 TS 文件,同时在 23.6.0 版本开始默认启用了此配置,也就是说,你可以直接 node index.ts 而不需要额外的命令行参数了,对应的,23.6.0 也新增了 --no-experimental-strip-types 配置来阻止这一默认行为。

NodeJs 底层是通过把类型相关的代码替换为空白字符串来实现此能力的,其底层是 Amaro ,Amaro 底层则是 @swc/wasm-typescript ,整体使用大致是这样:

const amaro = require('amaro');

const { code } = amaro.transformSync("const foo: string = 'bar';", { mode: "strip-only" });

console.log(code); // "const foo         = 'bar';"

对于某些不能直接移除的 TS 特性(比如枚举),NodeJs 23.7.0 也支持了 --experimental-transform-types 配置来做执行前的语法转换(通过设置 transformSync options 的 mode 为

'transform'),比如第一个例子中的代码,如果直接执行会有这么个错误:

node:internal/modules/typescript:67
        throw new ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX(error.message);
        ^

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]:   x TypeScript enum is not supported in strip-only mode
   ,-[1:1]
 1 | ,-> enum ValueKind {
 2 | |     Single,
 3 | |     Multiple,
 4 | `-> }

如果你在迁移某些工具链到原生的 NodeJs TS 支持,并且希望使用 'strip-only' 模式,那么就必须检查出代码中无法直接 strip 的部分。因此,TypeScript 5.8 引入了 --erasableSyntaxOnly 配置,能够帮你提前检查出这种代码。

先是为 JS 引入类型注释的 TC39 提案 proposal-type-annotations ,接着是 NodeJs 支持直接执行 TS 文件,感觉浏览器直接运行 TS 文件也不远了?

变量初始化检查(5.7 补档)

TS 5.7 版本开始支持了对跨函数上下文的未初始化变量检查,如对于以下的例子:

function foo({
    let result: number
    
    function printResult({
        console.log(result); // ok before 5.7, error since 5.7
    }
}

TS 5.7 版本只有这个类型能力增强值得一写,所以我就直接把 TS 5.7 的 Devblog 鸽了 :-)

其它

计算属性类型声明优化

此前 TypeScript 会约束 Class 结构内的计算属性,要求其类型必须是字面量类型或 symbol 类型,如:







请到「今天看啥」查看全文