前言
tsconfig.json 文件必须在 TypeScript 项目的根目录下。
tsconfig.json 文件中指定了用来编译这个项目的根文件和编译选项。
一、TSC 命令的使用
在使用 tsc 命令时,只有其后不加任何参数,才会使用 tsconfig.json 配置进行编译。
tsc
此时编译器会从当前目录开始查找 tsconfig.json 文件,如果当前目录没有发现该文件,则逐级向父级目录搜索。如果一直没有检索到该文件,编译器会给出使用提示。
若要直接编译指定的文件,则需要在 tsc 后指定文件名:
tsc <文件名>
TSC 的可选项:
- –project(简写 -p):编译器直接在该目录下查找 tsconfig.json 文件,如果没找到则报错。
- –build(简写 -b):编译器会进行增量构建。先找到所有引用的工程,然后检查它们是否为最新版本,最后按顺序构建非最新版本的工程。
- –verbose(简写 -v):打印详细的日志(可以与其它标记一起使用)。
- –dry: 显示将要执行的操作但是并不真正进行这些操作。
- –clean: 删除指定工程的输出(可以与–dry一起使用)。
- –force: 把所有工程当作非最新版本对待。
- –watch(简写 -w):观察模式。watch 模式监控当前项目 ts 文件变化立即进行编译。
- 其他 TSC 的可选项请参见:TS 中文官网配置文件之编译选项
二、tsconfig 配置文件的属性
1、tsconfig 配置文件的顶层属性
(1)、compilerOptions
compilerOptions 是编译选项,可以被忽略,这时编译器会使用默认值。
compilerOptions 的配置详见:TS 中文官网配置文件之编译选项
(2)、file
file 数组类型。它表示由 ts 管理的 文件 的具体路径,可以是相对或绝对路径。
这些文件内部有依赖的模块(或者引入了哪些模块),编译器也会搜索到依赖模块进行编译。
如果某些模块并没有在项目中引入,虽然在项目目录中也不会被编译。
如果不指定 files ,项目目录下的所有文件都会被编译器编译。
【注意】files 中不支持 glob 匹配模式的路径。
(3)、 include 和 exclude
include 和 exclude 都是数组类型。
include 用于表示 ts 管理的文件。
exclude 用于表示 ts 排除的文件(即不被编译的文件)。
文件列表可以使用 glob 匹配模式列表,支持的 glob 通配符有:
* :匹配0或多个字符(不包括目录分隔符)。? :匹配一个任意字符(不包括目录分隔符)。**/ :递归匹配任意子目录。
【注意】
- files、exclude 和 include 这三者的优先级是这样的:
files > exclude > include 。 - exclude 默认情况下会排除
node_modules 、bower_components 、jspm_packages 和 outDir 目录。
【拓展】 glob:是一个古老的 UNIX 程序,它用来匹配路径文件名(pathname模式匹配),现在在 Linux Shell 使用和编程方面,glob 还在被广泛使用。glob 的模式匹配跟正则表达式不太一样,它比正则表达式要简单一些。glob 的模式匹配有时也叫做通配符匹配(wildcard matching)。具体的规则可参考:glob 模式匹配简明教程
(4)、compileOnSave
布尔类型,可以让 IDE 在保存文件的时候根据 tsconfig.json 重新生成编译后的文件。
(5)、extends
字符串类型,该值是一个路径,指定另一个配置文件用于继承 tsconfig.json 中的配置。在原文件里的配置最先被加载,原文件里的配置被继承文件里的同名配置所重写。 如果发现循环引用,则会报错。
(6)、typeAcquisition
typeAcquisition 配置项在平时的开发中并不常用,了解即可。
对象类型,设置自动引入库类型定义文件。acquisition 翻译过来是 “获得物、获得” 的意思。在整个项目中,如果存在用JavaScript写的库,ts 会自动去 compilerOptions.typeRoots 指定的目录中寻找对应的类型声明文件。这个行为被称为 typeAcquisition (类型获得)。这个行为可以通过enable来开启或关闭,且以库级别来指定应用的范围。但我在实践中,通过指定 enable 的值去控制这个行为并未有明显的感官,即使使用 vscode 修改配置后重启也并未生效。
(7)、watchOptions
对象类型,用来配置使用哪种监听策略来跟踪文件和目录。由于 tsc 的监听文件机制依赖于 node 的 fs.watch / fs.watchFile 。这两种方法的实现并不相同,前者是采用文件系统的事件做到通知,而后者使用轮询的机制。更多可以查阅 node 官方文档里的:fs.watch 和 fs.watchFile
-
watchFile:字符串类型,配置单个文件的监听策略,必须为一下几个值:
- useFsEvents(默认):采用系统的文件系统的原生事件机制监听文件更改
- useFsEventsOnParentDirectory:采用系统的文件系统的原生事件机制监听修改文件所在的目录,这样修改一个文件实际上监听的是此文件所在的目录都被监听了,如此整个项目的文件监听器将显著减少,但可能导致监听并不准确。
- dynamicPriorityPolling:创建一个动态队列去监听文件,修改频率较低的文件将被减少轮询监听的频率。
- fixedPollingInterval:固定间隔的检查每个文件是否发生变化。
- priorityPollingInterval:固定间隔的检查每个文件是否发生变化,但使用启发式监听的文件的检查频率要低于非启发式监听的文件。
-
watchDirectory:字符串类型,配置监听目录的策略,必须为以下几个值(和 watchFile 里的对应值的功能一样):
- useFsEvents(默认)
- dynamicPriorityPolling
- fixedPollingInterval
-
fallbackPolling:当采用系统的文件系统中原生事件机制监听文件时,此选项指定本机的文件监听器被耗尽或者不支持本机文件监听器是编译器采用的轮询策略,可以设置为以下几个值(前三个和 watchFile 里的对应值的功能一样):
- fixedPollingInterval
- dynamicPriorityPolling
- priorityPollingInterval
- synchronousWatchDirectory:禁用对目录的延迟监听。如果有大量的文件更改,比如在 npm install 时 node_modules 目录发生的变化,延迟监听是非常有用的。但总有些不常见的场景需要禁用延迟监听。
-
synchronousWatchDirectory:布尔类型,是否对目录延迟监听。如果配置为 true ,当文件发生修改时同步的调用回调并更新目录监听器。 -
excludeFiles:字符串数组,用于指定不需要被监听变化的文件 -
excludeDirectories:字符串数组,用于指定不需要被监听变化的目录
(8)、reference
reference 是项目引用,它支持将 TypeScript 程序的结构分割成更小的组成部分。
refrence 的作用是将两个项目关联起来作为一个项目开发,当某个项目代码修改后还能单独编译相应的项目而不是整个项目,实现了关联项目间的懒编译。
下面举个应用场景:
假设:我们要开发一个类似于 lodash 的工具库,并在项目中使用,而且后期很有可能还要在业界推广。为了保证这个工具的顺利开发及推广,我们必须要做相应的单元测试。那这个工具库可以看做一个项目,对其中的每个功能的测试也可作为一个独立的项目。但整个过程中,工具库的开发和测试应该是属于同一个项目下 “分项目” 的。那这种情况下 reference 就很棒了。首先我们搭一个目录出来:
|---- src/
|---- index.ts
|---- copyDeep.ts
|---- test/
|---- copyDeep.test.ts
|---- 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/"
}
}
我们来分析这样配置的会有哪些问题:
- 首先,从整个项目层面,确实做到了修改任意文件重新编译的功能。但注意,编译的是全量的 ts 文件。
- 随着日后项目的增大,在 *.test.ts 文件中引入也将逐渐变大。
- 修改了 src///*.ts 的内容,test//*.ts 也将作为输出,这是我们不希望看到的。
此时,reference 将解决上述的每一个问题,我们修改项目结构如下:
|---- src/
|---- index.ts
|---- copyDeep.ts
|---- tsconfig.json
|---- test/
|---- copyDeep.test.ts
|---- tsconfig.json
|---- package.json
|---- tsconfig.json
并修改为以下配置:
{
"compilerOptions": {
"declaration": true,
"outDir": "./dist",
}
}
{
"extends": "../tsconfig",
"compilerOptions": {
"composite": true
}
}
{
"extends": "../tsconfig",
"references": [
{ "path": "../src" }
]
}
这样配置后,如果 src 项目已经编译完成并且输出了编译后的文件, 那在 test 项目中,实际加载的是 src 项目声明的 .d.ts 文件,而且这个声明文件是对 test 项目可见的。另外,如果开启了 watch 模式,修改了内容只会编译相应的项目而不会全量编译。这会显著的加速类型检查和编译,减少编辑器的内存占用。而且在代码结构层命有了一个很清晰的规划。
更多 reference 的配置详见:TS 中文官网配置文件之项目引用
2、tsconfig 具体配置与解析汇总
{
"include": ["./test.ts"],
"files": ["./src/**/*"],
"exclude": ["test.ts"],
"compilerOptions": {
"incremental": true,
"target": "es5",
"module": "commonjs",
"lib": ["es5", "dom", "ScriptHost", "es2015.promise"],
"allowJs": true,
"checkJs": true,
"jsx": preserve,
"declaration": true
"declarationMap": true
"sourceMap": true,
"outFile":"./",
"outDir": './build',
"rootDir": "./src",
"composite": true,
"tsBuildInfoFile": "./",
"removeComments": true,
"noEmit": true,
"importHelpers": true,
"downlevelIteration": true,
"isolatedModules": true,
"strict": false,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"baseUrl": './'
"paths": {},
"rootDirs": [],
typeRoots: [],
types:[],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true ,
"preserveSymlinks": true,
"allowUmdGlobalAccess": true,
"sourceRoot": '',
"mapRoot",
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"compileOnSave": true,
"references":[]",
}
}
3、重点属性的配置详解
(1)、strict 模式详解
当 Typescript 严格模式设置为 on 时,它将使用 strict 族下的 严格类型规则 对项目中的所有文件进行代码验证。
strict 的严格类型规则包含以下属性:
规则名称 | 解释 |
---|
noImplicitAny | 不允许变量或函数参数具有隐式any类型。 | noImplicitThis | 不允许this上下文隐式定义。 | strictNullChecks | 不允许出现null或undefined的可能性。 | strictPropertyInitialization | 验证构造函数内部初始化前后已定义的属性。 | strictBindCallApply | 对 bind, call, apply 更严格的类型检测。 | strictFunctionTypes | 对函数参数进行严格逆变比较。 |
1??、noImplicitAny
不允许变量或函数参数具有隐式 any 类型。
例如:
function getName (name) {
return name;
}
如果确定为name为any, 也必须显式的制定:
function getName1 (name: any) {
return name;
}
2??、noImplicitThis
不允许 this 上下文隐式定义。
例如:
function uppercaseLabel () {
return this.label.toUpperCase()
}
const config = {
label: 'foo-config',
uppercaseLabel
}
config.uppercaseLabel();
你可以这样修改:
interface MyConfig {
label: string
uppercaseLabel: (params: void) => string
}
const config: MyConfig = {
label: 'foo-config',
uppercaseLabel () {
return this.label.toUpperCase()
}
}
3??、strictNullChecks
不允许出现null或undefined的可能性。
例如:
function getArticleById (articles: Article[], id: string) {
const article = articles.find(article => article.id === id)
return article.meta
}
function getArticleById (articles: Article[], id: string) {
const article = articles.find(article => article.id === id)
return article.meta
}
function getArticleById (articles: Article[], id: string) {
const article = articles.find(article => article.id === id)
if (typeof article === 'undefined') {
throw new Error(`Could not find an article with id: ${id}.`)
}
return article.meta
}
4??、strictPropertyInitialization
验证构造函数内部初始化前后已定义的属性。
例如:
class User {
private username: string;
}
const user = new User();
const username = user.username.toLowerCase();
class User {
private username: string | undefined;
}
const user = new User();
class User {
constructor (public username: string) {}
}
const user = new User('paul');
class User {
username! : string;
}
const user = new User();
const username = user.username.toLowerCase();
5??、strictBindCallApply
对 bind, call, apply 更严格的类型检测。
例如:
function sum (num1: number, num2: number) {
return num1 + num2
}
sum.apply(null, [1, 2, 3]);
function sum(...args: number[]) {
return args.reduce<number>((total, num) => total + num, 0)
}
sum.apply(null, [1, 2, 3])
6??、strictFunctionTypes
对函数参数进行严格逆变比较。
例如:
declare let f1: (x: Animal) => void;
declare let f2: (x: Dog) => void;
declare let f3: (x: Cat) => void;
f1 = f2;
f2 = f1;
f2 = f3;
interface Animal {
Eat(): void
}
interface Dog extends Animal{
Bark():void
}
interface Cat extends Animal{
Meow():void
}
interface Comparer<T> {
compare: (a: T, b: T) => number;
}
declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;
animalComparer = dogComparer;
dogComparer = animalComparer;
(2)、模块解析baseUrl
-- src
version1
test.ts
version2
demo.ts
test2.ts
import test2 from 'test2',需要注意的是只有我们引用的是绝对路径的时候才会使用baseUrl去解析文件
(3)、路径映射(path)
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": [
"*",
"version2/*"
]
}
}
-- src
version1
test.ts
version2
demo.ts
test2.ts
(4)、rootDir
假设我的目录结构如下:
-- src
version1
test.ts
version2
demo.ts
如果我们设置 "rootDir": "./src" ,那么我们的编译后的文件结构如下:
--build
version1
test.ts
version2
demo.ts
【注意】项目中除了 src 目录之外的其他地方不能有 ts 文件。
(5)、虚拟目录 rootDirs
{
"compilerOptions": {
"rootDirs": [
"src/version2",
"src/version3",
],
}
-- src
version1
test.ts
version2
demo.ts
test2.ts
三、配置 tsconfig 文件时常见的错误信息列表
参见:TS 中文官网配置文件之错误信息列表
【参考文献】 TypeScript 中文官网的项目配置 typeScript tsconfig配置详解 tsconfig.json配置详解 Typescript 严格模式有多严格? tsconfig.json文件各字段吐血整理
|