TypeScript 配置文件(tsconfig.json)是用于配置 TypeScript 项目的重要文件。它允许开发者自定义 TypeScript 编译器的行为,指定编译选项、文件包含与排除规则、输出目录等。通过合理配置 tsconfig.json,我们可以根据项目需求进行灵活的 TypeScript 编译设置。
本文将全面解读 tsconfig.json 的各个配置选项,并提供一些常见的使用场景和示例代码
常用选项概览
json
{
"compilerOptions": {
/* 基本选项 */
"target": "es5", // 生成代码的 ECMAScript 目标版本
"module": "commonjs", // 生成代码的模块标准
"lib": ["es6", "dom"], // 编译过程中需要引入的库文件的列表
"allowJs": true, // 是否编译 JS 文件
"checkJs": true, // 是否在 JS 文件中报告错误
"jsx": "preserve", // 在 .tsx 文件里支持 JSX: 'preserve', 'react-native', or 'react'
"declaration": true, // 是否生成 .d.ts 类型定义文件
"emitDeclarationOnly": true, // 只生成类型声明文件,不生成js
"declarationMap": true, // 为每个 .d.ts 文件生成 sourcemap
"sourceMap": true, // 是否生成 .map 文件
"outFile": "./dist/main.js", // 将输出文件合并为一个文件
"outDir": "./dist", // 输出文件夹
"rootDir": "./", // 输入文件 folder 路径
"composite": true, // 是否编译构建引用项目
"removeComments": true, // 删除注释
"noEmit": true, // 不输出文件
"importHelpers": true, // 通过 tslib 引入辅助工具函数
"downlevelIteration": true, // 降级遍历器实现的支持
"useDefineForClassFields": true, // 是否使用 Object.defineProperty 定义类实例属性
/* 严格的类型检查 */
"strict": true, // 启用所有严格类型检查
"noImplicitAny": true, // 不允许隐式的 any 类型
"strictNullChecks": true, // 不允许把 null、undefined 赋值给其他类型变量
"strictFunctionTypes": true, // 严格检查函数的类型
"strictBindCallApply": true, // 严格检查 bind、call 和 apply 的参数规则
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"noImplicitThis": true, // 不允许 this 有隐式的 any类型
/* 额外检查 */
"noUnusedLocals": true, // 检查未使用的局部变量
"noUnusedParameters": true, // 检查未使用的参数
"noImplicitReturns": true, // 每个分支都会有返回值
"noFallthroughCasesInSwitch": true, // 检查 switch 语句包含正确的 break
/* 模块解析 */
"isolatedModules": true, // 控制是否将每个文件作为单独的模块处理。
"moduleResolution": "node", // 模块解析策略
"allowImportingTsExtensions": true, // 允许从没有默认导出的模块中导入类型定义(.d.ts)文件
"baseUrl": "./", // 解析非相对模块的基地址
"paths": {}, // 模块名称到基于 baseUrl 的路径映射表
"rootDirs": [], // 将多个文件夹放在一个虚拟目录下
"typeRoots": [], // 声明文件目录列表
"types": [], // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块默认导入
"esModuleInterop": true, // 通过创建命名空间实现 CommonJS 兼容性
"resolveJsonModule": true, // 自动解析JSON文件
/* Source Map */
"sourceRoot": "", // TypeScript 源代码所在的目录
"mapRoot": "", // 指定 map 文件的路径
"inlineSourceMap": true, // 生成单个 sourcemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中
/* 实验性 */
"experimentalDecorators": true, // 启用实验性的装饰器特性
"emitDecoratorMetadata": true // 为装饰器提供元数据支持
},
"files": [], // files可以配置一个数组列表,里面包含指定文件的相对或绝对路径,编译器在编译的时候只会编译包含在files中列出的文件,如果不指定,则取决于有没有设置include选项,如果没有include选项,则默认会编译根目录以及所有子目录中的文件。这里列出的路径必须是指定文件,而不是某个文件夹,而且不能使用* ? **/ 等通配符
"include": [], // include也可以指定要编译的路径列表,但是和files的区别在于,这里的路径可以是文件夹,也可以是文件,可以使用相对和绝对路径,而且可以使用通配符,比如"./src"即表示要编译src文件夹下的所有文件以及子文件夹的文件
"exclude": [], // exclude表示要排除的、不编译的文件,它也可以指定一个列表,规则和include一样,可以是文件或文件夹,可以是相对路径或绝对路径,可以使用通配符
"extends": "", // extends可以通过指定一个其他的tsconfig.json文件路径,来继承这个配置文件里的配置,继承来的文件的配置会覆盖当前文件定义的配置。TS在3.2版本开始,支持继承一个来自Node.js包的tsconfig.json配置文件
"compileOnSave": true, // compileOnSave的值是true或false,如果设为true,在我们编辑了项目中的文件保存的时候,编辑器会根据tsconfig.json中的配置重新生成文件,不过这个要编辑器支持
"references": [], // 一个对象数组,指定要引用的项目
}
常用选项解析
useDefineForClassFields
useDefineForClassFields 是 TypeScript 的一个编译选项,它用于控制类的实例属性如何编译。
这个选项的作用是:
- 当它设置为true时,类的实例属性会编译成使用 Object.defineProperty 来定义。
- 当它设置为false时(默认值),类的实例属性会编译成简单的赋值来定义。
举例说明:
ts
class Foo {
bar = 123;
}
如果 useDefineForClassFields 为 true:
js
var Foo = class {
constructor() {
Object.defineProperty(this, "bar", {
configurable: true,
writable: true,
value: 123
});
}
}
如果 useDefineForClassFields 为 false(默认):
js
var Foo = class {
constructor() {
this.bar = 123;
}
}
useDefineForClassFields 是一个用于类字段声明的配置选项。它在 TypeScript 2.7 版本中被引入,并在后续版本中得到改进和优化。
当开启 useDefineForClassFields 选项时,类字段的声明方式发生了改变,如下所示:
ts
// 开启 useDefineForClassFields
class MyClass {
myField = 42;
}
// 等价于未开启 useDefineForClassFields
class MyClass {
constructor() {
this.myField = 42;
}
}
在未开启 useDefineForClassFields 选项的情况下,类字段的初始化通常需要放在构造函数中。而开启了该选项后,可以直接在类的定义中进行初始化,类似于在构造函数中使用赋值语句。
开启 useDefineForClassFields 的好处包括:
- 简洁性:代码更加简洁易读,不再需要在构造函数中手动初始化类字段,使得代码更加简洁。
- 可读性:通过直接在类定义中初始化字段,可以更清晰地看到字段的初始值,提高了代码的可读性。
- 类字段支持类型推断:开启该选项后,TypeScript 可以正确推断类字段的类型,不再需要显式地指定类型。
isolatedModules
控制是否将每个文件作为单独的模块处理。
默认情况下,该选项为 true。这意味着:
- 每个文件都作为单独的模块处理。
- 文件之间的变量、函数、类等不会相互影响。
- 文件只能访问自己导出的内容。
如果设置为 false:
- 整个项目的文件会合并成一个模块。
- 文件之间可以相互访问变量、函数、类等成员。
- 一个文件可以直接使用另一个文件暴露出来的内容。
通常我们希望每个文件是独立的模块,所以保持该选项为 true。
但是有些场景下需要关闭该选项:
- 当项目文件间有重名的类时,需要设置为 false 以便合并为一个类。
- 当不同文件需要共享某些变量或状态时,需要设置为 false。
总之,isolatedModules 用于控制文件之间是否独立。它可以处理一些特殊的场景,但通常保持 true 即可。
paths
paths 选项用于配置模块导入时的路径映射。通过使用 paths,你可以在项目中定义模块的别名,使得导入模块时可以使用这些别名,从而简化代码,减少路径错误,并提高可维护性。
以下是 paths 选项的使用示例:
- 假设你的项目目录结构如下:
lua
project
|-- src
| |-- components
| | |-- Button.ts
| | |-- Card.ts
| |-- utils
| | |-- helpers.ts
|-- tsconfig.json
- 在 tsconfig.json 文件中,你可以这样配置 paths 选项:
json
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@components/*": ["components/*"],
"@utils/*": ["utils/*"]
}
}
}
在这个例子中:
-
"baseUrl": "./src" 表示项目中所有导入模块的相对路径都是相对于 "src" 目录的。
-
"paths" 配置了两个路径映射:
- "@components/*" 是一个模块别名,它将匹配任何以 "@components/" 开头的导入路径。
- "@utils/*" 是另一个模块别名,它将匹配任何以 "@utils/" 开头的导入路径。
- 使用别名进行模块导入:
现在,你可以在你的代码中使用这些别名进行模块导入,而不需要使用相对路径或绝对路径。
ts
// 使用别名导入模块
import { Button } from "@components/Button";
import { Card } from "@components/Card";
import { someHelperFunction } from "@utils/helpers";
// 使用别名进行导入后的代码更加简洁
通过使用 paths 选项,你可以定义更多的模块别名,根据你的项目结构和需求来简化导入语句,提高代码的可读性和可维护性
baseUrl
用于指定 TypeScript 编译器在解析模块导入路径时的基础路径。通过设置 baseUrl
,你可以简化模块导入语句,使得导入模块时的路径更加清晰和简洁。
假设项目结构如下:
lua
project
|-- src
| |-- components
| | |-- Button.ts
| | |-- Card.ts
| |-- utils
| | |-- helpers.ts
|-- tsconfig.json
默认情况下,TypeScript 编译器会将所有导入语句视为相对于包含它们的文件的路径。这意味着,如果你在 Button.ts
中想导入 Card.ts
,通常需要使用相对路径来完成导入,例如:
ts
import { Card } from '../Card';
但是,随着项目规模增长,导入语句中的相对路径可能会变得非常复杂和深层嵌套,这可能导致代码可读性降低,维护困难。
为了解决这个问题,可以使用 baseUrl
选项,将模块导入路径的基础路径设置为项目中的某个目录,通常是项目的 src
目录。
在 tsconfig.json 文件中设置 baseUrl
的用法如下:
json
{
"compilerOptions": {
"baseUrl": "./src"
}
}
上面的配置将 baseUrl
设置为项目中的 src
目录。这意味着,在所有的模块导入语句中,TypeScript 编译器会将导入路径解析为相对于 ./src
目录的路径。因此,我们可以简化模块导入语句,如下所示:
ts
// 使用 baseUrl 后,导入语句更简洁
import { Card } from 'components/Card';
import { someHelperFunction } from 'utils/helpers';
通过设置 baseUrl
选项,简化模块导入语句,提高代码的可读性和可维护性,尤其在大型项目中,这个功能尤为有用。
skipLibCheck
用于控制是否在编译时跳过对声明文件(.d.ts 文件)的类型检查。
在 TypeScript 项目中,除了普通的 TypeScript 文件外,我们还可能会引入第三方库或其他模块,这些模块通常会伴随着对应的声明文件,以便 TypeScript 在编译时能够正确地识别这些库的类型信息。有时候这些声明文件可能并不完善或存在一些错误,导致 TypeScript 编译器在检查时会发出一些类型相关的警告或错误。
开启 skipLibCheck 选项会提高编译速度但是可能会导致一些类型相关的问题在运行时才暴露出来,
moduleResolution
moduleResolution 选项用于配置模块解析策略,它有以下可选值:
- "node": 使用Node.js的模块解析策略。这是默认值。
- "classic": 使用TypeScript早期的模块解析策略,已废弃。
- "bundler": TypeScript在版本4.5中新增的一个模块解析策略。
"bundler"策略在解析模块时,会模拟一个 bundler 的行为。
它与"node"策略的主要区别是:
- "node"会将文件和目录作为不同的模块解析。
- "bundler"会将文件和目录都合并解析为一个模块。
举例来说,对于导入路径 "./foo":
- "node"会先查找 ./foo.ts 文件,如果不存在再查找 ./foo/index.ts
- "bundler"会直接将 ./foo.ts 和 ./foo/index.ts 合并为一个模块
使用"bundler"策略的目的是为了使TypeScript模块解析更接近捆绑工具(webpack等)的解析策略。
它的常见使用场景包括:
- 使用webpack等bundler时,让TypeScript的模块解析方式与之匹配,编写代码时能获得一致的导入解析结果。
- 需要模块路径映射等高级功能时,使用bundler策略可以避免与TypeScript解析不一致的情况。
- 希望模块解析更加灵活,文件和目录可以混合导入时。
allowImportingTsExtensions
通常情况下,如果一个模块没有默认导出,那么只能导入该模块中的类型,不能导入它的类型定义文件。
举例来说,存在一个没有默认导出的模块:
ts
// utils.ts
export function util1() {}
对应的类型定义文件:
ts
// utils.d.ts
export function util2();
默认情况下,不能导入 utils.d.ts 中的类型:
ts
// index.ts
import { util2 } from './utils.d.ts'; // Error
但如果设置了allowImportingTsExtensions为true,则可以导入:
ts
{
"compilerOptions": {
"allowImportingTsExtensions": true
}
}
// index.ts
import { util2 } } from './utils.d.ts'; // Allowed
这在一些特殊场景下是有帮助的,比如想直接导入声明文件中的类型定义。
但是通常不建议开启此选项,因为这会破坏模块的边界,将类型定义与模块实现混合在一起,不利于维护。
建议的方式是专门导出类型定义,或者直接导入模块的类型。
resolveJsonModule
它用于控制 TypeScript 编译器是否支持导入 JSON 文件作为模块。
TypeScript 2.9 版本起,通过设置 resolveJsonModule
选项为 true
,TypeScript 编译器会自动解析并识别导入的 JSON 文件,无需手动添加类型声明文件。
使用这个选项的好处在于,当你需要导入配置文件、静态数据或其他 JSON 文件时,可以直接在 TypeScript 文件中导入并使用它们,而无需手动处理类型声明。TypeScript 编译器会在编译过程中将 JSON 文件转换为一个对象,可以直接按照对象的属性来访问 JSON 文件的内容。
emitDeclarationOnly
TypeScript 编译器只会生成类型声明文件(.d.ts 文件),而不会生成编译后的 JavaScript 文件。这对于创建类型声明库非常有,并且能够提高构建速度。
需要注意的是,如果设置了 emitDeclarationOnly
,必须将 declaration
设置为 true
。
noEmit
当设置 noEmit
选项为 true
时,TypeScript 编译器将不会生成任何输出文件,包括 JavaScript 文件和类型声明文件(.d.ts 文件)。
这个选项通常在以下情况下使用:
- 代码检查:你只想进行 TypeScript 代码的类型检查,而不需要实际编译生成 JavaScript 文件。通过设置
noEmit
可以加快检查速度,特别是在大型项目中。 - 在编辑器中进行类型检查:一些编辑器和 IDE(如 Visual Studio Code)支持在保存 TypeScript 文件时进行实时类型检查。如果你只想在编辑器中进行类型检查而不需要生成文件,可以设置
noEmit
为true
。
需要注意的是,如果设置了 noEmit
为 true
,同时设置了其他与输出文件相关的选项(如 outDir
、outFile
、declaration
、emitDeclarationOnly
等),那么 TypeScript 编译器会抛出一个错误,因为要生成输出文件。
jsx
它用于指定 TypeScript 文件中 JSX 语法的处理方式。
jsx
选项有以下几个可能的值:
"preserve"
: 这是默认值。当设置为"preserve"
时,TypeScript 编译器会保留 JSX 语法不进行转换。这意味着 JSX 将会被传递给 React 或其他处理 JSX 的工具进行处理,而 TypeScript 不会对 JSX 语法进行解析和转换。"react"
: 当设置为"react"
时,TypeScript 编译器会将 JSX 语法转换为 React.createElement 函数调用。这是在不使用 JSX 转换工具(如 Babel)的情况下,直接使用 TypeScript 编译器进行 JSX 转换的方式。这样你就可以在 TypeScript 项目中直接使用 JSX 语法,而不需要额外的转换配置。"react-jsx"
: 类似于"react"
,当设置为"react-jsx"
时,TypeScript 编译器会将 JSX 语法转换为 React.createElement 函数调用。这个选项在 TypeScript 2.1 版本引入,是为了与 React 16.0 版本之前的 JSX 语法兼容。"react-jsxdev"
: 类似于"react"
和"react-jsx"
,当设置为"react-jsxdev"
时,TypeScript 编译器会将 JSX 语法转换为 React.createElement 函数调用。这个选项在 TypeScript 2.1 版本引入,是为了与 React 16.0 版本之前的 JSX 语法兼容,同时支持使用开发工具。"react-native"
: 当设置为"react-native"
时,TypeScript 编译器会将 JSX 语法转换为 React.createElement 函数调用,类似于"react"
,但与 React Native 一起使用。
importHelpers
它用于控制 TypeScript 是否在编译时自动引入辅助工具函数,以减少生成的 JavaScript 代码大小。
在使用 TypeScript 编译时,一些高级的 JavaScript 特性(如 async/await、generator、Promise 等)需要一些辅助函数来实现,这些辅助函数会被频繁地使用。默认情况下,TypeScript 会将这些辅助函数内联到每个文件中,这可能导致生成的 JavaScript 文件比较冗长,特别是当项目中有多个文件使用这些特性时。
importHelpers
选项的作用是将这些辅助函数抽取到单独的帮助模块中,然后在每个文件中通过 import
语句引入这个帮助模块。这样可以避免生成冗长的重复代码,减小最终生成的 JavaScript 文件的大小,提高代码的运行效率。
esModuleInterop
在 ES 模块系统中,导入和导出模块的语法是使用 import
和 export
关键字。而在 CommonJS 模块系统中,Node.js 早期使用的模块系统,导入模块的语法是使用 require
,导出模块的语法是使用 module.exports
。
当 esModuleInterop
设置为 true
时,TypeScript 编译器会在生成的 JavaScript 代码中使用 ESM 格式来处理模块的导入和导出。同时,它还允许你在 TypeScript 代码中使用 import
来导入 CommonJS 格式的模块。
设置 esModuleInterop
为 true
的好处是可以简化代码并提高互操作性。通过在 TypeScript 代码中使用 import
来导入 CommonJS 格式的模块,你可以统一使用一种导入导出的语法风格,避免混用 import
和 require
。
allowSyntheticDefaultImports
用于允许从没有默认导出的模块中默认导入。
正常情况下,只有当一个模块明确导出一个默认值时,才可以使用默认导入语法导入该模块:
ts
// module.ts
export default function foo() {}
// other.ts
import func from './module';
但是有些模块没有默认导出值,这时默认导入语法将不被允许:
ts
// module.ts
export function foo() {}
// other.ts
import func from './module'; // Error
allowSyntheticDefaultImports 选项允许使用默认导入语法,即使这个模块没有默认导出值:
ts
{
"compilerOptions": {
"allowSyntheticDefaultImports": true
}
}
// other.ts
import func from './module'; // Allowed
这在一些特殊场景下是有用的,例如允许默认导入 CommonJS 模块。
js
// CommonJS module
module.exports = {
foo() {
// ...
}
}
// tsconfig.json
{
"compilerOptions": {
"allowSyntheticDefaultImports": true
}
}
// TypeScript
import module from './module.js';
module.foo();
在这个例子中:
- module.js是一个 CommonJS 模块,使用module.exports导出对象而不是默认导出。
- 在tsconfig.json中开启allowSyntheticDefaultImports选项。
- 在TypeScript中可以直接默认导入这个模块,并像默认导出一样使用它。
- 这样使得导入 CommonJS 模块的语法与导入ES6模块的语法保持一致。
- TypeScript通过创建一个合成的默认导出来实现这一转换。
所以通过配置allowSyntheticDefaultImports,可以实现在TypeScript中将CommonJS模块平滑地默认导入的效果。
但是非必要场景下不建议开启此选项。因为这会破坏默认导入语法的语义,使得无法明确知道一个模块是否有默认导出,增加理解和维护的难度。