学习 element-plus 的 tsconfig 配置方式

1,028 阅读7分钟

最近在研究 Vue 项目中的 TypeScript 配置,于是去参考了下 element-plus 项目中的 tsconfig 文件。

element-plus 的 Github 仓库:github.com/element-plu…

整体架构

image.png

粗略地浏览一遍项目,可以发现 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 中的配置。

关于 includeexclude 字段

可以发现,该配置文件作用于 packages 目录下的所有文件,以及 typings 目录下的两个类型声明文件。同时,它排除了 node_modules 目录,构建结果目录,测试目录等无需类型检查的内容。

关于 lib 字段

lib 字段用于引入 JS 内置接口和宿主环境(如浏览器,Node 环境等)接口的类型声明。

文件中一共引入了 ES2018, DOMDOM.Iterable 三份类型声明:

  • 其中 ES2018 很好理解,表示引入 JS 标准对象的类型声明。
  • DOM 也较好理解,表示引入 documentwindow 等浏览器接口的类型声明。
  • DOM.Iterable 可能较为少见,它包含了 DOM 和 BOM 对象中的遍历方法的类型声明,例如keys(),values(),entries(),Symbol.iterator 等等。

需要注意的是,DOM 选项中并没有包含这些用于遍历的接口的类型声明,它仅提供了功能性接口的类型声明。因此 DOMDOM.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 的主要不同在于 libtypes 选项。由于该文件仅用于 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 的基础上引入了 jsdomunplugin-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 文件配置的原因。

如有疏漏,恳请指正。