理解Typescript配置项: isolateModules

首先,我们有这样一个简单的项目,它的目录结构是这样的

.
├── package.json
├── src
│   ├── index.ts
│   └── types.ts
├── tsconfig.json
└── yarn.lock
复制代码

我们的types.ts里的内容非常简单

export type UserConfig = {
  apiToken: string;
  uuid: string;
};
复制代码

接下来,我们分两种情况,在index.ts中引入UserConfig

Ⅰ 引入,并在index.ts作为类型使用

import { UserConfig } from './types';

export const userConfig: UserConfig = {
    apiToken: '114514',
    uuid: '1919810'
}
复制代码

Ⅱ 引入,在index.ts中直接直接导出, Ⅲ直接重导出

import { UserConfig } from './types';
export { UserConfig }
复制代码
export { UserConfig } from './types';
复制代码

显然,我们知道, Typescript在生成Javascript的代码时,会擦除我们定义的Typescript类型,在运行时,Typescript的类型系统是不存在的。

对于仅做类型擦除的编译器,比如被广泛使用的Babel,它对.ts文件的类型分析,仅限于当前文件,并没有跨文件分析的能力。那么对于上面三种index.ts,它就会做这样的处理。

对于Ⅰ,transfomer到了UserConfig是一个Typescript的类型使用的,因此移除它的所有定义和引用。显然,它是被import { UserConfig } from './types'定义的,那么,删除掉关于UserConfig的引入。

最后Babel编译出来的代码是这样的

const userConfig = {
  apiToken: '114514',
  uuid: '1919810'
}
复制代码

对于单个模块(文件)如果它的导入导出是确定的, 我们称呼他们是模块隔离的

但对于Ⅱ和Ⅲ,Babel并没有办法从单个index.ts文件中分析出,UserConfig是一个类型。那么经过Babel处理之后它就会原汁原味保留下来。

然而下面的代码会在Webpack中提示模块types 并没有导出成员 UserConfig,在Vite中会直接引起浏览器的Runtime error

import { UserConfig } from './types';
export { UserConfig }
复制代码
export { UserConfig } from './types';
复制代码

显然,这两种index.ts并不是模块隔离的。因为UserConfig不确定是Typescript中类型还是JavaScript运行时的值

除了引入类型不使用,重导出类型之外,还有两种情形也会导致模块隔离被破坏

  • 引入并使用了const enum的值
  • Namespaces,同名的namespace可以跨越多个文件,最后编译时他们会被tsc合并,这种跨文件的文件处理babel是做不到的

那么,isolateModules选项的功能也就很自然能理解了。

它会强制开发者的每个模块都能作为单个模块独立编译。当这种保证存在时,我们可以选择babel, swc, esbuild等仅做类型擦除的单文件的编译器。

那么,当我们打开isolateModule时,我们要移除const enum(用普通的enum代替),和跨文件namespace的使用,对于上面的Ⅱ和Ⅲ,我们也需要修改一下。 这种修改的核心思路就是,既然编译器不知道,那么就显式告诉编译器,我们引入或导出的内容,就是一个类型

// 法1
import type { UserConfig } from './types';
export { UserConfig };

// 法2
import { UserConfig } from './types';
export type { UserConfig }
复制代码
export type { UserConfig } from './types'
复制代码

结论

下面这些情况会破坏模块的隔离性

  • 从其它模块类型后未使用该类型
  • 重导出(expot { Type } from)其它模块的类型
  • 引入其它模块的const enum并使用
  • 使用namespace语法

isolateMododules

  • 开启后会强制要求开发者保持模块的隔离性
  • 如果使用babel, swc等非tsc编译器,强烈推荐打开isolateModules来避免潜在的Runtime error
  • 如果一个类型导入后不被使用,请使用import type { SomeType } from 'module',告诉编译器你导入的是一个类型
  • 如果需要导出类型,请使用export type { SomeType },但如果引入时使用import type,那么也可以直接export { SomeType }
  • 如果需要重导出类型,请使用export type { SomeType } from 'module'
  • 所有import typeimport type类型会告诉编译器,导入的是一个类型,他们都要最终编译产物中移除

关于tsc

  • tsc的类型分析是项目级的,因此比起其它单文件分析类型的编译器,它能比其它的知道更多类型信息,因此对于非模块隔离的代码也能灵活处理
  • 如果使用ts-loader, 它会用tsc分析整个项目。然而当它的transpileOnly选项为true时,它也将降级为仅对当前文件进行分析,此时也必须要求模块的隔离性,请启用isolateModules
  • 项目级的类型分析比单文件的类型分析更加强大,但代价就是它的运行速度会比单文件分析更慢。

猜你喜欢

转载自juejin.im/post/7053298681037979678