TypeScript 配置文件(tsconfig.json)是用于配置 TypeScript 项目的重要文件。它允许开发者自定义 TypeScript 编译器的行为,指定编译选项、文件包含与排除规则、输出目录等。通过合理配置 tsconfig.json,我们可以根据项目需求进行灵活的 TypeScript 编译设置。
本文将全面解读 tsconfig.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": "./", // 指定 TypeScript 编译器查找 TypeScript 文件的根目录,通常用于控制输入文件的搜索路径。假设你的 TypeScript 文件存储在项目的根目录下,你可以配置为 './' | |
| "composite": true, // 生成额外的元数据文件,这些文件可以帮助构建工具(包括 TypeScript 自身的 --build 模式)更快地确定项目是否已经被构建。 | |
| "removeComments": true, // 删除注释 | |
| "noEmit": true, // 不输出文件 | |
| "importHelpers": true, // 通过 tslib 引入辅助工具函数 | |
| "downlevelIteration": true, // 是否添加对迭代器和生成器的补丁(es6 + 无需关注) | |
| "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": [], //typeRoots 用来指定声明文件或文件夹的路径列表,如果指定了此项,则只有在这里列出的声明文件才会被加载 | |
| "types": [], //types 用来指定需要包含的模块,只有在这里列出的模块的声明文件才会被加载进来 | |
| "allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块默认导入 | |
| "esModuleInterop": true, // 通过创建命名空间实现 CommonJS 兼容性 | |
| "resolveJsonModule": true, // 自动解析 JSON 文件 | |
|     /* Source Map */ | |
| "sourceRoot": "", // TypeScript 源代码所在的目录 | |
| "mapRoot": "", //mapRoot 用于指定调试器找到映射文件而非生成文件的位置,指定 map 文件的根路径,该选项会影响.map 文件中的 sources 属性 | |
| "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 时 (默认值), 类的实例属性会编译成简单的赋值来定义。
举例说明:
| class Foo { | |
| bar = 123; | |
| } | 
如果 useDefineForClassFields 为 true:
| var Foo = class { | |
| constructor() { | |
| Object.defineProperty(this, "bar", { | |
| configurable: true, | |
| writable: true, | |
| value: 123 | |
| }); | |
|   } | |
| } | 
如果 useDefineForClassFields 为 false (默认):
| var Foo = class { | |
| constructor() { | |
| this.bar = 123; | |
|   } | |
| } | 
useDefineForClassFields 是一个用于类字段声明的配置选项。它在 TypeScript 2.7 版本中被引入,并在后续版本中得到改进和优化。
当开启 useDefineForClassFields 选项时,类字段的声明方式发生了改变,如下所示:
| // 开启 useDefineForClassFields | |
| class MyClass { | |
| myField = 42; | |
| } | |
| // 等价于未开启 useDefineForClassFields | |
| class MyClass { | |
| constructor() { | |
| this.myField = 42; | |
|   } | |
| } | 
在未开启 useDefineForClassFields 选项的情况下,类字段的初始化通常需要放在构造函数中。而开启了该选项后,可以直接在类的定义中进行初始化,类似于在构造函数中使用赋值语句。
开启 useDefineForClassFields 的好处包括:
- 简洁性:代码更加简洁易读,不再需要在构造函数中手动初始化类字段,使得代码更加简洁。
- 可读性:通过直接在类定义中初始化字段,可以更清晰地看到字段的初始值,提高了代码的可读性。
- 类字段支持类型推断:开启该选项后,TypeScript 可以正确推断类字段的类型,不再需要显式地指定类型。
# isolatedModules
控制是否将每个文件作为单独的模块处理。
默认情况下,该选项为 true。这意味着:
- 每个文件都作为单独的模块处理。
- 文件之间的变量、函数、类等不会相互影响。
- 文件只能访问自己导出的内容。
如果设置为 false:
- 整个项目的文件会合并成一个模块。
- 文件之间可以相互访问变量、函数、类等成员。
- 一个文件可以直接使用另一个文件暴露出来的内容。
通常我们希望每个文件是独立的模块,所以保持该选项为 true。
但是有些场景下需要关闭该选项:
- 当项目文件间有重名的类时,需要设置为 false 以便合并为一个类。
- 当不同文件需要共享某些变量或状态时,需要设置为 false。
总之,isolatedModules 用于控制文件之间是否独立。它可以处理一些特殊的场景。
# paths
paths 选项用于配置模块导入时的路径映射。通过使用 paths,你可以在项目中定义模块的别名,使得导入模块时可以使用这些别名,从而简化代码,减少路径错误,并提高可维护性。
以下是 paths 选项的使用示例:
- 假设你的项目目录结构如下:
| project | |
| |-- src | |
| | |-- components | |
| | | |-- Button.ts | |
| | | |-- Card.ts | |
| | |-- utils | |
| | | |-- helpers.ts | |
| |-- tsconfig.json | 
- 在 tsconfig.json 文件中,你可以这样配置 paths 选项:
| { | |
| "compilerOptions": { | |
| "baseUrl": "./src", | |
| "paths": { | |
| "@components/*": ["components/*"], | |
| "@utils/*": ["utils/*"] | |
|     } | |
|   } | |
| } | 
在这个例子中:
- "baseUrl": "./src" 表示项目中所有导入模块的相对路径都是相对于 "src" 目录的。
- "paths" 配置了两个路径映射:- "@components/*" 是一个模块别名,它将匹配任何以 "@components/" 开头的导入路径。
- "@utils/*" 是另一个模块别名,它将匹配任何以 "@utils/" 开头的导入路径。
 
- 使用别名进行模块导入:
在你的代码中使用这些别名进行模块导入,而不需要使用相对路径或绝对路径。
| // 使用别名导入模块 | |
| import { Button } from "@components/Button"; | |
| import { Card } from "@components/Card"; | |
| import { someHelperFunction } from "@utils/helpers"; | |
| // 使用别名进行导入后的代码更加简洁 | 
通过使用 paths 选项,你可以定义更多的模块别名,根据你的项目结构和需求来简化导入语句,提高代码的可读性和可维护性
# baseUrl
用于指定 TypeScript 编译器在解析模块导入路径时的基础路径。通过设置 baseUrl ,可以简化模块导入语句,使得导入模块时的路径更加清晰和简洁。
假设项目结构如下:
| project | |
| |-- src | |
| | |-- components | |
| | | |-- Button.ts | |
| | | |-- Card.ts | |
| | |-- utils | |
| | | |-- helpers.ts | |
| |-- tsconfig.json | 
默认情况下,TypeScript 编译器会将所有导入语句视为相对于包含它们的文件的路径。这意味着,如果你在 Button.ts 中想导入 Card.ts ,通常需要使用相对路径来完成导入,例如:
| import { Card } from '../Card'; | 
但是,随着项目规模增长,导入语句中的相对路径可能会变得非常复杂和深层嵌套,这可能导致代码可读性降低,维护困难。
为了解决这个问题,可以使用 baseUrl 选项,将模块导入路径的基础路径设置为项目中的某个目录,通常是项目的 src 目录。
在 tsconfig.json 文件中设置 baseUrl 的用法如下:
| { | |
| "compilerOptions": { | |
| "baseUrl": "./src" | |
|   } | |
| } | 
上面的配置将 baseUrl 设置为项目中的 src 目录。这意味着,在所有的模块导入语句中,TypeScript 编译器会将导入路径解析为相对于 ./src 目录的路径。因此,我们可以简化模块导入语句,如下所示:
| // 使用 baseUrl 后,导入语句更简洁 | |
| import { Card } from 'components/Card'; | |
| import { someHelperFunction } from 'utils/helpers'; | 
通过设置 baseUrl 选项,简化模块导入语句,提高代码的可读性和可维护性,尤其在大型项目中,这个功能尤为有用。
# rootDirs
它用于指定 TypeScript 编译器应该搜索源文件的根目录。通常情况下,TypeScript 编译器会搜索项目中的所有源文件,并根据相对路径和引用关系进行编译,保留源文件结构。但有时,你可能希望将多个不同目录下的源文件编译到同一个输出目录中,这时就可以使用 rootDirs 选项。
示例:
假设你有一个项目,其中包含两个源文件目录:src1 和 src2,它们分别包含一些 TypeScript 文件。你希望将这两个目录中的文件编译到一个输出目录,比如 dist 中。
首先,你可以在 tsconfig.json 文件中配置 rootDirs 选项:
| { | |
| "compilerOptions": { | |
| "outDir": "./dist", | |
| "rootDirs": ["./src1", "./src2"] | |
|   } | |
| } | 
在上面的示例中,我们指定了两个根目录:./src1 和 ./src2。TypeScript 编译器会在这两个目录中搜索源文件,并将它们编译到 ./dist 目录中。这样,无论源文件在哪个目录中,编译后的输出都会放在同一个输出目录中。
# rootDir
rootDir 是用来指定 TypeScript 编译器应该从哪个根目录开始查找源代码文件。
通常情况下,你会将 rootDir 设置为项目的根目录,这样 TypeScript 编译器将会从这个目录开始递归地查找源文件。
当你只有一个根目录时,使用 rootDir 就足够了。
# typeRoots
typeRoots 用于指定 TypeScript 类型声明文件(.d.ts 文件)的根目录。类型声明文件用于提供类型信息,以帮助 TypeScript 编译器理解第三方库或模块的类型。通过配置 typeRoots 告诉 TypeScript 编译器在哪些目录中查找类型声明文件。
默认情况下,类型文件查找规则:
- 在项目根目录下的 node_modules/@types 目录中查找与你的项目依赖库匹配的类型声明文件。这是因为许多第三方库的类型声明文件通常会安装在 @types 目录下。
- 在项目根目录下的 node_modules 目录中查找与你的项目依赖库匹配的类型声明文件。
- 在全局安装的 TypeScript 类型声明文件目录中查找,通常是 lib 目录。
- 如果上述步骤都未找到匹配的类型声明文件,TypeScript 将会在相对于导入模块的文件夹中查找类型声明文件。这是一种后备机制,用于处理那些没有被正确配置的第三方库或自定义模块。
当你指定了 typeRoots 后,TypeScript 编译器会从你指定的路径去引入声明文件,而不是 node_modules/@types。
例如,如果你的配置如下:
| { | |
| "compilerOptions": { | |
| "typeRoots": ["./typings", "./vendor/types"] | |
|   } | |
| } | 
那么编译器将会包含所有在./typings 和./vendor/types 下面的包,而不会包含任何在./node_modules/@types 下面的包。所有的路径都是相对于 tsconfig.json 文件的。
如果你希望在自定义路径下没有找到匹配的声明文件时,编译器能够回退到默认的 node_modules/@types 路径,你可以在 typeRoots 中包含这个路径。例如:
| { | |
| "compilerOptions": { | |
| "typeRoots": ["./typings", "./vendor/types", "./node_modules/@types"] | |
|   } | |
| } | 
这样,即使在 ./typings 和 ./vendor/types 中没有找到匹配的声明文件,编译器也会去 ./node_modules/@types 中查找。
# types
types 用于指定哪些类型声明文件应该被包含在编译过程中。通过配置 types,你可以选择性地包含或排除特定的类型声明文件,types 支持配置模块名。
默认情况下,所有可见的 @types 包都会被包含在你的编译中。例如, ./node_modules/@types/ , ../node_modules/@types/ , ../../node_modules/@types/ 等任何包含文件夹的 node_modules/@types 中的包都被视为可见。
如果指定了 types,则只有列出的包才会被包含在全局范围内。例如:
| { | |
| "compilerOptions": { | |
| "types": ["node", "jest", "express"] | |
|   } | |
| } | 
这个 tsconfig.json 文件只会包含 ./node_modules/@types/node, ./node_modules/@types/jest , 和 ./node_modules/@types/express . 其他在 node_modules/@types/* 下的包将不会被包含。
还可以自定义类型,这是你自定义的类型声明文件,它们位于项目中的某个位置,例如:
| d.// my-custom-types.d.ts | |
| declare module 'my-module' { | |
|   // 在这里定义你的自定义类型或扩展已有的类型 | |
| interface MyCustomType { | |
|     // ... | |
|   } | |
|   // 如果需要,也可以导出变量、函数等 | |
| const myVar: string; | |
| function myFunction(): void; | |
| } | |
| { | |
| "compilerOptions": { | |
| "types": ["my-module"] | |
|   } | |
| } | 
通过这样的配置,TypeScript 编译器将仅包含指定的类型声明文件,而忽略其他类型声明文件。这有助于减少不必要的类型信息加载,提高编译速度。如果不指定 types 选项,TypeScript 编译器将根据默认规则自动包含项目中的所有类型声明文件。
如果同时配置了 typeRoots 和 types,types 选项的设置将优先生效,即 TypeScript 编译器将根据 typeRoots 中包含的且 types 指定的文件来包含类型声明。
如果只配置了 typeRoots 而没有配置 types,那么 TypeScript 编译器将根据 typeRoots 中指定的目录来查找类型声明文件,但不会自动包含这些文件。你需要确保在代码中显式地导入需要的类型声明。
所以,你可以理解为 typeRoots 是用来限定类型声明的搜索文件范围的,types 才是用来指定具体要包含的类型声明。
# 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
通常情况下,如果一个模块没有默认导出,那么只能导入该模块中的类型,不能导入它的类型定义文件。
举例来说,存在一个没有默认导出的模块:
| // utils.ts | |
| export function util1() {} | 
对应的类型定义文件:
| // utils.d.ts | |
| export function util2(); | 
默认情况下,不能导入 utils.d.ts 中的类型:
| // index.ts | |
| import { util2 } from './utils.d.ts'; // Error | 
但如果设置了 allowImportingTsExtensions 为 true, 则可以导入:
| { | |
| "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
用于允许从没有默认导出的模块中默认导入。
正常情况下,只有当一个模块明确导出一个默认值时,才可以使用默认导入语法导入该模块:
| // module.ts | |
| export default function foo() {} | |
| // other.ts | |
| import func from './module'; | 
但是有些模块没有默认导出值,这时默认导入语法将不被允许:
| // module.ts | |
| export function foo() {} | |
| // other.ts | |
| import func from './module'; // Error | 
allowSyntheticDefaultImports 选项允许使用默认导入语法,即使这个模块没有默认导出值:
| { | |
| "compilerOptions": { | |
| "allowSyntheticDefaultImports": true | |
|   } | |
| } | |
| // other.ts | |
| import func from './module'; // Allowed | 
这在一些特殊场景下是有用的,例如允许默认导入 CommonJS 模块。
| // 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 模块平滑地默认导入的效果。
但是非必要场景下不建议开启此选项。因为这会破坏默认导入语法的语义,使得无法明确知道一个模块是否有默认导出,增加理解和维护的难度。
# importHelpers
importHelpers 的作用是控制 TypeScript 是否在生成的 JavaScript 代码中引入辅助函数。
辅助函数是一些用来处理特定编译目标(如 ES5、ES6 等)的函数,它们包含在 TypeScript 核心库中,用于支持某些语言特性或编译后的代码结构。在某些情况下,引入这些辅助函数可能会导致生成的 JavaScript 代码更大,但在其他情况下,可以提供更好的兼容性和性能。
以下是 importHelpers 的两种配置方式的示例说明:
importHelpers: true(默认值):
默认情况下,TypeScript 会将辅助函数内联到每个编译后的文件中。这意味着每个生成的 JavaScript 文件都包含了这些辅助函数的副本。这样生成的代码可能会更大,但不需要额外的模块加载。
importHelpers: false:
如果将 importHelpers 设置为 false,TypeScript 将不再内联辅助函数,而是假定它们在运行时会由某个共享的模块提供。这可以减小生成的 JavaScript 文件的大小,但需要确保在运行时包含了 TypeScript 的辅助函数模块如 "tslib"。
一般来说,如果你的目标是生成较小的 JavaScript 文件并且你可以控制将 TypeScript 的辅助函数模块包含在项目中,那么将 importHelpers 设置为 false 可能是一个好选择。如果你不想关心这些辅助函数模块,或者希望在单个文件中包含所有必要的功能,那么可以保持默认值 true。
# downlevelIteration
downlevelIteration 的作用是控制 TypeScript 在编译时如何处理迭代器(Iterators)和生成器(Generators)。这个选项通常用于处理与迭代相关的代码,特别是当目标编译版本为 ES5 或更早的 JavaScript 版本时。
downlevelIteration: true(默认值):
当将 downlevelIteration 设置为 true 时,TypeScript 将使用一种转换技术来生成代码,以便在目标 JavaScript 版本中模拟支持迭代器和生成器。这样可以确保在较旧的 JavaScript 运行时中运行与迭代相关的代码,但可能会导致生成的代码变得更复杂和冗长。
downlevelIteration: false:
如果将 downlevelIteration 设置为 false,TypeScript 将不会进行迭代器和生成器的降级处理。这意味着生成的代码将依赖于运行时环境对迭代器和生成器的支持。如果你的目标运行时环境已经支持迭代器和生成器,设置为 false 可以生成更简洁的代码。
通常情况下,保持默认配置就可以了,如果你的目标是在较旧的 JavaScript 环境中运行你的代码,将 downlevelIteration 设置为 true 以确保代码的可用性和兼容性。而 ES6 和更高版本的 JavaScript 已经支持原生的迭代器和生成器,不需要额外的转换或降级处理即使配置为 true 也不会生成兼容代码。
# composite 和 references
composite 选项用于指定一个项目是否是一个 “组合” 项目,即它是否可以被其他项目引用。如果你设置了 "composite": true,那么 TypeScript 编译器将生成额外的元数据,以便其他项目可以更快地构建这个项目。
即使你的项目没有被其他项目引用(也就是说,你没有使用 references 选项),composite 选项仍然可以帮助提高构建性能。例如,如果你在持续集成(CI)环境中构建项目,那么使用 composite 选项可以帮助缩短构建时间。
references 选项用于指定一个项目依赖哪些其他项目。当你在主项目的 tsconfig.json 文件中设置了 references 选项后,你可以使用 tsc --build 命令来一次性构建主项目及其所有依赖项。
假设你有一个大型的 TypeScript 项目,它由多个子项目组成,每个子项目都有自己的源代码和 tsconfig.json 文件。例如:
| my-large-project/ | |
| ├── tsconfig.json | |
| ├── subproject1/ | |
| │   ├── tsconfig.json | |
| │   └── src/ | |
| │       └── index.ts | |
| └── subproject2/ | |
|     ├── tsconfig.json | |
|     └── src/ | |
|         └── index.ts | 
subproject1 和 subproject2 都是独立的子项目,它们各自有自己的源代码和配置。
你可以在每个子项目的 tsconfig.json 文件中设置 "composite": true,这样 TypeScript 编译器就知道这些项目是可以单独编译的。例如,subproject1/tsconfig.json 可能如下:
| { | |
| "compilerOptions": { | |
| "composite": true, | |
|     // 其他编译器选项... | |
| }, | |
|   // 文件和包含模式... | |
| } | 
然后,在主项目的 tsconfig.json 文件中,你可以使用 references 选项来引用这些子项目:
| { | |
| "compilerOptions": { | |
|     // 编译器选项... | |
| }, | |
| "references": [ | |
| { "path": "./subproject1" }, | |
| { "path": "./subproject2" } | |
|   ] | |
| } | 
现在,当你在主项目目录下运行 tsc --build 命令时,TypeScript 编译器会自动按照正确的顺序构建所有子项目。如果 subproject2 依赖于 subproject1,那么 TypeScript 会首先构建 subproject1,然后再构建 subproject2。
这种方式可以帮助你更好地组织和构建大型 TypeScript 项目,并且可以提高构建性能,因为 TypeScript 编译器可以跳过已经构建过的子项目。
如果你有一个小型项目,没有复杂的依赖关系,并且不需要特别关注编译性能,那么可以不使用这两个选项。
如果你的项目很大,包含多个源文件,并且你希望通过指定依赖关系来管理它们的编译顺序,可以配置 references 和 composite,当你拆分成多个工程后,会显著地加速类型检查和编译,减少编辑器的内存占用,还会改善程序在逻辑上进行分组。
