每一次搞ts项目的时候我都是抄其他项目的tsconfig,虽然大多数配置项都见过很多次知道大概什么意思了,但是还没有整体性地梳理过,今天就写一篇文章来帮助我梳理脑袋中的一团乱麻,也分享给需要的人。

开始

现在的TypeScript版本是5.4.3,本篇文章主要的参考来源是官网tsconfig页面。另外,提前说明一下,这篇文章主要写这些选项有什么用,什么场景用,具体怎么用还是需要参考文档。

目前tsconfig.json文件中顶级的字段一共有8个,buildOptions等没有写到官方文档中的几个字段,这里暂不研究。其中核心的也是最复杂的选项是compilerOptions,其余几个分别是files extends include exclude references watchOptions typeAcquisition。下面先从简单的开始说起

作用范围相关: files exclude include

一个项目有多份配置文件是非常正常的,比如一个Vue项目,前端页面一份配置,其产物运行环境是浏览器;打包工具比如Vite一份配置,其运行环境是Node.js。如果再有单元测试、端到端测试等等那就还会有更多的配置文件。这几个字段可以指定每份配置文件作用于哪个部分。

首先是files字段,其值是一个具体的文件名组成的数组,不允许使用通配符,如果当前这份配置文件就是明确为了几个单独的文件写的,那么用files指定会非常清晰且方便,比如专门给Vite写的那就用"files": ["vite.config.ts"]来指定作用范围,这种情况之下,只有这一个文件需要遵守这份配置,除此之外的ts文件不受影响。

如果文件比较多,尤其是涉及到某个文件夹内的文件,那么就得用include字段了。include值也是数组,不过同时支持通配符和具体的文件名,比如src/**/*就代表src文件夹及其子文件夹下的所有相关文件。可以看出来,include是完全可以替代files的,我个人感觉files显得有些鸡肋。

不过exclude还是很有用的,这个字段排除掉的是include中的内容,比如,前面把src目录下所有文件都包含进去了,这时候里面可能有__test__文件夹,里面是写的单元测试,配置规则和正常文件不一样,那么就用exclude给排除掉就好了。

复用相关: extends references

extends字段,和字面意思一样,继承,这很容易理解。把多份配置文件的共同配置抽离出来,然后通过设置此字段,比如"extends": "./tsconfig.base.json"来复用配置项。继承针对的是配置文件,因此不仅是compilerOptions,所有字段都会被继承。比如Vue官方的tsconfig项目包含了Vue开发的推荐配置,还有社区的tsconfig/bases项目包含了很多常用场景下的配置,通过安装npm包就可以直接引用相关配置,免除了每次都手动配置的麻烦。

extends复用的是配置项本身,其效果和把对应配置直接粘贴过来是一样的。而references就要复杂和高级一些了,其值是由对象组成的数组,它可以复用编译的产物,提升编译效率。显然这个选项的效果只有项目规模比较大了才能凸显出来,小项目可以说完全没必要配置。比如一个比较庞大的库,其环境就是Node.js,整个项目就需要一个配置文件就够了,但是改一个文件重新编译可能就得花半小时,这样开发起来就太折磨了。通过设置references细分各个模块,然后在改动的时候就可以只编译变化的模块,这样可能只需要半分钟,效率显著提升。这样描述可能比较抽象,如果想要了解,我推荐直接查看TypeScript项目自身的tsconfig.json文件,更进一步可以修改这个字段对比编译过程的不同。

编译相关: watchOptions typeAcquisition compilerOptions

watchOptions的配置比较简单,涉及到file、directory的几个选项和上面提到的include exclude选项规则是类似的,只是这些选项只有设置了--watch时才生效,由于大多框架的功能比较丰富,通常不太常用tsc的watch功能,所以按需了解。

typeAcquisition也是一个一般用不到的选项,在js项目获取模块的类型。因为ts项目需要手动安装@types/xxx这种类型包,只有js项目不想在package.json里面引入这些依赖,对应模块也没有类型文件,但是自己又想要类型补全和检查等功能,这种不太常见的场景之下才需要配置该选项。不过选项的配置并不复杂,同样是和前面的exclude include类似的规则,看文档配置就可以了。

compilerOptions下选项非常多,官方按负责的功能分为了以下14个部分,接下来我也是按这个分类来,但是顺序并不保持完全一致

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Type Checking
Modules
Emit
JavaScript Support
Editor Support
Interop Constraints
Backwards Compatibility
Language and Environment
Compiler Diagnostics
Projects
Output Formatting
Completeness
Command Line
Watch Options

Command Line

这个部分文档是空白的,不过我还是在这里简单说明一下。TypeScript官方的命令行工具有两个,分别是tsctsserver,前者是一个编译器,一般用法就是手动执行获取产物,或者通过--watch即时编译修改后文件;后者是封装了编译器和语言服务的服务器,主要提供给IDE使用,提供类型补全和检查等能力。绝大多数情况下,通过tsc -h查看帮助内容来使用就可以了,tsserver是用不到的,具体用法可以查看TypeScript Wiki,此处就不多费笔墨了。

Emit

这个部分的选项控制的是输出产物,包括.js文件,.d.ts类型声明文件,.map.jssource-map文件,可以分别控制每个部分的输出配置。

配置这个部分的时候,首先考虑是否需要输出,前端项目如果使用了vitewebpack之类的打包工具,那么大多数情况下,需要配置的是打包工具的输出选项,此处只需要设置"noEmit": true即可。

如果确认需要使用tsc编译器输出,那么根据项目类型是应用(Application)还是库(Library)会有所不同。通常Application需要输出js文件,对应的常用选项包括outDir outFile removeComments等,除此之外生产环境可能想设置sourceMap: false(这是默认行为),或者设置mapRoot sourceRoot以避免泄露source-map文件,类型声明是需要关闭的;而库的配置则不同,因为要供其他人调用,要设置emitDeclarationOnly: true确保只输出类型声明,以及设置stripInternal: true避免输出内部属性/方法的类型。

Type Checking

这部分的选项虽然不少,但是一般设置strict: true就足矣了,这样noImplictAny noImplictThis也会自动置为true,这样的配置符合大多数的编程习惯。如果项目比较特殊,不能这样设置,那么就根据实际需要逐个选项配置,或者完全忽略这里的选项。

Modules

这一部分需要配置的选项会比较多,首先是baseUrlpaths,它们的作用就是简化项目文件中的引入路径,一般配置后者就可以,比如前端项目通常要配置

1
2
3
4
5
{
    "paths": {
        "@/*": ["./src/*"]
    }
}

然后是modulemoduleResolution选项,这影响模块怎么解析,以及你需要使用什么样的语法来引入模块。一个现代的项目,如果是前端项目,一般前者设置的值是esnext或者es2022,后者设置为bundler比较好;如果是Node.js项目,一般二者均设置node16即可。

不少项目中会需要导入json文件,因此resolveJsonModule: true也是一个常用的选项。

还有一个可能需要配置的选项是types,这通常需要在项目的次要配置文件中设置,比如只针对测试的tsconfig.test.json中,设置"types": ["node", "jsdom"]。这是因为默认情况下,ts会把项目中所有的@types包都包括在内,但是针对某个环境只需要一两个包,正如前面配置的那样。

Interop Constraints

这个部分推荐设置esModuleInterop: true这会将allowSyntheticDefaultImports选项也设置为true,开启这两个选项修复了之前import * as React from 'react'这种语法不严格符合ES6模块的遗留问题。同时也允许使用import React from 'react'语法来导入像react这种没有默认导出的包,使得导入语句更统一。另外还推荐将verbatimModuleSyntax也设置为true,目的是可以显式地用import type XXX from xxx语法来导入类型(这会在最终产物中移除),使用import XXX from xxx来导入值或对象等(这不会被移除)。

Language and Environment

两个带decorator的选项决定是否开启装饰器这个实验性的功能,如果之前没听说过装饰器,那么就不要开启,当然,可以阅读ts官方的装饰器介绍以及Mirone的这篇文章来了解装饰器,另外,AngularNest两个项目都大量使用了装饰器。

带jsx的几个选项我觉得bun这个文档解释的很不错,这里简单讲,就是需要根据项目的框架以及使用的打包工具类型来设置,比如项目使用的是React,那么设置"jsx": "react-jsx" 即可;如果是Vue项目,可能需要设置"jsx": "preserve"以及"jsxImportSource": "vue"两个选项。

target指定了输出产物的ES版本,对于现代的浏览器,ES2020是一个推荐的选项,这个选项设置之后,lib的类型定义也会自动调整,比如ES2020对应的string.matchAll特性会出现类型提示,且编译不报错。但是更常见的做法是手动设置lib的值,比如通常是"lib": ["ES2020", "DOM", "DOM.Iterable"]。Node.js的这两个值根据版本的不同而不同,比如现在的lts是v18和v20,二者可以设置为"lib": ["ES2023"]"target": "ES2022"

这部分还有一个useDefineForClassFields选项也推荐设置为true,这样,针对class字段,可以确保遵守ECMA的新规范而不是TypeScript自身的旧规范。

JavaScript Support

既然是ts项目,我个人是不喜欢掺杂js文件的,因此通常会保持默认。不过有些js项目想要迁移为ts项目时,开始allowJS是很有用的。

Completeness && Projects

Vue项目可能推荐设置skipLibCheck选项为true,其具体讨论在这里,我个人的偏好是,如果官方没有建议就保持默认值false。

Projects部分的compositetsBuildInfoFile两个选项建议查看文档,了解一下,但是一般也不需要更改其值。

Editor Support && Output Formatting && Watch Options && Compiler Diagnostics && Backwards Compatibility

这里几个部分的选项基本不会用到,甚至没有必要看文档,真正遇到问题再了解也可以,因此此处不过多描述了。

总结

这篇文章主要梳理了常用的以及我个人在实际项目中用到过的一些配置项,没有提到的选项通常是不常用的,我也没有遇到的。暂时就写到这里,后续如果有所变化,我会再补充进来。