理解 Typescript 配置文件

熟悉 Typescript 配置选项是 TS 项目开发的最基本要求。
TS 使用 tsconfig.json 作为其配置文件,它主要包含两块内容:

  1. 指定待编译的文件
  2. 定义编译选项

另外,一般来说,tsconfig.json 文件所处的路径就是当前 TS 项目的根路径

基本用法

TS 的编译命令为 tsc ,当我们在命令行中直接输入 tsc 时,会打印出如下的使用说明:

$ tsc
Version 2.7.2
Syntax:   tsc [options] [file ...]
Examples: tsc hello.ts
          tsc --outFile file.js file.ts
          tsc @args.txt

Options:
-h, --help                                         Print this message.
--all                                              Show all compiler options.
-v, --version                                      Print the compiler's version.
...

如果仅仅是编译少量的文件,我们可以直接使用 tsc ,通过其选项来设置编译配置,如:

tsc --outFile file.js --target es3 --module commonjs file.ts

但如果是编译整个项目的话,最推荐的做法是使用 tsconfig.json 文件,这样就不用每次编译时都还得手动敲配置,而且也便于团队协作。

以下是让 tsc 使用 tsconfig.json 的两种方式:

  • 不显式指定 tsconfig.json ,此时,编译器会从当前路径开始寻找 tsconfig.json 文件,如果没有找到,则继续往上级路径逐步寻找,直到找到为止
  • 通过 --project (或缩写 -p )指定一个包含 tsconfig.json 的路径,或者包含配置信息的 .json 文件路径

注意,tsc 的命令行选项具有优先级,会覆盖 tsconfig.json 中的同名选项。

使用示例

下面是一个简单的配置示例:

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "sourceMap": true
  },
  "files": [
    "app.ts",
    "foo.ts",
  ]
}

其中,compilerOptions 用来配置编译选项,files 用来指定待编译文件。
这里的待编译文件是指入口文件,任何被入口文件依赖的文件,比如 foo.ts 依赖 bar.ts ,那这里并不需要写上 bar.ts ,编译器会自动把所有的依赖文件纳为编译对象。

也可以使用 includeexclude 来指定和排除待编译文件:

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "sourceMap": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

所以,总结一下,指定待编译文件有两种方式:

  • 使用 files 属性
  • 使用 includeexclude 属性

开发者可以按照自己的喜好使用其中任意一种。但它们不是互斥的,在某些情况下两者搭配起来使用效果更佳。

配置说明

文件指定

files 属性是一个数组,数组元素可以是相对文件路径和绝对文件路径。

includeexclude 属性也是一个数组,但数组元素是类似 glob 的文件模式。它支持的 glob 通配符包括:

  • * :匹配 0 或多个字符(注意:不含路径分隔符)
  • ? :匹配任意单个字符(注意:不含路径分隔符)
  • **/ :递归匹配任何子路径

在继续说明之前,有必要先了解下在编译器眼里什么样的文件才算是 TS 文件
TS 文件指拓展名为 .ts.tsx.d.ts 的文件。如果开启了 allowJs 选项,那 .js.jsx 文件也属于 TS 文件。

如果仅仅包含一个 * 或者 .* ,那么只有TS 文件才会被包含。

如果 filesinclude 都未设置,那么除了 exclude 排除的文件,编译器会默认包含路径下的所有 TS 文件

如果同时设置 filesinclude ,那么编译器会把两者指定的文件都引入。

如果未设置 exclude ,那其默认值为 node_modulesbower_componentsjspm_packages 和编译选项 outDir 指定的路径。

exclude 只对 include 有效,对 files 无效。即 files 指定的文件如果同时被 exclude 排除,那么该文件仍然会被编译器引入。

前面提到,任何被 filesinclude 引入的文件的依赖会被自动引入。
反过来,如果 B.tsA.ts 依赖,那么 B.ts 不能被 exclude 排除,除非 A.ts 也被排除了。

有一点要注意的是,编译器不会引入疑似为输出的文件。比如,如果引入的文件中包含 index.ts ,那么 index.d.tsindex.js 就会被排除。通常来说,只有拓展名不一样的文件命名法是不推荐的。

tsconfig.json 也可以为空文件,这种情况下会使用默认的编译选项来编译所有默认引入的文件。

编译选项

常用选项

选项字段类型默认值说明
allowJsbooleanfalse允许编译 JS 文件
checkJsbooleanfalse报告 JS 文件中存在的类型错误需要配合 allowJs 使用
declarationbooleanfalse生成对应的 .d.ts 文件
declarationDirstring-生成的 .d.ts 文件存放路径默认与 .ts 文件相同
experimentalDecoratorsbooleanfalse启用实验功能-ES 装饰器
jsxstringPreserve.tsx 中支持 JSX :ReactPreserve详细说明
jsxFactorystringReact.createElementjsx 设置为 React 时使用的创建函数
libstring[]-编译时引入的 ES 功能库,包括:es5es6es7dom 等。如果未设置,则默认为: targetes5 时: ["dom", "es5", "scripthost"] targetes6 时: ["dom", "es6", "dom.iterable", "scripthost"]
modulestringtarget === "es3" or "es5" ?"commonjs" : "es6"生成的模块形式:nonecommonjsamdsystemumdes6es2015esnext 只有 amdsystem 能和 outFile 一起使用 targetes5 或更低时可用 es6es2015
moduleResolutionstringmodule === "amd" or "system" or "es6" ? "classic" : "node"模块解析方式,详细说明
noImplicitAnybooleanfalse存在隐式 any 时抛错
noImplicitReturnsbooleanfalse不存在 return 时抛错
noImplicitThisbooleanfalsethis 可能为 any 时抛错
outDirstring-编译生成的文件存放路径默认与 .ts 文件相同
sourceMapbooleanfalse生成 .map 文件
targetstringes3生成 .js 文件版本
附:官方完整的编译选项列表

类型相关

类型相关的选项包括 typeRootstypes

有一个普遍的误解,以为这两个选项适用于所有的类型声明文件,包括用户自定义的声明文件。其实不然。
这两个选项只对通过 npm 安装的声明模块有效,用户自定义的类型声明文件与它们没有任何关系

声明模块通常会包含一个 index.d.ts 文件,或者其 package.json 设置了 types 字段。

默认的,所有位于 node_modules/@types 路径下的模块都会引入到编译器。
具体来说是,./node_modules/@types../node_modules/@types../../node_modules/@types 等等。

typeRoots 用来指定默认的类型声明文件查找路径,默认为 node_modules/@types 。比如:

{
  "compilerOptions": {
    "typeRoots": ["./typings"]
  }
}

上面的配置会自动引入 ./typings 下的所有 TS 类型声明模块,而不是 ./node_modules/@types 下的模块。

如果不希望自动引入 typeRoots 指定路径下的所有声明模块,那可以使用 types 指定自动引入哪些模块。比如:

{
  "compilerOptions": {
    "types" : ["node", "lodash", "express"]
  }
}

只会引入 nodelodashexpress 三个声明模块,其它的声明模块则不会被自动引入。
如果 types 被设置为 [] ,那么将不会自动引入任何声明模块。此时,如果想使用声明模块,只能在代码中手动引入了。

请记住,自动引入只对包含全局声明的模块有效。比如 jQuery ,我们不用手动 import 或者 ///<reference/> 即可在任何文件中使用 $ 的类型。再比如,对于 import 'foo' ,编译器会分别在 node_modulesnode_modules/@types 文件下查找 foo 模块和声明模块。

基于此,如果想让自定义声明的类型不需要手动引入就可以在任何地方使用,可以将其声明为全局声明 global ,然后让 files 或者 include 包含即可。
比如:

declare global {
  const graphql: (query: TemplateStringsArray) => void;
  namespace Gatsby {
    interface ComponentProps {
      children: () => React.ReactNode,
      data: RootQueryType
    }
  }
}

这样的话,就可以在任何地方直接使用 graphqlGatsby 对应的类型了。

配置复用

可以使用 extends 来实现配置复用,即一个配置文件可以继承另一个文件的配置属性。

比如,建立一个基础的配置文件 configs/base.json

{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

然后,tsconfig.json 就可以引用这个文件的配置了:

{
  "extends": "./configs/base",
  "files": [
    "main.ts",
    "supplemental.ts"
  ]
}

这种继承有两种特点:

  • 继承者中的同名配置会覆盖被继承者
  • 所有相对路径都被解析为其所在文件的路径

参考资料