tsconfig常用配置解析

tsconfig常用配置解析

JS_Even_JS发布于 2019-12-27

一、TypeScript编译工具安装

我们用TypeScript开发项目的时候,编写的源文件都是以".ts"结尾的文件,而".ts"文件是无法直接被浏览器或node环境下直接运行的,所以必须进行编译,将".ts"的文件编译成".js"文件后才能直接运行。要想编译TypeScript文件,那么必须使用tsc编译工具,需要注意的是,我们并不是直接去安装tsc,而是全局安装typescript,全局安装好typescript之后,就可以在命令行工具中直接使用tsc命令了,如下图所示:

tsc.png

二、tsc编译工具的使用

首先我们可以在命令行中,输入tsc --help,即可查看tsc命令的使用帮助文档。如果直接在项目根目录下输入tsc,那么tsc会将项目下的所有".ts"文件都进行编译,编译后输出的文件与源ts文件同名、同位置。

tsc--help.png

我们可以在执行tsc命令的时候传递一些参数,进行特定的配置,如:
// 编译结果采用es5,模块形式采用commonjs,编译源文件为 ts/index.ts

tsc --target es5 --module commonjs ts/index.ts

对于简单的项目,我们可以通过给tsc命令传递一些参数进行编译,但是如果是大型的复杂项目,那么通过传递命令参数的形式进行编译就会显得心有余而力不足了,也不方便。我们可以采用配置文件的方式,来指导编译。即在项目根目录下,新建一个tsconfig.json的文件。注意,这是一个json文件,当然不一定要放在根目录下,可以通过tsc --project path/to/tsconfig.json指定配置文件。

三、使用配置文件编译ts文件

① files

由于默认情况下,tsc会编译当前项目下的所有ts文件,所以如果我们可以通过files配置来指定编译的入口文件,files的属性值为一个数组,可以指定要编译的具体文件,但是其不能使用通配符进行指定。

{
    //"files": [ // files中不能使用通配符进行配置
      //  "ts/**/*.ts"
    //]
    "files": [
        "ts/index.ts" // 对ts目录下的index.ts文件进行编译
    ]
}
如果files中配置了通配符,那么会报错 error TS6053: File '/path/to/ts-test/ts/**/*.ts' not found.,并且files的属性值不能是空数组([]),否则也会编译报错;

② include

正是由于files配置无法使用通配符进行配置,所以添加了include配置,includes配置属性值也是一个数组,但是include中可以使用通配符,并且可以和files一起使用,最终编译的源文件包含,files和include的合集,如:

{
    "files": [
        "foo/foo.ts" // 包含foo目录下的foo.ts
    ],
    "include": [ // 可以使用通配符
        "ts/**/*.ts" // 包含ts目录下的所有ts文件
    ]
}
所以最终编译的源文件包含ts目录下的所有ts文件和foo目录下的foo.ts文件。

③ exclude

我们可以通过exclude配置来排除掉include配置中包含的源文件,需要特别注意的是,exclude只对include中包含文件起到排除的作用,其无法排除files中配置的源文件。exclude的适用场景,通常为,当所有的源文件被includes进来后,而其中有一些是ts的测试文件,可以直接排除掉,如:

{
    "include": [
        "ts/**/*.ts"
    ],
    "exclude": ["node_modules", "ts/test"] // 排除对ts目录下的test目录下的测试文件的编译
}

exclude只能排除include中包含的文件,并且不是可编译文件的依赖文件,也就是说,如果include配置为可编译ts目录下的所有ts文件,那么index.ts可以编译,虽然exclude了foo.ts文件,但是index.ts依赖了foo.ts,所以foo.ts还是会被编译。

④ compilerOptions

这是一个编译选项配置,用于控制编译过程和编译结果。常用的编译选项为:

4.1 noEmitOnError

这个是用于配置,当编译源文件出现错误的时候,是否继续输出编译结果。noEmitOnError默认为false,所以即使编译的源文件中有错误,那么也会继续输出编译结果,如果noEmitOnError配置为true,那么当源文件中有错误的时候,将不再输出编译结果。

{
    "compilerOptions": {
        "noEmitOnError": true // 编译的源文件中存在错误的时候不再输出编译结果文件
    }
}

4.2 outDir

这个是用于指定编译结果的输出目录的。在不指定outDir的时候,默认是将编译结果输出文件输出到源文件所在目录下,所以可以通过outDir指定一个编译结果输出目录,那么所有的编译文件都会输出到指定的目录下,如:

{
    "compilerOptions": {
        "outDir": "./dist" // 将所有编译结果输出到dist目录下
    }
}

4.3 noImplicitAny

这个是用于控制当源文件中存在隐式的any的时候是否报错,noImplicitAny默认为false,即当源文件中存在隐式的any的时候也不报错,如果设置为true则会报错,如:

// ts/index.ts
function foo(bar) { // bar参数存在隐式any
    console.log(bar);
}
{
    "compilerOptions": {
        "noImplicitAny": true, // 当源文件中存在隐式any的时候报错
    }
}

4.4 noImplicitThis

这个是用于控制当源文件中存在this的值是any的时候是否报错,noImplicitThis默认为false,即当源文件中存在this为any的情况也不报错,如果设置为true则会报错,如:

// ts/index.ts
function foo(bar: string) {
    console.log(this.str); // 这里的this为any
}
{
     "compilerOptions": {
        "noImplicitThis": true, // 当源文件中存在this为any的时候报错
     }
}
// 改正方法为,显示指定this的类型
class Foo {
    str: string;
}
function foo(this: Foo, bar: string) { // 指定this为Foo类型的实例
    console.log(this.str);
}

4.5 target

这个是用于控制编译后输出的是什么js版本。即生成的js符合什么版本的js规范,其默认值为es3,如下:

// ts/index.ts
const str = "this is a string";
{
     "compilerOptions": {
        "target": "es6"
     }
}
// dist/index.js编译输出结果
const str = "this is a string";

4.6 lib

这个是用于指定要引入的库文件,当ts文件中使用到了一些全局的类库的时候才会配置,属性值为一个数组,有es5、es6、es7、dom四个值可选,如果不配置lib,那么其默认会引入dom库,但是如果配置了lib,那么就只会引入指定的库了。如:

// ts/index.ts
document.getElementById("#app");
{
    "compilerOptions": {
        "lib": ["es6"], // 只引入es6的库文件,不引入dom的库文件
        "target": "es6"
    }
}

由于配置了lib,那么就只会引入es6的库文件,不再引入dom相关的库文件了,所以无法使用dom相关的东西,比如document,会提示ts/index.ts:2:1 - error TS2584: Cannot find name 'document'. Do you need to change your target library? Try changing the lib compiler option to include 'dom'.

lib的配置和target也有关,target默认值为es3,所以当ts文件中使用到了Promise等全局类库的时候,就无法解析了,这个时候我们可以通过lib配置,引入es6的全局类库;当然我们也可以直接将target设置为es6,那么就可以解析Promise了。

4.7 module

这个用于指定要使用的模块标准,如果不显式配置module,那么其值与target的配置有关,其默认值为target === "es3" or "es5" ?"commonjs" : "es6",所以当target为es3或者es5的时候,module的默认值为commonjs,当target为其他的值的时候,那么module的默认值为es6。当然可以显式的指定,如:

{
    "compilerOptions": {
        "module": "commonjs", // 指定使用的模块标准
    }
}

由于模块标准与采用是es5语法还是es6语法没有关系,也就是说,模块标准可以与各种es语法相互配合,比如,es5语法也可以采用es6的模块标准,所以module专门用于配置输出结果中引入或导出模块时采用的语法标准,是采用commonjs还是node。
为了支持CommonJS和AMD的exports, TypeScript提供了export =语法。所以ts源文件使用export =语法导出的时候,module必须配置为commonjs,同时ts源文件中也必须使用import module = require("module")来导入此模块。
也就是说,如果我们希望最终使用commonjs模块标准,那么我们用export =语法导出,用import module = require("module")来导入;如果我们希望最终使用ES6模块标准,那么我们就使用ES6的模块标准进行导入导出,二者不可混用。

4.8 removeComments

这个用于指定编译输出文件中是否删除源文件中的注释,默认为false,即不删除,如果设置为true,则会在输出文件中清除所有注释。

{
    "compilerOptions": {
        "removeComments": true, // 是否在输出文件中清除源文件中的注释
    }
}

4.9 alwaysStrict

这个用于控制是否始终以严格模式检查每个模块,并且在编译后的输出结果中加入"use strict";默认为false。

{
    "compilerOptions": {
        "alwaysStrict": true, // 始终以严格模式检查每个模块,并且在编译后的结果文件中加入"use strict";
    }
}

4.10 declaration

这个用于指定是否在编译完成后生成相应的*.d.ts文件,默认为false,即不生成对应的声明文件,只有当你的代码需要给其他模块引用的时候才需要生成相应的类型声明文件,如:

{
    "compilerOptions": {
        "declaration": true, // 用于指定是否在编译完成后生成相应的*.d.ts文件
    }
}

4.11 moduleResolution

这个是用于配置模块的解析规则,主要有两种,分别为classic和node。默认值为module ==="amd" or "system" or "es6" or "es2015"?"classic" : "node",所以其默认值和module的配置有关联,由于module的默认值和target有关,而target默认值为es3,所以module的默认值commonjs,所以moduleResolution的默认值为node。这里解释一下classic和node两种解析规则的不同:


假设用户主目录下有一个ts-test的项目,里面有一个src目录,src目录下有一个a.ts文件,即/Users/**/ts-test/src/a.ts

  • classic模块解析规则:
    ① 对于相对路径模块: 只会在当前相对路径下查找是否存在该文件(.ts文件),不会作进一步的解析,如"./src/a.ts"文件中,有一行import { b } from "./b",那么其只会检测是否存在"./src/b.ts",没有就算找不到。
    ② 对于非相对路径模块: 编译器则会从包含导入文件的目录开始依次向上级目录遍历,尝试定位匹配的ts文件或者d.ts类型声明文件,如果/Users/**/ts-test/src/a.ts文件中有一行import { b } from "b",那么其查找过程如下:
/Users/**/ts-test/src/b.ts
/Users/**/ts-test/src/b.d.ts
/Users/**/ts-test/b.ts
/Users/**/ts-test/b.d.ts
/Users/**/b.ts
/Users/**/b.d.ts
/Users/b.ts
/Users/b.d.ts
/b.ts
/b.d.ts
  • node模块解析规则:
    ① 对于相对路径模块:除了会在当前相对路径下查找是否存在该文件(.ts文件)外,还会作进一步的解析,如果在相对目录下没有找到对应的.ts文件,那么就会看一下是否存在同名的目录,如果有,那么再看一下里面是否有package.json文件,然后看里面有没有配置,main属性,如果配置了,则加载main所指向的文件(.ts或者.d.ts),如果没有配置main属性,那么就会看一下目录里有没有index.ts或者index.d.ts,有则加载。
    ② 对于非相对路径模块: 对于非相对路径模块,那么会直接到a.ts所在目录下的node_modules目录下去查找,也是遵循逐层遍历的规则,查找规则同上,同上node模块解析规则查找如下:
/Users/**/ts-test/src/node_modules/b.ts
/Users/**/ts-test/src/node_modules/b.d.ts
/Users/**/ts-test/src/node_modules/b/package.json(如果指定了main)
/Users/**/ts-test/src/node_modules/b/index.ts
/Users/**/ts-test/src/node_modules/b/index.d.ts

/Users/**/ts-test/node_modules/b.ts
/Users/**/ts-test/node_modules/b.d.ts
/Users/**/ts-test/node_modules/b/package.json(如果指定了main)
/Users/**/ts-test/node_modules/index.ts
/Users/**/ts-test/node_modules/index.d.ts

/Users/**/node_modules/b.ts
/Users/**/node_modules/b.d.ts
/Users/**/node_modules/b/package.json(如果指定了main)
/Users/**/node_modules/index.ts
/Users/**/node_modules/index.d.ts

/Users/node_modules/b.ts
/Users/node_modules/b.d.ts
/Users/node_modules/b/package.json(如果指定了main)
/Users/node_modules/index.ts
/Users/node_modules/index.d.ts

/node_modules/b.ts
/node_modules/b.d.ts
/node_modules/b/package.json(如果指定了main)
/node_modules/index.ts
/node_modules/index.d.ts

4.12 baseUrl

这个是用于拓宽引入非相对模块时的查找路径的。其默认值就是"./",比如当moduleResolution属性值为node的时候,如果我们引入了一个非相对模块,那么编译器只会到node_modules目录下去查找,但是如果配置了baseUrl,那么编译器在node_modules中没有找到的情况下,还会到baseUrl中指定的目录下查找;同样moduleResolution属性值为classic的时候也是一样,除了到当前目录下找之外(逐层),如果没有找到还会到baseUrl中指定的目录下查找;就是相当于拓宽了非相对模块的查找路径范围。

{
    "compilerOptions": {
        "moduleResolution": "node",
        "baseUrl": "./typings" // 配合moduleResolution解析方式,如果没有找到,则会再到当前配置所在目录下的typings目录下查找
    }
}

需要注意的时候,如果把baseUrl配置成默认值"./",那么将不会起作用,相当于没有配置,将按node和classic的解析规则进行解析,此时必须配合下面的paths配置才会生效,并且既可以找.ts文件,也可以找.d.ts类型声明文件。

4.13 paths

这个是配合baseUrl一起使用的,因为其是相对于baseUrl所在的路径的,主要用于到baseUrl所在目录下查找的时候进行的路径映射。如:

// projectRoot/src/index.ts
import foo from "foo";
{
    "compilerOptions": {
        "baseUrl": "./typings",
        "paths": { // 路径映射,相对于baseUrl
            "foo": ["node_modules/foo"]
        }
    }
}
如果没有配置paths(没有配置paths属性或者paths属性值为{}),那么当引入的非相对模块找不到的情况下,这里以classic模块解析规则为例,编译器会到./typings目录下去查找有没有foo.ts或者foo.d.ts,但是如果配置了paths(至少配置了一项),那么编译器就不会到./typings目录下去查找有没有foo.ts或foo.d.ts了,即使./typings目录下有foo.ts或foo.d.ts也无法找到,因为其只会到baseUrl/映射路径下查找,即./typings/node_modules/foo.ts或者./typings/node_modules/foo.d.ts,如果是node的解析规则,那么foo就可以是文件夹了,主要取决于模块解析规则。

4.14 typeRoots

这个用于指定类型声明文件的查找路径。默认值为node_modules/@types,即在node_modules下的@types里面查找。需要注意的是这里仅仅是d.ts文件的查找路径。同样,这个也是相当于在引入非相对模块的时候拓宽了类型声明文件的查找范围,其实就是配置类型声明文件的查找目录,如:

// projectRoot/src/index.ts
import foo from "foo";
{
    "compilerOptions": {
        "typeRoots": [
            "node_modules/@types", // 默认值
            "./typings"
        ]
    }
}

在其他情况都找不到foo模块的时候,编译器还会到项目根目录下的typings目录下去查找有没有foo目录里面是否有一个index.d.ts类型声明文件,并且只能识别目录下的.d.ts文件,不能识别.ts文件。


不管typeRoots怎么配置,编译器都会到node_modules/@types下查找类型配置文件,并且不管是classic解析还是node解析,都会到node_modules/@types目录下查找类型声明文件,即typeRoots和types的配置与模块的解析规则无关。

4.15 types

这个需要配合typeRoots来使用,用于指定需要包含的模块,只有在这里列出的模块的声明文件才会被加载进来,其属性值为一个数组,如果将types设置为一个空的数组,那么typeRoots配置的目录里的声明文件都将不会被加载进来,比如此时如果源文件中使用到了node的内置模块,将会编译失败,如:

// ts/index.ts
import http = require("http"); // 引入了node里的http模块
console.log(http);
{
    "compilerOptions": {
        "typeRoots": [
            "node_modules/@types" // 默认值
        ],
        "types": ["node"], // 将@types/node里的类型声明文件引入进来
    }
}

// 加载自己的类型声明文件

{
     "compilerOptions": {
         "typeRoots": [
            "./typings"
         ],
         "types": ["foo"]
     }
}
可以到./typings目录下查找是否有foo目录,并且foo目录下是否有index.d.ts

再比如,源码中存在import foo from "foo",因为编译器在node解析规则的情况下,既会到node_modules下面找也会到node_modules/@types下面找,但是如果这两个目录下都没有找到foo模块的定义,那么编译器会看一下typeRoots和types的配置,如果node_modules/@types目录下存在一个bar目录,并且bar目录里面有一个index.d.ts和foo.d.ts,具体配置如下:
// node_modules/@types/bar/foo.d.ts
declare class Foo {

}
declare module "foo" {
    export default Foo;
}
// node_modules/@types/bar/index.d.ts
/// <reference path="foo.d.ts" />
{
    "types": ["bar"] // 引入bar模块的定义
}
由于源码中引入的是foo模块,但是node_modules/@types目录下并没有foo模块的定义,但是有bar模块的定义,但是bar模块中有foo模块的定义,那么就可以通过"types"配置,引入整个bar模块的定义,因为bar模块中引入了foo模块的定义,所以foo模块的定义也会被加载进来,当然不配置types,那么node_modules/@types目录下的所有模块的定义都会加载进来,所以types的配置主要为了一次性加载某个包含了很多类型声明的模块。

如果node_modules和node_modules/@type里找不到foo模块的定义,通过typeRoots和types的配置也找不到,那么编译器就会根据baseUrl的配置,进一步查找。所以非相对模块的查找顺序为,根据moduleResolution的配置,确定是使用node还是classic模块进行基础解析,如果找不到,则查看typeRoots和types的配置,如果还是找不到,则查看baseUrl和paths的配置,需要注意的是typeRoots和types的配置只能是查找.d.ts类型声明文件,如果还是找不到,那么就在编译入口所在目录下查找有没有对应模块的定义了,后面会具体讲解。

四、其他配置项查询

其他配置项查询
.d.ts文件的查找范围为,只要该.d.ts文件在编译入口范围之内,那么其中的定义都可以被识别,并且类型声明文件的名称可自定义。

{
    "includes": ["./**/*.ts"],
     "exclude": ["./node_modules", "test", "hack"]
}

如上配置,那么项目根目录下的类型声明文件,除了在test和hack目录下的都能被识别。因为其编译入口范围为,项目根目录下的所有.ts文件,但是排除了test和hack目录。

五、总结

tsconfig的配置主要分两块,编译入口和编译选项。编译入口主要通过files、includes、excludes进行控制,files只能配置具体的文件,不能使用通配符,includes和excludes可以使用通配符,并且excludes只能排除includes中的文件。
编译选项这一块,主要需要理清引入非相对模块时的查找方式和顺序,通过moduleResolution确定模块的解析规则,引入相对先按是classic还是node解析规则进行查找,如果查找不到,那么编译器提供了两种方式来拓宽查找范围,一种是typeRoots和types,另一种是baseUrl和paths,首先到typeRoot和types包含的目录中查找(只能查找类型声明文件),typeRoot和types的配置和模块的解析规则无关,只有node_modules目录下查找才和模块的解析规则有关,如果还是找不到,再到baseUrl和paths包含的目录中查找。

阅读 16.5k更新于 2020-01-20
 

posted on 2022-05-28 17:59  漫思  阅读(239)  评论(0编辑  收藏  举报

导航