关于tsconfig.json,最后更新2023/11月...
原文链接:https://github.com/taoliujun/blog/issues/23
截至TypeScript 5.2,tsconfig.json
的配置项已经有百十个之多,它的某些选项甚至影响了项目的执行结果,所以尽量多的了解它们能让程序员更深的了解ts,写出优美语句让CTO赞赏,甚至避免写出bug。
本issue的内容,只是个人在工作经验的影响下,对官方文档的内容理解。且内容有所欠缺,基本只记录了我工作涉及到的配置项,比如不怎么使用class,对class的配置项就略过了。
官方文档:https://www.typescriptlang.org/tsconfig
Top Level
一些根配置项。
extends
继承另一个配置文件,推荐的一些官方的配置文件来继承使用:https://github.com/tsconfig/bases/tree/main/bases
files, include, exclude
这几项指定了在项目里,被ts作用的文件集合。配置本身没什么好说的,记录下它们之间的关系。
- 如果files指定了,那么include的默认值是
[]
,否则include的默认值是**/*
; - exclude用于排除include已指定的文件集合,但这些文件仍然是可以在项目里被引用的;
- exclude默认排除了
node_modules
、outDir
;
references
用于项目文件夹分别打包,并且其中有缓存机制的参与,所以编译速度会快很多。
如下项目结构:
1 | ./interface |
在上面,user和admin文件夹分别是业务用户端、业务管理端,interface是接口定义,components是通用组件。在以往,改动了任何文件,都需要整个项目重新编译。而在使用references之后,将4个文件夹中放入对应的tsconfig.json并各有配置,在根tsconfig中指定好references的path后,tsc利用缓存机制,会只打包改动过的文件夹。
compilerOptions
编译器配置项太多了,按作用拆分成几个评论来说。
compilerOptions - Type Checking
和类型检查有关的配置项。
strict
strict是一个严格模式的快捷开关,开启后会默认打开strictNullChecks、noImplicitAny等选项。并且随着typescript的升级,它可能会默认开启新增特性,列出截至5.2默认开启的选项:
alwaysStrict
strictNullChecks
strictBindCallApply
strictFunctionTypes
strictPropertyInitialization
noImplicitAny
noImplicitThis
useUnknownInCatchVariables
alwaysStrict
对所有文件启用javascript strict模式。注意,这和typescript strict不是同一个东西。参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
allowUnreachableCode
对未使用的代码的处理策略,可设置警告(默认)、错误、忽略。
allowUnusedLabels
对未使用的Label的处理策略,可设置警告(默认)、错误、忽略。
exactOptionalPropertyTypes
对可选项设置为undefined的策略。
举例:
1 | interface User { |
User.sex
是一个可选项,它的预期值是0|1|undefined
。但是呢,sex
可选想表达的意思是未被定义,而不是值真的是undefined。那么开启本选项后,man.sex = undefined
这个赋值语句会给一个报错。
noImplicitAny
对推导类型为any的策略。
1 | function fn(s) { |
入参s
推导为any
,在选项开启情况下会报错。
noImplicitReturns
对函数每个分支必须有return语句的策略。
1 | function fn(s: string) { |
开启后,ts报错认为fn
函数的最后没有return语句。
noPropertyAccessFromIndexSignature
对访问未定义的对象属性的策略。
1 | interface User { |
访问对象属性有.
和[index]
两个方式,访问未定义的属性,开启本选项后,在a.sex
处会抛出一个错误。
noUncheckedIndexedAccess
未定义的对象属性值类型,给追加上undefined
类型。
1 | interface User { |
上例中,birthday的值类型是string
,开启本项后,类型追加上undefined
。
1 | // (index) User[string]: string | undefined |
noUnusedLocals
局部变量未使用,抛出错误。
1 | function test() { |
noUnusedParameters
入参未使用,抛出错误。
1 | const createDefaultKeyboard = (modelID: number) => { |
strictBindCallApply
在使用函数的call、bind、apply时,是否要检查传入参数的类型。
1 | function fn(x: string) { |
如上代码中,第二个call中的入参false和string类型不匹配而报错。
strictFunctionTypes
更精确的检查函数入参类型。
1 | type GetUser = (id: string | number) => any; |
如上代码中,GetUser的入参类型string | number
不能精确的分配给test的入参类型string
。
strictNullChecks
是否校验null
, undefined
的属性访问?
在以往的纯js项目中,容易忽略变量为undefined后仍然访问其属性的场景,比如在如下代码中,忽略了a
为undefined
的情况,导致运行时引发异常。
1 | const a = arr1.find(); |
本选项开启后,在静态检查时就提示开发者,变量可能是null
, undefined
,不能访问到name
属性。
useUnknownInCatchVariables
有时候我们并不知道catch的err类型是什么,它的类型由try里的实际运行分支决定,而如果当成any处理,那么访问它的属性是危险的。当开启本项后,err的类型是unknown,必须先限定其类型再安全的访问其属性,如:
1 | try { |
compilerOptions - Modules
模块的处理策略。
allowArbitraryExtensions
ts默认支持了ts、js、cjs、jsx等模块的解析描述,通过global.d.ts
的定义扩展,还可以支持css、jpg等模块的解析描述(你要自己保证webpack loader之类的解析器去真实支持加载这些模块)。而有时候,需要对特定模块文件(不管是否已全局定义过该模块的描述)做特别的描述,就可以开启该选项,并且创建一个{file basename}.d.{extension}.ts
文件。
如:
1 | // test.ts |
开启该项,并增加a.d.jpk.ts
文件:
1 | // a.d.jpk.ts |
allowImportingTsExtensions
是否允许在import path带入ts、tsx等后缀名。
ts项目需要编译成js代码后执行,如果我们使用ts-node来执行项目,可以启用noEmit
来禁止这个编译行为,并且在项目源码中直接引入.ts
来引入正确的、完整的路径。
举例:
1 | import { wait } from '@/utils/utils.ts'; |
allowUmdGlobalAccess
// TODO
和umd的声明有关系,不过我还不明确它的意义。见:https://github.com/microsoft/TypeScript/pull/30776。
baseUrl
为解析无路径修饰的模块,设置一个基础路径。 什么叫做无路径修饰?就是该模块不是一个绝对或相对路径,如import a from '@/hello/world'
。
如果配置了该项,那么ts从该项指定的目录中开始查找模块,优先级也高于node_modules
。
module
模块打包的策略,也就是指编译后的模块加载代码是require、import之类的语法。推荐大家阅读:模块理论。
这个话题有点繁琐,也涉及到javascript的模块化amd、umd之类的历史,对于2023年这个时间点来说,关注下CommonJS、ESM即可,ESM还细分为ES2015、ES20XX等版本,他们的模块打包策略是一样的,区别是更高版本的ESM还支持了dynamic imports
、import.meta
和top level await
之类的东西。
补充的是,nodejs的模块打包策略也早就支持esm了,具体参照package.json的设置:https://www.typescriptlang.org/docs/handbook/modules/reference.html#node16-nodenext
moduleResolution
加载模块的策略,也就是指用什么策略去解析import xxx from 'pathA'
这里的pathA
。注意区分和module的关系,module指的是项目的模块化是如何打包的,而moduleResolution指的是项目如何加载模块的。截至2023支持如下策略:
- node16/nodenext,对于Node12+版本,本策略可自由根据import/require选择合适的算法去加载模块。
- node10,对于Node12之前的版本,本策略只支持require算法加载模块。基本不用关心了
- bundler,和node16一样,但不要求模块有后缀名。
- classic,早期的策略,不用管了。
这东西还要和module搭配使用,并且!并且!和package.json还有扯不清的关系。强烈建议阅读上文提到的模块理论
另外,由于自己对这块的理解也不足深,还参考了这篇文章:https://zhuanlan.zhihu.com/p/621795173。
moduleSuffixes
模块搜索时的文件扩展名补充,比如以下配置项:
1 | { |
1 | import * as foo from "./foo"; |
TypeScript 会按顺序寻找./foo.ios.ts
、./foo.native.ts
、./foo.ts
。
paths
在加载模块路径时,加入一个别名匹配。可以理解为webpack的alias。
resolveJsonModule
允许加载json模块(文件)。
resolvePackageJsonExports
引用node_modules
包时,强制遵循包package.json的exports字段的定义。还是那句话,阅读下模块理论那篇文章,不然很难理解前面这句话的信息量。
resolvePackageJsonImports
强制typescript使用package.json中imports字段的定义去加载#
符号开头的模块路径。例:
1 | // package.json |
1 | // src/test/1.ts |
1 | // src/b.ts |
rootDir
指定项目的根目录,默认值可以理解为所有ts文件目录的最大集。例如:
1 | MyProj |
如上的项目,rootDir的推断值是”core”。但是你可以手工指定为tsconfig.json
的相对目录”,比如.”。
另外,如果设置了composite
,rootDir
的默认值就是tsconfig.json
文件的目录。
rootDirs
这个属性有点拽,它可以将多个目录虚拟成一个目录。这样在import
的时候,就当成同一个目录就行啦。
typeRoots
类型定义文件的目录,默认所有@types目录都会被包含,包括node_modules下的@types目录。
如果指定了typeRoots,那么@types目录的相关规则会被忽略。
types
类型定义文件的具体目录,规则和typeRoots
一样,不同点是types
只指定具体目录,而非*
这种匹配型目录。
compilerOptions - Emit
编译策略。
outDir
编译文件放置的目录,默认和源码放同一个目录。
noEmit
不要编译ts。
项目中一般使用babel去编译,typescript仅用来做静态检查,所以设置noEmit就可以不生成js、sourcemap、declaration文件了。
noEmitOnError
顾名思义,在检查到错误的时候,不要继续编译了。
declaration / emitDeclarationOnly / declarationDir
生成类型描述文件。
这个是typescript最重要的几个特性之一了,为.ts
文件生成类型定义文件.d.ts
。如果不想同时生成.js
文件,则使用emitDeclarationOnly
。
同时,可以使用declarationDir
指定类型定义文件的生成目录。
declarationMap
给.d.ts
文件增加一个map标识到.ts
源文件,类似sourceMap。
sourceMap / inlineSourceMap / inlineSources / mapRoot / sourceRoot
typescript也是支持souce map的,这里就不解释sourcemap是什么了。它的相关配置项有:
sourceMap,生成sourcemap文件。
inlineSourceMap,不生成sourcemap文件,而是直接把source内容写在.js文件中。
inlineSources,同inlineSourceMap。
mapRoot,指定sourceMap文件的路径,比如部署到网络中,可以指定为”http://xxxx.com"。
sourceRoot,指定source文件的路径,同mapRoot。
downlevelIteration
对迭代器的降级解析。
在es6中增加了for/of,spread等迭代特性,typescript在编译成es5的时候,要使用何种语法。文档中有个字符遍历的例子说明开启与否的迭代影响,这对业务计算结果是有影响的。
不过项目中使用了babel之类的编译器,就不用担心这些影响了。
importHelpers
引入降级解析的包。
将es6的某些特新编译成es5,如迭代器、异步语法等,会将模块中每个相应的代码都编译成降级代码,使得类似代码重复。如:
1 | var __read = (this && this.__read) || function (o, n) { |
如果同时开启了downlevelIteration
和importHelpers
,并且安装了tslib
包,那么代码会编译成类似这样:
1 | import { __read, __spreadArray } from "tslib"; |
noEmitHelpers
typescript处理迭代器、异步等的降级策略是生成一堆降级代码,也可以使用”importHelpers”和”tslib”来提取公共代码。但通过这个配置,可以自定义降级代码了。具体参照文档。
preserveConstEnums
是否在编辑结果中移除const enum
的定义。因为javascript没有enum概念,typescript会将enum的引用编译成具体的值。如:
1 | const enum Album { |
编译结果如下:
1 | ; |
如果开启此项,则编译结果中会描述出该enum的结构。如下:
1 | ; |
preserveValueImports
通常,打包器会将未使用的”import”移除掉,但开启了该特性后,会在编译中保留未使用的”import”。例如:
1 | import { Animal } from "./animal.js"; |
removeComments
移除代码中的注释。
compilerOptions - JavaScript Support
对javascript文件的支持
allowJs
允许.ts
文件引用.js
文件,这常用在javascript项目升级为typescript的过程中。
checkJs
是否开启对.js
文件的错误检查。
maxNodeModuleJsDepth
为.js
文件寻找类型定义文件时,允许的最大依赖深度。
正常情况下,我们会在.js
文件的同级补写.d.ts
文件,tsc会很快找到类型定义。但有时候确实要扩大搜索范围去找部分.js
文件的类型定义文件。
compilerOptions - Interop Constraints
(模块)互相操作的约束
allowSyntheticDefaultImports
允许合成default
。
如果一个模块A没有export default
,那么另一个模块B在import defaultA from './a'
的时候,会抛出错误说A中没有default。
有一个做法是import * as defaultA from './a'
曲线救国,但开启这个配置项后,可以直接使用import defaultA from './a'
这样的语法了。
esModuleInterop
esm在使用import default from 'xxx'
方式导入cjs的时候会抛出一个错误,因为cjs模块中没有default。开启此选项后,typescript会使用一些helpers去兼容cjs的default问题。
forceConsistentCasingInFileNames
强制引用的模块文件名,和磁盘中的文件名保持一致的大小写。
compilerOptions - Language and Environment
和语言、环境有关的配置项。
jsx
如何解析.tsx
文件中jsx语法?在2023年,react17+后,用react-jsx
就行了。
lib
在项目中使用的api类型定义集合。
ts是需要知道你用的每一个javascript方法、属性的定义的,所以它内置了一批定义(比如Math.abs方法)。但是呢,后面新增方法的定义,需要你手工指定包含,ts才能理解了,比如Array.include方法,你就要包含ES2016。但好在你不需要记住这些,因为当你指定target
字段时,lib会被默认设置成对应的值的。
另外,大部分情况下,我们要访问一些和特定环境有关的特性,比如浏览器里的Dom特性,那么这儿额外引入DOM
定义就行了。
target
项目编译的目标javascript版本。
也就是你的目标客户端支持的最低javascript版本。注意它会影响默认的lib
配置项。
compilerOptions - Compiler Diagnostics
编译诊断配置项。
diagnostics / extendedDiagnostics
打印出编译的信息,比如多少文件、编译时间等。
extendedDiagnostics包含了diagnostics给出的所有信息,所以用extendedDiagnostics就行了。这是一个开启extendedDiagnostics的编译信息:
1 | Files: 143 |
explainFiles
打印出文件被编译的原因,也就是它们的引用链。
listEmittedFiles / listFiles
列出参与编译的文件。
traceResolution
打印每一个文件的编译流程。
compilerOptions - Completeness
完整性检测的配置项
skipLibCheck
跳过lib库的类型检测。