最近在研究 Vue 项目中的 TypeScript 配置,于是去参考了下 element-plus 项目中的 tsconfig 文件。
element-plus 的 Github 仓库:github.com/element-plu…
整体架构
粗略地浏览一遍项目,可以发现 element-plus 项目的根目录中一共有 7 份 TypeScript 配置文件,根据名称可以大致猜测其用途:
tsconfig.json:入口配置文件。tsconfig.base.json:所有环境共用的基础配置文件。tsconfig.web.json:Web 环境下的配置文件。tsconfig.node.json:Node 环境下的配置文件。tsconfig.vite-config.json:专用于vite.config.ts的配置文件。tsconfig.vitest.json:用于 vitest 测试工具环境下的配置文件。tsconfig.play.json:用于 Playground 环境的配置。
下面将逐一分析上述文件的具体内容。
tsconfig.json 文件
tsconfig.json作为 TypeScript 的入口配置文件,其内容如下:
// tsconfig.json
{
"files": [],
"references": [
{ "path": "./tsconfig.web.json" },
{ "path": "./tsconfig.play.json" },
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.vite-config.json" },
{ "path": "./tsconfig.vitest.json" }
]
}
可以看到,tscofig.json只做了一件事:使用references字段来引入其他 5 份配置文件。
tsconfig.json本身则并不包含任何配置。
设置"files": []选项的原因我猜测是:
- 如果没有设置
files字段,则include字段的默认值将会是**,即包含项目的所有文件。 - 而前面提到,
tsconfig.json仅负责引入各个环境下的具体配置文件,本身不负责任何配置。 - 因此需要将
files字段设置为[],此时include的默认值也将为[],这样tsconfig.json本身就不会作用于任何文件,也就不会影响到其他配置文件的内容。
关于files字段对include默认值的影响可以参考文档:
使用references字段将项目分割为几个部分单独配置和处理的方式可以参考:
tsconfig.base.json 文件
该文件保存了各配置文件共享的基础配置。它的内容如下:
// tsconfig.base.json
{
"compilerOptions": {
"outDir": "dist", // 将编译结果输出到 dist 文件夹中
"target": "es2018", // 将代码编译为 ES9
"module": "esnext", // 使用最新的 ES Module 标准打包代码
"baseUrl": ".", // 设置模块解析的基础路径
"sourceMap": false, // 不生成 sourceMap
"moduleResolution": "node", // 使用 node 的模块解析方式
"allowJs": false, // 不允许引入 js 文件
"strict": true, // 开启严格的类型检查
"noUnusedLocals": true, // 对未使用的本地变量进行报错
"resolveJsonModule": true, // 允许使用 import 引入 JSON 文件
"allowSyntheticDefaultImports": true, // 允许使用 import xxx from 'xxx' 的形式引入默认内容,而非必须使用 import * as xxx from 'xxx' 的方式引入
"esModuleInterop": true, // 控制默认内容导入的转译规则
"removeComments": false, // 编译后删除注释内容
"rootDir": ".", // 设置代码的根文件夹,会影响编译结果的目录结构
"types": [], // 不引入任何环境的类型定义
"paths": {
"@element-plus/*": ["packages/*"] // 设置路径别名
}
}
}
可以看到,该文件没有指定作用范围,也没有引入任何环境的类型定义,仅包含了一些构建选项、模块系统和一些基础的类型检查方面的配置。
各个字段的作用已在注释中进行了标注,其中只有 esModuleInterop 字段的作用自己尚未明确了解,后续将继续进行相关学习。
esModuleInterop 字段的文档链接:
tsconfig.web.json 文件
显然,该文件是用于浏览器环境的配置文件,其内容如下:
// tsconfig.web.json
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"composite": true, // 使用 references 字段引入的配置文件所必需的字段
"jsx": "preserve", // 保留代码中的 JSX 代码
"lib": ["ES2018", "DOM", "DOM.Iterable"], // 引入 ES 自身和浏览器对象的类型定义
"types": ["unplugin-vue-define-options"], // 引入插件的类型定义
"skipLibCheck": true
},
"include": ["packages", "typings/components.d.ts", "typings/env.d.ts"],
"exclude": [
"node_modules",
"**/dist",
"**/__tests__/**/*",
"**/gulpfile.ts",
"**/test-helper",
"packages/test-utils",
"**/*.md"
]
}
关于 extends 字段
不出预料,该文件继承了 tsconfig.base.json 中的配置。
关于 include 和 exclude 字段
可以发现,该配置文件作用于 packages 目录下的所有文件,以及 typings 目录下的两个类型声明文件。同时,它排除了 node_modules 目录,构建结果目录,测试目录等无需类型检查的内容。
关于 lib 字段
lib 字段用于引入 JS 内置接口和宿主环境(如浏览器,Node 环境等)接口的类型声明。
文件中一共引入了 ES2018, DOM 和 DOM.Iterable 三份类型声明:
- 其中
ES2018很好理解,表示引入 JS 标准对象的类型声明。 DOM也较好理解,表示引入document和window等浏览器接口的类型声明。- 而
DOM.Iterable可能较为少见,它包含了 DOM 和 BOM 对象中的遍历方法的类型声明,例如keys(),values(),entries(),Symbol.iterator等等。
需要注意的是,DOM 选项中并没有包含这些用于遍历的接口的类型声明,它仅提供了功能性接口的类型声明。因此 DOM 和 DOM.iterable 两个两份类型声明并不重复,建议同时使用。
关于 types 字段
types 字段用于引入 npm 包中的类型定义。
文件中仅在 types 字段中引入了 unplugin-vue-define-options 这一个包的类型声明。这个包是一款 Vue 插件,用于在 <script setup> 标签中提供 defineOptions() 等接口,以类似 Vue2 的选项式 API 形式定义组件选项。
该插件一般在 vite.config.ts 等构建工具的配置脚本中引入,不会在 .vue 文件中显式引入。所以需要在 types 字段进行手动声明,不然 TS 就无法在 .vue 文件中提供相关的类型支持。
顺便记录下 types 字段的相关知识:
- 如果不设置
types字段,则 TypeScript 会默认引入node_modules/@types目录下的所有类型声明文件。无论文件是否真正使用这个包。 - 如果设置了
types字段,则 TypeScript 只会引入字段中 npm 包的类型声明,不会引入其他的类型声明文件。 - 无论
types如何设置,使用import等方式显式引入包时总会自动引入相关的类型声明。
types 字段的文档说明可以参考:
关于 compilerOptions 中的其他字段
剩下的字段作用都较好理解:
"composite": true:用于设置 TypeScript 的构建方式,通常使用references字段引入的配置文件都应添加这个选项。"jsx": "preserve":用于保留源代码中的 JSX 代码,不进行编译处理。"skipLibCheck": true:用于跳过类型声明文件的类型检查,主要是为节省时间。
tsconfig.node.json 文件
该文件是 Node 环境下的 TypeScript 配置文件,内容如下:
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"composite": true,
"lib": ["ESNext"],
"types": ["node"],
"skipLibCheck": true
},
"include": [
"internal/**/*",
"internal/**/*.json",
"scripts/**/*",
"packages/theme-chalk/*",
"packages/element-plus/version.ts",
"packages/element-plus/package.json"
],
"exclude": ["**/__tests__/**", "**/tests/**", "**/dist"]
}
其中多数选项和 tsconfig.web.json 中的内容类似,这里就不再赘述。
它和 tsconfig.web.json 的主要不同在于 lib 和 types 选项。由于该文件仅用于 Node 环境代码,因此只引入了 JS 标准对象和 Node 接口的类型声明。
tsconfig.vite-config.json 文件
该文件是专用于 vite.config.ts 文件的配置。它的内容如下:
{
"extends": "./tsconfig.node.json",
"compilerOptions": {
"composite": true,
"types": ["node"]
},
"include": ["**/vite.config.*", "**/vitest.config.*", "**/vite.init.*"],
"exclude": ["docs"]
}
理论上 vite.config.ts 文件也属于 Node 环境代码,直接使用 tsconfig.node.json 即可满足需要。将该文件和 tsconfig.node.json 进行对比,发现 tsconfig.vite-config.json 仅是缺少了 "lib": ["ESNext"] 和 "skipLibCheck": true 两个选项,其余部分基本相同。
目前暂不清楚为何要将 vite.config.ts 的配置单独进行编写,后续将会继续学习。
tsconfig.vitest.json 文件
该文件较好理解,是用于 Vitest 测试代码的配置文件。其内容如下:
{
"extends": "./tsconfig.web.json",
"compilerOptions": {
"composite": true,
"lib": ["ES2021", "DOM", "DOM.Iterable"],
"types": ["node", "jsdom", "unplugin-vue-define-options"],
"skipLibCheck": true
},
"include": ["packages", "vitest.setup.ts", "typings/env.d.ts"],
"exclude": ["node_modules", "dist", "**/*.md"]
}
其中代码较好理解,它在 tsconfig.web.json 的基础上引入了 jsdom 和 unplugin-vue-define-options 两个代码测试所需依赖库的类型声明,并无其他配置。
tsconfig.play.json 文件
这是最后一份配置文件,它的内容如下:
其内容如下:
{
"extends": "./tsconfig.web.json",
"compilerOptions": {
"allowJs": true,
"lib": ["ESNext", "DOM", "DOM.Iterable"]
},
"include": [
"packages",
"typings/components.d.ts",
"typings/env.d.ts",
// playground
"play/main.ts",
"play/env.d.ts",
"play/src/**/*"
]
}
从 include 字段可以看出,它是作用于 play 目录下的代码。浏览这些文件可以了解到这是一个试用 element-plus 组件的 Playground 环境。
在配置方面,它继承了 tsconfig.web.json 的配置,并添加了 "allowJs": true 这一选项,允许 .ts 文件导入 .js 文件的内容。应当是 Playground 环境需要引入编译好的 .js 文件,因此才单独添加了这份配置。
至此,所有的 TypeScript 配置文件就浏览完毕了。
总结
此次学习的收获有:
- 为浏览器环境、Node 环境等分别编写配置文件,然后使用
references字段在tsconfig.json中进行引入。tsconfig.json文件本身则不负责任何配置,仅用于导入其他配置文件。 - 可以将配置的公共部分提取为
tsconfig.base.json文件,然后通过extends字段进行引入。 - 浏览器环境下应当设置
"lib": ["DOM", "DOM.Iterable"]选项。 - Node 环境下应当设置
"types": ["Node"]选项。 - 通过
<script>或构件工具插件等方式全局导入的 npm 包应当在types字段中进行声明。
目前还未弄懂的有:
esModuleInterop字段的具体作用。- 单独编写
vite.config.ts文件配置的原因。
如有疏漏,恳请指正。