tsconfig.json文件各字段吐血整理

tsconfig.json文件说明 一般在 typescript 的项目中,我们都能看到 tsconfig.json 这个文件,它指定了此项目的编译选项,也指定了此项目的根目录,因此这个文件一般也是在项目的根目录下。既然如此,就单单 typescript 项目而言,它的编译一般有以下几种方式:

  • 命令行直接输入 tsc 命令不带任何参数进行编译:
    此时编译器会从当前目录开始查找 tsconfig.json 文件,如果当前目录没有发现该文件,则逐级向父级目录搜索。如果一直没有检索到该文件,编译器会给出使用提示。
  • 命令行调用 tsc 带参数 --project(或 -p) 而指定一个目录:
    编译器直接在该目录下查找 tsconfig.json 文件,如果没找到则报错。
  • 命令行调用 tsc 后直接指定文件:
    直接编译指定的文件。
各字段说明 tsconfig.json 所包含的属性其实并不多,顶层属性只有几个。最重要的而且最多的应该属 compilerOptions 这个属性中的各个字段了。这个字段中的属性居多,而且 中文官网 讲述的很清晰。本篇文章重点来聊聊除此之外的顶层属性。
另外,需要注意的是,在命令行上指定的编译选项会覆盖在tsconfig.json文件里的相应选项。
1. files
数组类型,用于表示由 ts 管理的 文件 的具体路径,可以是相对或绝对路径。这些文件内部有依赖的模块(或者引入了哪些模块),编译器也会搜索到依赖模块进行编译。如果某些模块并没有在项目中引入,虽然在项目目录中也不会被编译。需要注意的是,files 中不支持 glob 匹配模式的路径。
2. include 与 exclude
数组类型,include 用于表示 ts 管理的文件。exclude用于表示 ts 排除的文件(即不被编译的文件)。其中的文件列表可以使用 glob 匹配模式列表,支持的glob通配符有:
  • * 匹配0或多个字符(不包括目录分隔符)
  • ? 匹配一个任意字符(不包括目录分隔符)
  • **/ 递归匹配任意子目录
注意,这三者的优先级是这样的:files > exclude > include 。如果不指定 files ,项目目录下的所有文件都会被编译器编译。如果同一个文件在三者中均指定,此文件一定会被编译器编译。而 files 中不指定而在 excludeinclude 中同时指定的文件也会被编译,因为优先级是这样的 exclude > include 。另外,exclude默认情况下会排除node_modulesbower_componentsjspm_packagesoutDir 目录。
3. compileOnSave
布尔类型,可以让 IDE 在保存文件的时候根据 tsconfig.json 重新生成编译后的文件。
4. extends
字符串类型,该值是一个路径,指定另一个配置文件用于继承 tsconfig.json 中的配置。在原文件里的配置最先被加载,原文件里的配置被继承文件里的同名配置所重写。 如果发现循环引用,则会报错。
5. typeAcquisition
对象类型,设置自动引入库类型定义文件。acquisition 翻译过来是 “获得物、获得” 的意思。在整个项目中,如果存在用JavaScript写的库,ts 会自动去 compilerOptions.typeRoots 指定的目录中寻找对应的类型声明文件。这个行为被称为 typeAcquisition (类型获得)。这个行为可以通过enable来开启或关闭,且以库级别来指定应用的范围。但我在实践中,通过指定 enable 的值去控制这个行为并未有明显的感官,即使使用 vscode 修改配置后重启也并未生效。
当我使用 jquery 做测试的时候,将 enable 设为 false 且下载了 @types/jquery 的时候,vscode 并未提示无法找到该声明,也无任何报错。但当我将其设为 true,且删除 @types/jquery时,vscode 仍未提示无法找到该声明,鼠标悬浮引入的 jquery 提示在全局的 typescript/3.8/node_modules/@types/ 目录下找到了该声明。
这个配置项在平时的开发中并不常用,大家也不必深究。
6. watchOptions
对象类型,typescript3.8 以上新增加的配置,用来配置使用哪种监听策略来跟踪文件和目录。由于 tsc 的监听文件机制依赖于 nodefs.watch/fs.watchFile。这两种方法的实现并不相同,前者是采用文件系统的事件做到通知,而后者使用轮询的机制。更多可以查阅 node 官方文档。
  1. watchFile
    字符串类型,配置单个文件的监听策略,必须为一下几个值:
    • useFsEvents(默认):采用系统的文件系统的原生事件机制监听文件更改
    • useFsEventsOnParentDirectory:采用系统的文件系统的原生事件机制监听修改文件所在的目录,这样修改一个文件实际上监听的是此文件所在的目录都被监听了,如此整个项目的文件监听器将显著减少,但可能导致监听并不准确。
    • dynamicPriorityPolling:创建一个动态队列去监听文件,修改频率较低的文件将被减少轮询监听的频率。
    • fixedPollingInterval:固定间隔的检查每个文件是否发生变化。
    • priorityPollingInterval:固定间隔的检查每个文件是否发生变化,但使用启发式监听的文件的检查频率要低于非启发式监听的文件。
  2. watchDirectory
    字符串类型,配置监听目录的策略,必须为以下几个值:
    • useFsEvents(默认)
    • dynamicPriorityPolling
    • fixedPollingInterval
    以上三个和 watchFile 中相差不多
  3. fallbackPolling
    当采用系统的文件系统中原生事件机制监听文件时,此选项指定本机的文件监听器被耗尽或者不支持本机文件监听器是编译器采用的轮询策略,可以设置为以下几个值:
    • fixedPollingInterval
    • dynamicPriorityPolling
    • priorityPollingInterval
    • synchronousWatchDirectory:禁用对目录的延迟监听。如果有大量的文件更改,比如在 npm installnode_modules 目录发生的变化,延迟监听是非常有用的。但总有些不常见的场景需要禁用延迟监听。
  4. synchronousWatchDirectory
    布尔类型,是否对目录延迟监听。如果配置为 true ,当文件发生修改时同步的调用回调并更新目录监听器。
  5. excludeFiles
    字符串数组,用于指定不需要被监听变化的文件
  6. excludeDirectories
    字符串数组,用于指定不需要被监听变化的目录
7. reference
项目引用是 TypeScript 3.0的新特性,它支持将 TypeScript 程序的结构分割成更小的组成部分。
这是 typescript 官网中的描述,那怎么理解这句话呢。我们通过一个场景认识新出这种的 reference 特性。
假设我们要开发一个类似于 lodash 的工具库,并在项目中使用,而且后期很有可能还要在业界推广。为了保证这个工具的顺利开发及推广,我们必须要做相应的单元测试。那这个工具库可以看做一个项目,对其中的每个功能的测试也可作为一个独立的项目。但整个过程中,工具库的开发和测试应该是属于同一个项目下 “分项目” 的。那这种情况下 reference 就很棒了。首先我们搭一个目录出来:
|---- src/ |---- index.ts// 整个工具库的入口 |---- copyDeep.ts // 其中定义了copyDeep方法 |---- test/ |---- copyDeep.test.ts // copyDeep的单元测试 |---- package.json |---- tsconfig.json

copyDeep.test.ts 中肯定要引用 src/copyDeep,也就是说 test 的项目是依赖于 src 的。如果 src 中的代码发生了变化,整个工具库项目应该重新编译,而 test 项目不应该再被编译,这本来就是合理的。如果 test 项目中的代码发生了变化,那 test 项目应该被重新编译,而 src 项目不应该再被编译。如何在一个项目中配置而做到分别编译相应的子项目呢?首先最先想到的应该是在 tsconfig.json 文件中引入 include 字段配置,我们先尝试一下下面的配置:
{ "files": [ "./src/index.ts" ], "include": [ "./test/**/*.test.ts" ], "compilerOptions": { "outDir": "./dist/" } }

我们来分析这样配置的会有哪些问题:
  1. 首先,从整个项目层面,确实做到了修改任意文件重新编译的功能。但注意,编译的是全量的 ts 文件。
  2. 随着日后项目的增大,在 *.test.ts 文件中引入也将逐渐变大。
  3. 修改了 src//**/*.ts 的内容,test/**/*.ts 也将作为输出,这是我们不希望看到的。
此时,reference 将解决上述的每一个问题,我们修改项目结构如下:
|---- src/ |---- index.ts// 整个工具库的入口 |---- copyDeep.ts// 其中定义了copyDeep方法 |---- tsconfig.json // 工具库的编译配置文件 |---- test/ |---- copyDeep.test.ts// copyDeep的单元测试 |---- tsconfig.json// 测试的编译配置文件 |---- package.json |---- tsconfig.json

并修改为以下配置:
// 根目录下的 /tsconfig.json { "compilerOptions": { "declaration": true, // 为子项目生成.d.ts声明文件 "outDir": "./dist", } }// src目录下的 /src/tsconfig.json { "extends": "../tsconfig", "compilerOptions": { "composite": true // 必须设置为true,表明该文件夹为一个子项目 } }// test目录下的 /src/tsconfig.json { "extends": "../tsconfig", "references": [ { "path": "../src" } // 表示引用了工具库项目 ] }

这样配置后,如果 src 项目已经编译完成并且输出了编译后的文件, 那在 test 项目中,实际加载的是 src 项目声明的 .d.ts 文件,而且这个声明文件是对 test 项目可见的。另外,如果开启了 watch 模式,修改了内容只会编译相应的项目而不会全量编译。这会显著的加速类型检查和编译,减少编辑器的内存占用。而且在代码结构层命有了一个很清晰的规划。
总结来讲,refrence 的作用是将两个项目关联起来作为一个项目开发,当某个项目代码修改后还能单独编译相应的项目而不是整个项目。再说的简单点,就是实现了关联项目间的懒编译。
总结
本篇文章先到这里,总结一下:tsconfig.json 这个文件是用来界定 ts 项目的根目录,也用来配置 tsc 在编译 ts 文件时的一些选项。files、exclude、include 用来配置需要编译哪些文件;compilerOnSave 是指定 IDE 保存后是否重新编译的;extends 用来扩展当前的配置;扩展配置文件中的字段会覆盖当前文件的相同字段;typeAcquisition 用来指定某些库的类型声明文件,如:
"typeAcquisition": { "jquery": "@/types/jquery" }

watchOptions 用来配置 tsc 的监听策略;reference 指定关联项目,从而提高编译速度。
另外:
【tsconfig.json文件各字段吐血整理】文中若有表述不妥或是知识点有误之处,欢迎留言批评指正,共同进步!

    推荐阅读