tsconfig.json 指南
项目根目录标识
tsconfig.json 是 TypeScript 项目的配置文件,它在项目中扮演着非常重要的角色。当一个目录中存在 tsconfig.json 文件时,TypeScript 编译器会将其视为项目的根目录。这意味着,编译器会从这个文件所在的位置开始,按照配置文件中的指令来编译项目中的 TypeScript 文件。
例如,如果你的项目结构如下:
demo/
├── src/
│ ├── index.ts
├── tsconfig.json
└── package.json那么,tsconfig.json 所在的 demo 就是项目的根目录。当你运行 tsc 命令时,编译器会自动查找 tsconfig.json 文件,并根据其中的配置来编译 src 目录下的 .ts 文件。如果没有 tsconfig.json 文件,TypeScript 编译器会逐级向上查找,直到找到一个 tsconfig.json 文件为止。
自动生成与继承
tsconfig.json 文件可以通过命令行工具自动生成,也可以通过继承其他配置文件来实现复用。使用 tsc --init 命令可以快速生成一个默认的 tsconfig.json 文件,这个文件包含了 TypeScript 编译器的一些常用配置项。例如:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"src/**/*"
]
}这个默认生成的文件已经包含了大多数项目的基本配置,你可以根据项目的具体需求进行修改和调整。 除了自动生成,tsconfig.json 还支持继承其他配置文件。这在大型项目中非常有用,特别是当项目中有多个子项目时。你可以将公共的配置项放在一个基础配置文件中,然后在各个子项目的 tsconfig.json 文件中通过 extends 属性来继承这些公共配置。
例如,你有一个基础配置文件 tsconfig.base.json:
{
"compilerOptions": {
"target": "es6",
"module": "esnext",
"strict": true
}
}然后在子项目的 tsconfig.json 文件中继承它:
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": [
"src/**/*"
]
}这样,子项目就会继承基础配置文件中的所有配置项,并且还可以添加或覆盖一些特定的配置。这种继承机制可以大大减少配置的重复,提高项目的可维护性。
顶层配置项
extends
extends 是 tsconfig.json 文件中非常重要的一个配置项,它允许一个配置文件继承另一个配置文件的内容。
这在大型项目中非常有用,尤其是当项目中有多个子项目时,可以将公共的配置项放在一个基础配置文件中,然后在各个子项目的 tsconfig.json 文件中通过 extends 属性来继承这些公共配置。
例如,你有一个基础配置文件 tsconfig.base.json:
{
"compilerOptions": {
"target": "es6",
"module": "esnext",
"strict": true
}
}然后在子项目的 tsconfig.json 文件中继承它:
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": [
"src/**/*"
]
}这样,子项目就会继承基础配置文件中的所有配置项,并且还可以添加或覆盖一些特定的配置。这种继承机制可以大大减少配置的重复,提高项目的可维护性。 需要注意的是,如果 extends 属性指定的路径不是以 ./ 或 ../ 开头,那么编译器将在 node_modules 目录下查找指定的配置文件。此外,extends 指定的配置文件会先加载,然后加载当前的 tsconfig.json 文件。如果两者有重名的属性,后者会覆盖前者。
files
files 属性用于显式指定需要编译的文件列表。它是一个数组,其中包含文件的相对或绝对路径。如果某个文件被列在 files 中,那么它会被编译器包含在编译过程中,即使它在 exclude 列表中也是如此。例如:
{
"files": [
"src/index.ts",
"src/utils.ts"
]
}在这个例子中,只有 src/index.ts 和 src/utils.ts 会被编译器处理。如果 files 和 include 都没有被指定,编译器默认包含当前目录和子目录下所有的 TypeScript 文件(.ts、.d.ts 和 .tsx),排除在 exclude 里指定的文件。如果指定了 files 或 include,编译器会将它们结合一并包含进来。
需要注意的是,files 中列出的路径必须是指定文件,而不是某个文件夹,而且不能使用 *、?、** 等通配符。此外,任何被 files 或 include 指定的文件所引用的文件也会被包含进来。例如,如果 A.ts 引用了 B.ts,那么 B.ts 也会被编译器包含,除非引用它的 A.ts 在 exclude 列表中。
include
include 属性用于指定需要编译的文件或目录的模式匹配列表。它支持使用通配符,例如:
- *:匹配0或多个字符(不包括目录分隔符)
- ?:匹配一个任意字符(不包括目录分隔符)
- **/:递归匹配任意子目录
{
"include": [
"src/**/*"
]
}这个配置表示编译器会包含 src 目录下的所有文件(包括子目录中的文件)。如果 include 和 exclude 都被指定,编译器会先根据 include 包含文件,然后根据 exclude 排除文件。 需要注意的是,include 中的路径可以是文件或目录,可以使用相对或绝对路径。此外,如果没有特殊指定,exclude 默认情况下会排除 node_modules、bower_components、jspm_packages 和 outDir 目录。
exclude
exclude 属性用于指定需要从编译中排除的文件或目录的模式匹配列表。它也支持使用通配符,与 include 的通配符规则相同。例如:
{
"exclude": [
"node_modules",
"dist"
]
}这个配置表示编译器会排除 node_modules 和 dist 目录下的所有文件。需要注意的是,exclude 只对 include 包含的文件有效。如果某个文件被显式地列在 files 中,那么它不会被 exclude 排除。
此外,exclude 中的路径可以是文件或目录,可以使用相对或绝对路径。如果没有特殊指定,exclude 默认情况下会排除 node_modules、bower_components、jspm_packages 和 outDir 目录。
编译选项 compilerOptions
target
target 是 compilerOptions 中非常重要的一个配置项,它用于指定编译后的 JavaScript 文件的目标 ECMAScript 版本。不同的目标版本会影响代码的兼容性和性能。
- 支持的版本:
target支持的版本包括es3、es5、es6(或es2015)、es2016、es2017、es2018、es2019、es2020、es2021、es2022和esnext。例如,如果你设置为 "target": "es5",编译器会将 TypeScript 代码转换为兼容 ES5 的 JavaScript 代码。 - 默认值:如果没有指定
target,默认值是es3。但在现代项目中,通常建议至少设置为es5,因为 ES5 已经被广泛支持,且提供了更好的性能和特性。 - 实际影响:选择不同的
target会对编译结果产生显著影响。例如,如果你的目标环境是较新的浏览器或 Node.js 环境,可以选择es2015或更高版本,这样可以利用现代 JavaScript 的新特性,如箭头函数、解构赋值等。但如果需要兼容旧的浏览器(如 IE11),则需要设置为es5或es3。
module
module 配置项用于指定编译后的 JavaScript 模块格式。不同的模块格式适用于不同的运行环境。
- 支持的模块格式:
module支持的模块格式包括none、commonjs、amd、umd、system、es6(或es2015)、es2020、es2022、esnext、node16和nodenext。例如,如果你设置为 "module": "commonjs",编译器会将 TypeScript 代码转换为 CommonJS 模块格式,这在 Node.js 环境中非常常用。 - 默认值:如果没有指定
module,默认值取决于target。如果target是ES3或ES5,默认值是commonjs;否则默认值是ES6。 - 实际影响:选择合适的模块格式对项目的运行环境至关重要。例如,如果你的项目运行在浏览器中,可以选择
umd或es6;如果运行在 Node.js 环境中,commonjs 是一个不错的选择。不同的模块格式会影响代码的加载方式和执行效率。
strict
strict 是一个非常有用的配置项,它用于启用一系列严格的类型检查选项,帮助开发者编写更安全、更可靠的代码。
- 启用严格模式:当设置为 "
strict":true时,TypeScript 会启用以下严格的类型检查选项:noImplicitAny:不允许在没有明确指定类型的情况下使用any类型。noImplicitThis:不允许在没有明确指定this类型的情况下使用this。strictNullChecks:启用严格的空值检查,不允许将null或undefined赋值给其他类型的变量。strictFunctionTypes:启用严格的函数类型检查,防止函数参数双向协变。strictPropertyInitialization:确保类的非undefined属性在构造函数中初始化。
- 默认值:如果没有指定
strict,默认值是false,即不启用严格的类型检查。 - 实际影响:启用
strict模式可以显著提高代码的质量和安全性。虽然它可能会增加一些开发成本,但长远来看,它有助于减少潜在的错误和问题,特别是在大型项目中。
outDir
outDir 配置项用于指定编译后的 JavaScript 文件的输出目录。通过合理配置 outDir,可以更好地组织项目的文件结构。
- 指定输出目录:例如,如果你设置为 "
outDir": "./dist",编译器会将所有编译后的 JavaScript 文件输出到项目根目录下的dist文件夹中。 - 默认值:如果没有指定
outDir,编译器会将编译后的文件输出到与源文件相同的目录中。这可能会导致源代码和编译后的代码混在一起,不利于项目的维护。 - 实际影响:合理配置
outDir可以帮助你更好地管理项目的文件结构,避免源代码和编译后的代码混淆。同时,它也有助于自动化工具(如 Webpack)更好地处理项目的构建过程。
文件处理配置
allowJs
allowJs 是 compilerOptions 中的一个配置项,用于指定是否允许编译器编译 .js 和 .jsx 文件。默认情况下,TypeScript 编译器只处理 .ts 和 .tsx 文件,但通过设置 allowJs 为 true,可以将 .js 和 .jsx 文件也纳入编译范围。
实际作用:当你需要在 TypeScript 项目中同时处理 JavaScript 文件时,这个配置项非常有用。
例如,你可能有一些遗留的 JavaScript 代码需要逐步迁移到 TypeScript,或者你希望在项目中使用一些 JavaScript 库。通过设置 "allowJs": true,TypeScript 编译器会将 .js 文件原样输出到指定的 outDir 目录中,而不会对其进行类型检查。
project-root/
├── src/
│ ├── index.ts
│ └── utils.js
├── tsconfig.json
└── package.json在 tsconfig.json 中设置:
{
"compilerOptions": {
"allowJs": true,
"outDir": "./dist"
},
"include": [
"src/**/*"
]
}那么,utils.js 会被编译器原样输出到 dist 目录中,而 index.ts 会被编译为 JavaScript 并输出到 dist 目录。
checkJs
checkJs 是 compilerOptions 中的一个配置项,用于指定是否对 .js 文件进行类型检查。默认情况下,TypeScript 编译器不会对 .js 文件进行类型检查,但通过设置 checkJs 为 true,可以启用对 .js 文件的类型检查。
实际作用:当你希望在 JavaScript 文件中也享受 TypeScript 的类型检查功能时,这个配置项非常有用。它可以帮助你在 JavaScript 代码中发现潜在的类型错误,提高代码质量。
**需要注意的是,启用 checkJs 时,allowJs 也必须为 true,否则编译器不会处理 .js 文件。
假设项目结构如下:
project-root/
├── src/
│ ├── index.ts
│ └── utils.js
├── tsconfig.json
└── package.json在 tsconfig.json 中设置:
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"outDir": "./dist"
},
"include": [
"src/**/*"
]
}那么,utils.js 不仅会被原样输出到 dist 目录中,还会进行类型检查。如果 utils.js 中存在类型错误,编译器会报错。
declaration
declaration 是 compilerOptions 中的一个配置项,用于指定是否为每个 .ts 文件生成对应的 .d.ts 声明文件。默认情况下,declaration 为 false,即不生成声明文件。
实际作用:当你需要为你的 TypeScript 项目生成类型声明文件时,这个配置项非常有用。声明文件 .d.ts 可以被其他项目引用,从而在不包含源代码的情况下,提供类型信息。这对于开发库或框架非常有用,因为你可以将声明文件发布到 npm,供其他开发者使用。
假设项目结构如下:
project-root/
├── src/
│ ├── index.ts
│ └── utils.js
├── tsconfig.json
└── package.jsontsconfig.json 中设置:
{
"compilerOptions": {
"declaration": true,
"outDir": "./dist",
"declarationDir": "./types"
},
"include": [
"src/**/*"
]
}那么,编译器会为 src 目录下的每个 .ts 文件生成对应的 .d.ts 文件,并将它们输出到 types 目录中。例如,src/index.ts 会生成 types/index.d.ts,src/utils.ts 会生成 types/utils.d.ts。
模块与路径配置
moduleResolution
moduleResolution 是 compilerOptions 中用于指定模块解析策略的配置项。它决定了 TypeScript 编译器如何查找模块文件,这对于项目中模块的加载和运行至关重要。
支持的值:
node:这是默认值,也是最常用的模块解析策略。它模拟了 Node.js 的模块解析方式,会按照node_modules目录的结构来查找模块。例如,当你导入一个模块import * as module from 'some-module'时,编译器会先在当前目录下的node_modules文件夹中查找some-module,如果找不到,会逐级向上查找,直到找到为止。classic:这是 TypeScript 早期的模块解析策略,主要用于支持旧版本的 TypeScript 项目。它会按照文件路径的顺序来查找模块,而不是按照node_modules的结构。例如,如果导入路径是相对路径,编译器会直接在当前目录下查找对应的文件。bundler:这种策略主要用于支持打包工具(如 Webpack)的模块解析方式。它假设模块已经被打包工具处理过,因此不会按照常规的文件路径来查找模块,而是直接使用打包后的路径。这种策略适用于使用打包工具进行构建的项目,可以提高编译速度并减少对源文件的依赖。
实际影响:选择合适的模块解析策略可以确保模块的正确加载和运行。例如,在大多数现代项目中,推荐使用
node模块解析策略,因为它与 Node.js 的生态系统兼容性最好。如果你的项目使用了打包工具,可以考虑使用bundler策略来优化编译性能。
baseUrl
baseUrl 是 compilerOptions 中用于指定模块解析的基准目录的配置项。它通常与 paths 配置项一起使用,用于配置模块的路径映射。
实际作用:当你使用相对路径导入模块时,
baseUrl指定了这些相对路径的起点。例如,如果你设置"baseUrl": "./",那么当你导入模块import * as module from 'some-module'时,编译器会从项目根目录开始查找some-module。如果没有设置baseUrl,编译器会默认以tsconfig.json文件所在的目录为基准。案例:假设你的项目结构如下:
project-root/ ├── src/ │ ├── index.ts │ └── utils/ │ └── helper.ts ├── tsconfig.json └── package.json如果你在
tsconfig.json中设置:{ "compilerOptions": { "baseUrl": "./src", "moduleResolution": "node" } }那么,当你在
src/index.ts中导入utils/helper.ts时,可以使用相对路径import * as helper from 'utils/helper',编译器会从src目录开始查找utils/helper.ts。
paths
paths 是 compilerOptions 中用于配置模块路径映射的配置项。它通常与 baseUrl 配合使用,允许你自定义模块的解析路径。
实际作用:
paths允许你为模块名指定一个或多个路径,从而实现模块的别名映射。这对于大型项目中模块的组织和管理非常有用,可以简化模块的导入路径,提高代码的可读性和可维护性。例如,你可以为src目录下的模块设置别名,或者为第三方库指定特定的版本路径。案例:假设你的项目结构如下:
project-root/ ├── src/ │ ├── index.ts │ └── utils/ │ └── helper.ts ├── node_modules/ │ └── some-library/ │ └── index.js ├── tsconfig.json └── package.json如果你在
tsconfig.json中设置:{ "compilerOptions": { "baseUrl": "./src", "paths": { "@utils/*": ["utils/*"], "@library": ["../node_modules/some-library"] } } }那么,你可以在
src/index.ts中使用别名导入模块:import * as helper from '@utils/helper'; import * as library from '@library';编译器会根据
paths配置将@utils/helper解析为src/utils/helper,将@library解析为node_modules/some-library。# 6. 性能与辅助配置
incremental
incremental 是 compilerOptions 中用于优化编译性能的配置项。它通过启用增量编译功能,帮助加快大型项目的编译速度。
- 实际作用:当设置为
"incremental": true时,TypeScript 编译器会在第一次编译后生成一个tsbuildinfo文件,该文件存储了编译过程中的信息。在后续的编译中,编译器会利用这些信息,只重新编译那些自上次编译以来发生更改的文件,从而显著减少编译时间。例如,对于一个包含 100 个文件的项目,如果只修改了其中一个文件,增量编译只会重新编译这个修改过的文件及其依赖的文件,而不是整个项目。 - 案例:假设你的项目结构如下:如果你在
project-root/ ├── src/ │ ├── index.ts │ └── utils.ts ├── tsconfig.json └── package.jsontsconfig.json中设置:那么,当你第一次运行{ "compilerOptions": { "incremental": true, "outDir": "./dist" }, "include": [ "src/**/*" ] }tsc命令时,编译器会生成一个tsbuildinfo文件。在后续的编译中,如果你只修改了utils.ts,编译器只会重新编译utils.ts和依赖它的文件,而不是整个项目。
sourceMap
sourceMap 是 compilerOptions 中用于调试的配置项。它允许生成源代码映射文件(.js.map),这些文件将编译后的 JavaScript 代码与原始的 TypeScript 源代码关联起来。
- 实际作用:当设置为
"sourceMap": true时,TypeScript 编译器会为每个编译后的 JavaScript 文件生成一个对应的.js.map文件。这些文件包含了映射信息,使得开发者可以在浏览器的开发者工具中直接调试 TypeScript 源代码,而不是编译后的 JavaScript 代码。这对于调试大型项目非常有用,因为它可以让你更直观地查看和调试代码。 - 案例:假设你的项目结构如下:如果你在
project-root/ ├── src/ │ ├── index.ts │ └── utils.ts ├── tsconfig.json └── package.jsontsconfig.json中设置:那么,编译器会为{ "compilerOptions": { "sourceMap": true, "outDir": "./dist" }, "include": [ "src/**/*" ] }src/index.ts和src/utils.ts分别生成dist/index.js.map和dist/utils.js.map文件。当你在浏览器中打开项目并使用开发者工具时,你可以直接看到和调试 TypeScript 源代码,而不是编译后的 JavaScript 代码。
emitDeclarationOnly
emitDeclarationOnly 是 compilerOptions 中用于优化编译输出的配置项。它允许只生成类型声明文件(.d.ts),而不生成 JavaScript 文件。
- 实际作用:当设置为
"emitDeclarationOnly": true时,TypeScript 编译器只会生成.d.ts文件,而不会生成.js文件。这对于开发库或框架非常有用,因为你可以将声明文件发布到npm,供其他开发者使用,而不需要包含实际的 JavaScript 实现代码。这可以减少包的大小,同时提供完整的类型信息。 - 案例:假设你的项目结构如下:如果你在
project-root/ ├── src/ │ ├── index.ts │ └── utils.ts ├── tsconfig.json └── package.jsontsconfig.json中设置:那么,编译器会为{ "compilerOptions": { "emitDeclarationOnly": true, "declaration": true, "declarationDir": "./types" }, "include": [ "src/**/*" ] }src/index.ts和src/utils.ts分别生成types/index.d.ts和types/utils.d.ts文件,而不会生成对应的.js文件。这样,你就可以将这些声明文件发布到npm,供其他开发者使用。
类型检查与代码规范
noImplicitAny
noImplicitAny 是 compilerOptions 中用于增强类型安全性的配置项。当设置为 true 时,TypeScript 编译器会禁止在没有明确指定类型的情况下使用 any 类型。
- 实际作用:在开发过程中,
any类型可能会导致类型安全问题,因为它可以表示任何类型,从而绕过类型检查。启用noImplicitAny可以帮助开发者在编写代码时更明确地指定类型,减少潜在的错误。例如,当你声明一个变量但没有指定其类型时,编译器会报错,提示你需要明确指定类型。 - 案例:假设你有以下代码:如果在
let x; // 没有指定类型 x = 123; x = "hello";tsconfig.json中设置:那么,编译器会报错,提示变量{ "compilerOptions": { "noImplicitAny": true } }x的类型没有明确指定。你需要显式地指定类型,例如:let x: number | string; x = 123; x = "hello";
noUnusedLocals
noUnusedLocals 是 compilerOptions 中用于优化代码质量的配置项。当设置为 true 时,TypeScript 编译器会报错,提示存在未使用的局部变量。
- 实际作用:在大型项目中,未使用的变量可能会导致代码混乱和维护困难。启用
noUnusedLocals可以帮助开发者及时发现并清理这些未使用的变量,从而提高代码的可读性和可维护性。例如,当你声明了一个变量但没有在代码中使用它时,编译器会报错。 - 案例:假设你有以下代码:如果在
function example() { let unusedVariable = 42; console.log("Hello, world!"); }tsconfig.json中设置:那么,编译器会报错,提示变量{ "compilerOptions": { "noUnusedLocals": true } }unusedVariable未被使用。你需要删除或使用这个变量,例如:function example() { let usedVariable = 42; console.log(usedVariable); }
noImplicitReturns
noImplicitReturns 是 compilerOptions 中用于增强代码规范的配置项。当设置为 true 时,TypeScript 编译器会报错,提示函数的所有可能路径都必须有明确的返回值。
- 实际作用:在开发过程中,函数的某些分支可能没有返回值,这可能会导致代码行为不符合预期。启用
noImplicitReturns可以帮助开发者确保每个函数的所有分支都有明确的返回值,从而提高代码的可读性和可维护性。例如,当你编写一个函数但某些分支没有返回值时,编译器会报错。 - 案例:假设你有以下代码:如果在
function example(condition: boolean): string { if (condition) { return "Condition is true"; } // 缺少返回值 }tsconfig.json中设置:那么,编译器会报错,提示函数{ "compilerOptions": { "noImplicitReturns": true } }example的某些分支没有返回值。你需要为所有分支添加返回值,例如:function example(condition: boolean): string { if (condition) { return "Condition is true"; } return "Condition is false"; }