tsconfig.json
简介 #
tsconfig.json
是 TypeScript 项目的配置文件,放在项目的根目录。反过来说,如果一个目录里面有tsconfig.json
,TypeScript 就认为这是项目的根目录。
如果项目源码是 JavaScript,但是想用 TypeScript 处理,那么配置文件的名字是jsconfig.json
,它跟tsconfig
的写法是一样的。
tsconfig.json
文件主要供tsc
编译器使用,它的命令行参数--project
或-p
可以指定tsconfig.json
的位置(目录或文件皆可)。
$ tsc -p ./dir
如果不指定配置文件的位置,tsc
就会在当前目录下搜索tsconfig.json
文件,如果不存在,就到上一级目录搜索,直到找到为止。
tsconfig.json
文件的格式,是一个 JSON 对象,最简单的情况可以只放置一个空对象{}
。下面是一个示例。
{
"compilerOptions": {
"outDir": "./built",
"allowJs": true,
"target": "es5"
},
"include": ["./src/**/*"]
}
本章后面会详细介绍tsconfig.json
的各个属性,这里简单说一下,上面示例的四个属性的含义。
- include:指定哪些文件需要编译。
- allowJs:指定源目录的 JavaScript 文件是否原样拷贝到编译后的目录。
- outDir:指定编译产物存放的目录。
- target:指定编译产物的 JS 版本。
tsconfig.json
文件可以不必手写,使用 tsc 命令的--init
参数自动生成。
$ tsc --init
上面命令生成的tsconfig.json
文件,里面会有一些默认配置。
你也可以使用别人预先写好的 tsconfig.json 文件,npm 的@tsconfig
名称空间下面有很多模块,都是写好的tsconfig.json
样本,比如 @tsconfig/recommended
和@tsconfig/node16
。
这些模块需要安装,以@tsconfig/deno
为例。
$ npm install --save-dev @tsconfig/deno
# 或者
$ yarn add --dev @tsconfig/deno
安装以后,就可以在tsconfig.json
里面引用这个模块,相当于继承它的设置,然后进行扩展。
{
"extends": "@tsconfig/deno/tsconfig.json"
}
@tsconfig
空间下包含的完整 tsconfig 文件目录,可以查看 GitHub。
tsconfig.json
的一级属性并不多,只有很少几个,但是compilerOptions
属性有很多二级属性。下面先逐一介绍一级属性,然后再介绍compilerOptions
的二级属性,按照首字母排序。
exclude #
exclude
属性是一个数组,必须与include
属性一起使用,用来从编译列表中去除指定的文件。它也支持使用与include
属性相同的通配符。
{
"include": ["**/*"],
"exclude": ["**/*.spec.ts"]
}
extends #
tsconfig.json
可以继承另一个tsconfig.json
文件的配置。如果一个项目有多个配置,可以把共同的配置写成tsconfig.base.json
,其他的配置文件继承该文件,这样便于维护和修改。
extends
属性用来指定所要继承的配置文件。它可以是本地文件。
{
"extends": "../tsconfig.base.json"
}
如果extends
属性指定的路径不是以./
或../
开头,那么编译器将在node_modules
目录下查找指定的配置文件。
extends
属性也可以继承已发布的 npm 模块里面的 tsconfig 文件。
{
"extends": "@tsconfig/node12/tsconfig.json"
}
extends
指定的tsconfig.json
会先加载,然后加载当前的tsconfig.json
。如果两者有重名的属性,后者会覆盖前者。
files #
files
属性指定编译的文件列表,如果其中有一个文件不存在,就会报错。
它是一个数组,排在前面的文件先编译。
{
"files": ["a.ts", "b.ts"]
}
该属性必须逐一列出文件,不支持文件匹配。如果文件较多,建议使用include
和exclude
属性。
include #
include
属性指定所要编译的文件列表,既支持逐一列出文件,也支持通配符。文件位置相对于当前配置文件而定。
{
"include": ["src/**/*", "tests/**/*"]
}
include
属性支持三种通配符。
?
:指代单个字符*
:指代任意字符,不含路径分隔符**
:指定任意目录层级。
如果不指定文件后缀名,默认包括.ts
、.tsx
和.d.ts
文件。如果打开了allowJs
,那么还包括.js
和.jsx
。
references #
references
属性是一个数组,数组成员为对象,适合一个大项目由许多小项目构成的情况,用来设置需要引用的底层项目。
{
"references": [
{ "path": "../pkg1" },
{ "path": "../pkg2/tsconfig.json" }
]
}
references
数组成员对象的path
属性,既可以是含有文件tsconfig.json
的目录,也可以直接是该文件。
与此同时,引用的底层项目的tsconfig.json
必须启用composite
属性。
{
"compilerOptions": {
"composite": true
}
}
compilerOptions #
compilerOptions
属性用来定制编译行为。这个属性可以省略,这时编译器将使用默认设置。
allowJs #
allowJs
允许 TypeScript 项目加载 JS 脚本。编译时,也会将 JS 文件,一起拷贝到输出目录。
{
"compilerOptions": {
"allowJs": true
}
}
alwaysStrict #
alwaysStrict
确保脚本以 ECMAScript 严格模式进行解析,因此脚本头部不用写"use strict"
。它的值是一个布尔值,默认为true
。
allowSyntheticDefaultImports #
allowSyntheticDefaultImports
允许import
命令默认加载没有default
输出的模块。
比如,打开这个设置,就可以写import React from "react";
,而不是import * as React from "react";
。
allowUnreachableCode #
allowUnreachableCode
设置是否允许存在不可能执行到的代码。它的值有三种可能。
undefined
: 默认值,编辑器显示警告。true
:忽略不可能执行到的代码。false
:编译器报错。
allowUnusedLabels #
allowUnusedLabels
设置是否允许存在没有用到的代码标签(label)。它的值有三种可能。
undefined
: 默认值,编辑器显示警告。true
:忽略没有用到的代码标签。false
:编译器报错。
baseUrl #
baseUrl
的值为字符串,指定 TypeScript 项目的基准目录。
由于默认是以 tsconfig.json 的位置作为基准目录,所以一般情况不需要使用该属性。
{
"compilerOptions": {
"baseUrl": "./"
}
}
上面示例中,baseUrl
为当前目录./
。那么,当遇到下面的语句,TypeScript 将以./
为起点,寻找hello/world.ts
。
import { helloWorld } from "hello/world";
checkJs #
checkJS
设置对 JS 文件同样进行类型检查。打开这个属性,也会自动打开allowJs
。它等同于在 JS 脚本的头部添加// @ts-check
命令。
{
"compilerOptions":{
"checkJs": true
}
}
composite #
composite
打开某些设置,使得 TypeScript 项目可以进行增量构建,往往跟incremental
属性配合使用。
declaration #
declaration
设置编译时是否为每个脚本生成类型声明文件.d.ts
。
{
"compilerOptions": {
"declaration": true
}
}
declarationDir #
declarationDir
设置生成的.d.ts
文件所在的目录。
{
"compilerOptions": {
"declaration": true,
"declarationDir": "./types"
}
}
declarationMap #
declarationMap
设置生成.d.ts
类型声明文件的同时,还会生成对应的 Source Map 文件。
{
"compilerOptions": {
"declaration": true,
"declarationMap": true
}
}
emitBOM #
emitBOM
设置是否在编译结果的文件头添加字节顺序标志 BOM,默认值是false
。
emitDeclarationOnly #
emitDeclarationOnly
设置编译后只生成.d.ts
文件,不生成.js
文件。
esModuleInterop #
esModuleInterop
修复了一些 CommonJS 和 ES6 模块之间的兼容性问题。
如果module
属性为node16
或nodenext
,则esModuleInterop
默认为true
,其他情况默认为false
。
打开这个属性,使用import
命令加载 CommonJS 模块时,TypeScript 会严格检查兼容性问题是否存在。
import * as moment from 'moment'
moment(); // 报错
上面示例中,根据 ES6 规范,import * as moment
里面的moment
是一个对象,不能当作函数调用,所以第二行报错了。
解决方法就是改写上面的语句,改成加载默认接口。
import moment from 'moment'
moment(); // 不报错
打开esModuleInterop
以后,如果将上面的代码编译成 CommonJS 模块格式,就会加入一些辅助函数,保证编译后的代码行为正确。
注意,打开esModuleInterop
,将自动打开allowSyntheticDefaultImports
。
exactOptionalPropertyTypes #
exactOptionalPropertyTypes
设置可选属性不能赋值为undefined
。
// 打开 exactOptionalPropertyTypes
interface MyObj {
foo?: 'A' | 'B';
}
let obj:MyObj = { foo: 'A' };
obj.foo = undefined; // 报错
上面示例中,foo
是可选属性,打开exactOptionalPropertyTypes
以后,该属性就不能显式赋值为undefined
。
forceConsistentCasingInFileNames #
forceConsistentCasingInFileNames
设置文件名是否为大小写敏感,默认为true
。
incremental #
incremental
让 TypeScript 项目构建时产生文件tsbuildinfo
,从而完成增量构建。
inlineSourceMap #
inlineSourceMap
设置将 SourceMap 文件写入编译后的 JS 文件中,否则会单独生成一个.js.map
文件。
inlineSources #
inlineSources
设置将原始的.ts
代码嵌入编译后的 JS 中。
它要求sourceMap
或inlineSourceMap
至少打开一个。
isolatedModules #
isolatedModules
设置如果当前 TypeScript 脚本作为单个模块编译,是否会因为缺少其他脚本的类型信息而报错,主要便于非官方的编译工具(比如 Babel)正确编译单个脚本。
jsx #
jsx
设置如何处理.tsx
文件。它可以取以下五个值。
preserve
:保持 jsx 语法不变,输出的文件名为.jsx
。react
:将<div />
编译成React.createElement("div")
,输出的文件名为.js
。react-native
:保持 jsx 语法不变,输出的文件后缀名为.js
。react-jsx
:将<div />
编译成_jsx("div")
,输出的文件名为.js
。react-jsxdev
:跟react-jsx
类似,但是为_jsx()
加上更多的开发调试项,输出的文件名为.js
。
{
"compilerOptions": {
"jsx": "preserve"
}
}
lib #
lib
值是一个数组,描述项目需要加载的 TypeScript 内置类型描述文件,跟三斜线指令/// <reference lib="" />
作用相同。
{
"compilerOptions": {
"lib": ["dom", "es2021"]
}
}
TypeScript 内置的类型描述文件,主要有以下一些,完整的清单可以参考 TypeScript 源码。
- ES5
- ES2015
- ES6
- ES2016
- ES7
- ES2017
- ES2018
- ES2019
- ES2020
- ES2021
- ES2022
- ESNext
- DOM
- WebWorker
- ScriptHost
listEmittedFiles #
listEmittedFiles
设置编译时在终端显示,生成了哪些文件。
{
"compilerOptions": {
"listEmittedFiles": true
}
}
listFiles #
listFiles
设置编译时在终端显示,参与本次编译的文件列表。
{
"compilerOptions": {
"listFiles": true
}
}
mapRoot #
mapRoot
指定 SourceMap 文件的位置,而不是默认的生成位置。
{
"compilerOptions": {
"sourceMap": true,
"mapRoot": "https://my-website.com/debug/sourcemaps/"
}
}
module #
module
指定编译产物的模块格式。它的默认值与target
属性有关,如果target
是ES3
或ES5
,它的默认值是commonjs
,否则就是ES6/ES2015
。
{
"compilerOptions": {
"module": "commonjs"
}
}
它可以取以下值:none、commonjs、amd、umd、system、es6/es2015、es2020、es2022、esnext、node16、nodenext。
moduleResolution #
moduleResolution
确定模块路径的算法,即如何查找模块。它可以取以下四种值。
node
:采用 Node.js 的 CommonJS 模块算法。node16
或nodenext
:采用 Node.js 的 ECMAScript 模块算法,从 TypeScript 4.7 开始支持。classic
:TypeScript 1.6 之前的算法,新项目不建议使用。bundler
:TypeScript 5.0 新增的选项,表示当前代码会被其他打包器(比如 Webpack、Vite、esbuild、Parcel、rollup、swc)处理,从而放宽加载规则,它要求module
设为es2015
或更高版本,详见加入该功能的 PR 说明。
它的默认值与module
属性有关,如果module
为AMD
、UMD
、System
或ES6/ES2015
,默认值为classic
;如果module
为node16
或nodenext
,默认值为这两个值;其他情况下,默认值为Node
。
moduleSuffixes #
moduleSuffixes
指定模块的后缀名。
{
"compilerOptions": {
"moduleSuffixes": [".ios", ".native", ""]
}
}
上面的设置使得 TypeScript 对于语句import * as foo from "./foo";
,会搜索以下脚本./foo.ios.ts
、./foo.native.ts
和./foo.ts
。
newLine #
newLine
设置换行符为CRLF
(Windows)还是LF
(Linux)。
noEmit #
noEmit
设置是否产生编译结果。如果不生成,TypeScript 编译就纯粹作为类型检查了。
noEmitHelpers #
noEmitHelpers
设置在编译结果文件不插入 TypeScript 辅助函数,而是通过外部引入辅助函数来解决,比如 NPM 模块tslib
。
noEmitOnError #
noEmitOnError
指定一旦编译报错,就不生成编译产物,默认为false
。
noFallthroughCasesInSwitch #
noFallthroughCasesInSwitch
设置是否对没有break
语句(或者return
和throw
语句)的 switch 分支报错,即case
代码里面必须有终结语句(比如break
)。
noImplicitAny #
noImplicitAny
设置当一个表达式没有明确的类型描述、且编译器无法推断出具体类型时,是否允许将它推断为any
类型。
它是一个布尔值,默认为true
,即只要推断出any
类型就报错。
noImplicitReturns #
noImplicitReturns
设置是否要求函数任何情况下都必须返回一个值,即函数必须有return
语句。
noImplicitThis #
noImplicitThis
设置如果this
被推断为any
类型是否报错。
noUnusedLocals #
noUnusedLocals
设置是否允许未使用的局部变量。
noUnusedParameters #
noUnusedParameters
设置是否允许未使用的函数参数。
outDir #
outDir
指定编译产物的存放目录。如果不指定,编译出来的.js
文件存放在对应的.ts
文件的相同位置。
outFile #
outFile
设置将所有非模块的全局文件,编译在同一个文件里面。它只有在module
属性为None
、System
、AMD
时才生效,并且不能用来打包 CommonJS 或 ES6 模块。
paths #
paths
设置模块名和模块路径的映射,也就是 TypeScript 如何导入require
或imports
语句加载的模块。
paths
基于baseUrl
进行加载,所以必须同时设置后者。
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"b": ["bar/b"]
}
}
}
上面示例中,paths 设置的是执行require('b')
时,即加载的是./bar/b
。
它还可以使用通配符“*”,表明模块名与模块位置的对应关系。
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@bar/*": ["bar/*"]
}
}
}
preserveConstEnums #
preserveConstEnums
将const enum
结构保留下来,不替换成常量值。
{
"compilerOptions": {
"preserveConstEnums": true
}
}
pretty #
pretty
设置美化输出终端的编译信息,默认为true
。
removeComments #
removeComments
移除 TypeScript 脚本里面的注释,默认为false
。
resolveJsonModule #
resolveJsonModule
允许 import 命令导入 JSON 文件。
rootDir #
rootDir
设置源码脚本所在的目录,主要跟编译后的脚本结构有关。rootDir
对应目录下的所有脚本,会成为输出目录里面的顶层脚本。
rootDirs #
rootDirs
把多个不同目录,合并成一个虚拟目录,便于模块定位。
{
"compilerOptions": {
"rootDirs": ["bar", "foo"]
}
}
上面示例中,rootDirs
将bar
和foo
组成一个虚拟目录。
sourceMap #
sourceMap
设置编译时是否生成 SourceMap 文件。
sourceRoot #
sourceRoot
在 SourceMap 里面设置 TypeScript 源文件的位置。
{
"compilerOptions": {
"sourceMap": true,
"sourceRoot": "https://my-website.com/debug/source/"
}
}
strict #
strict
用来打开 TypeScript 的严格检查。它的值是一个布尔值,默认是关闭的。
{
"compilerOptions": {
"strict": true
}
}
这个设置相当于同时打开以下的一系列设置。
- alwaysStrict
- strictNullChecks
- strictBindCallApply
- strictFunctionTypes
- strictPropertyInitialization
- noImplicitAny
- noImplicitThis
- useUnknownInCatchVariables
打开strict
的时候,允许单独关闭其中一项。
{
"compilerOptions": {
"strict": true,
"alwaysStrict": false
}
}
strictBindCallApply #
strictBindCallApply
设置是否对函数的call()
、bind()
、apply()
这三个方法进行类型检查。
如果不打开strictBindCallApply
编译选项,编译器不会对以上三个方法进行类型检查,参数类型都是any
,传入任何参数都不会产生编译错误。
function fn(x: string) {
return parseInt(x);
}
// strictBindCallApply:false
const n = fn.call(undefined, false);
// 以上不报错
strictFunctionTypes #
strictFunctionTypes
允许对函数更严格的参数检查。具体来说,如果函数 B 的参数是函数 A 参数的子类型,那么函数 B 不能替代函数 A。
function fn(x:string) {
console.log('Hello, ' + x.toLowerCase());
}
type StringOrNumberFunc = (ns:string|number) => void;
// 打开 strictFunctionTypes,下面代码会报错
let func:StringOrNumberFunc = fn;
上面示例中,函数fn()
的参数是StringOrNumberFunc
参数的子集,因此fn
不能替代StringOrNumberFunc
。
strictNullChecks #
不打开strictNullChecks
的情况下,一个变量不管类型是什么,都可以赋值为undefined
或null
。
// 不打开 strictNullChecks 的情况
let x:number;
x = undefined; // 不报错
x = null; // 不报错
上面示例中,不打开strictNullChecks
时,变量x
的类型是number
,但是赋值为undefined
或null
都不会报错。这是为了继承 JavaScript 的设定:当变量没有赋值时,它的值就为undefined
。
一旦打开strictNullChecks
,就使用严格类型,禁止变量赋值为undefined
和null
,除非变量原本就是这两种类型。它相当于从变量的值里面,排除了undefined
和null
。
// 打开 strictNullChecks 的情况
let x:number;
x = undefined; // 报错
x = null; // 报错
上面示例中,打开strictNullChecks
时,变量x
作为number
类型,就不能赋值为undefined
和null
。
下面是一个例子。
// 打开 strickNullChecks 时,类型 A 为 number
// 不打开时,类型 A 为 string
type A = unknown extends {} ? string : number;
上面示例中,{}
代表了 Object 类型,不打开strictNullChecks
时,它包括了undefined
和null
,就相当于包括了所有类型的值,所以这时unknown
类型可以赋值给{}
类型,类型A
就为string
。打开strictNullChecks
时,{}
就排除掉了undefined
和null
,这时unknown
类型就不能赋值给{}
类型后,类型A
就为number
。
最后,strict
属性包含了strictNullChecks
,如果打开strict
属性,就相当于打开了strictNullChecks
。
strictPropertyInitialization #
strictPropertyInitialization
设置类的实例属性都必须初始化,包括以下几种情况。
- 设为
undefined
类型 - 显式初始化
- 构造函数中赋值
注意,使用该属性的同时,必须打开strictNullChecks
。
// strictPropertyInitialization:true
class User {
// 报错,属性 username 没有初始化
username: string;
}
// 解决方法一
class User {
username = '张三';
}
// 解决方法二
class User {
username:string|undefined;
}
// 解决方法三
class User {
username:string;
constructor(username:string) {
this.username = username;
}
}
// 或者
class User {
constructor(public username:string) {}
}
// 解决方法四:赋值断言
class User {
username!:string;
constructor(username:string) {
this.initialize(username);
}
private initialize(username:string) {
this.username = username;
}
}
suppressExcessPropertyErrors #
suppressExcessPropertyErrors
关闭对象字面量的多余参数的报错。
target #
target
指定编译出来的 JavaScript 代码的 ECMAScript 版本,比如es2021
,默认是es3
。
它可以取以下值。
- es3
- es5
- es6/es2015
- es2016
- es2017
- es2018
- es2019
- es2020
- es2021
- es2022
- esnext
注意,如果编译的目标版本过老,比如"target": "es3"
,有些语法可能无法编译,tsc
命令会报错。
traceResolution #
traceResolution
设置编译时,在终端输出模块解析的具体步骤。
{
"compilerOptions": {
"traceResolution": true
}
}
typeRoots #
typeRoots
设置类型模块所在的目录,默认是node_modules/@types
,该目录里面的模块会自动加入编译。一旦指定了该属性,就不会再用默认值node_modules/@types
里面的类型模块。
该属性的值是一个数组,数组的每个成员就是一个目录,它们的路径是相对于tsconfig.json
位置。
{
"compilerOptions": {
"typeRoots": ["./typings", "./vendor/types"]
}
}
types #
默认情况下,typeRoots
目录下所有模块都会自动加入编译,如果指定了types
属性,那么只有其中列出的模块才会自动加入编译。
{
"compilerOptions": {
"types": ["node", "jest", "express"]
}
}
上面的设置表示,默认情况下,只有./node_modules/@types/node
、./node_modules/@types/jest
和./node_modules/@types/express
会自动加入编译,其他node_modules/@types/
目录下的模块不会加入编译。
如果"types": []
,就表示不会自动将所有@types
模块加入编译。
useDefineForClassFields #
useDefineForClassFields
这个设置针对的是,在类(class)的顶部声明的属性。TypeScript 早先对这一类属性的处理方法,与写入 ES2022 标准的处理方法不一致。这个设置设为true
,就用来开启 ES2022 的处理方法,设为false
就是 TypeScript 原有的处理方法。
它的默认值跟target
属性有关,如果编译目标是ES2022
或更高,那么useDefineForClassFields
默认值为true
,否则为false
。
useUnknownInCatchVariables #
useUnknownInCatchVariables
设置catch
语句捕获的try
抛出的返回值类型,从any
变成unknown
。
try {
someExternalFunction();
} catch (err) {
err; // 类型 any
}
上面示例中,默认情况下,catch
语句的参数err
类型是any
,即可以是任何值。
打开useUnknownInCatchVariables
以后,err
的类型抛出的错误将是unknown
类型。这带来的变化就是使用err
之前,必须缩小它的类型,否则会报错。
try {
someExternalFunction();
} catch (err) {
if (err instanceof Error) {
console.log(err.message);
}
}
参考链接 #
- Strict Property Initialization in TypeScript, Marius Schulz