Skip to content

tsconfig配置理解

tsconfig.json 将会把一个文件夹转换为「项目」,如果不指定任何 exclude 或者 files,则包含在 tsconfig.json 中的所有文件夹中的所有文件都会被包含在编译中。

node 指令运行 js 时寻找 node_modules 一样

运行方(tsc)指令来运行 ts 会有一个寻找 tsconfig.json 的过程

运行方(转译ts的工具)在这里是: tsx/esno,其他的如:ts-nodetscwebpack-ts-loader ...

🤔 其他具有 配置文件 并且 默认识别配置文件 机制的前端构建工具(webpackvite),其实也有这种寻找机制(只会从当前执行命令根目录寻找),而因为 tsconfig的寻址node_modules寻址 完全一致因此拿出来对比

都没找到配置文件时会有写死的 默认配置,对应 node_modules 的全局依赖

注意:只有直接使用 tsc 指令才会自动寻址,当写了指令参数如 tsc src/index.ts 将会理解为不需要自定义配置而使用默认配置(就是 tsc --init 自动生成的那份配置),即使项目目录中有 tsconfig.json

顶层配置项

我们使用 tsc --init 生成出来的 tsconfig.json 只有 compilerOptions ,但其实和 compilerOptions 同级的其他配置也是需要了解的

外层 tsconfig 配置

json
{
  "compilerOptions": {},

  // ✨ 以下与compilerOptions同级的配置项
  "compileOnSave": true,
  "files": [],
  "include": [],
  "exclude": [],
  "extends": "",
  "references": []
}
配置项说明
compilerOptions编译选项,详见compilerOptions
--
compileOnSave让IDE在保存文件的时候根据tsconfig.json重新生成文件,要想支持这个特性需要Visual Studio 2015, TypeScript1.8.4以上并且安装atom-typescript插件
files指定一个包含相对或绝对文件路径的列表
include指定一个文件glob匹配模式列表,注意:src/* 只扫描src一级目录
exclude指定一个文件glob匹配模式列表
extends继承其他配置文件,如基础配置 tsconfig.base.json
references一个对象的数组,指明要引用的工程。path属性可以指向到包含tsconfig.json文件的目录,或者直接指向到配置文件本身(名字是任意的)

我们一般不使用 TS 的 编译功能,不需要实时编译 compileOnSave

入口配置files、include、exclude

我们创建 TS 项目,什么都不配置时有一份默认配置

这也是很多入门教程:

  1. 创建 src/index.ts 编写内容
  2. 直接运行 npx tsc 指令
  3. 查看生成的 src/index.js 内容

看这些步骤和结果,可以猜测: TS 至少默认设置了 入口、出口、转译级别

而入口的配置就是 tsconfig.json 外层的 files、include、exclude 属性

这里要注意:

  • 不指定files选项值时,includes的默认值为["**/*"] (包含当前项目中所有文件)
  • 指定了files选项值时,includes 的默认值为[] (根据依赖分析自动识别需要包含的文件)

如:files 设置为 ['src/index.ts'] 则该文件依赖的其他文件也会被归纳为编译对象

index.ts 依赖 user.ts 时,不需要在 files 中指定 user.tsuser.ts 会自动纳入待编译文件

files 配置项在复杂项目时并不适用,此时我们使用 include、exclude 指定目录,而不是配置 files

exclude 的默认值是 ["node_modules", "bower_components", "jspm_packages"] + outDir选项指定的值, 如果有一些文件实在不想被 TS 扫描,才手动配置(node_modules不会被覆盖而是合并)

注意以下情况,exclude 中指定的被忽略文件,将会无效,依然被扫描:👇

  1. import操作符
  2. types操作符
  3. ///<reference操作符
  4. files选项中添加配置的方式

如 umi 项目配置:👇

json
{
  "include": [
    "mock/**/*",
    "src/**/*",
    "typings/**/*",
    "config/**/*",
    ".eslintrc.js",
    ".stylelintrc.js",
    ".prettierrc.js",
    "mock/*"
  ],
  "exclude": ["node_modules", "build", "dist", "scripts", "src/.umi/*", "webpack"]
}

可以看出一般:

  • include
    • src目录下的业务代码(路由页面、组件、工具方法等)
    • 根目录下的配置文件(eslintrc、prettierrc、stylelintrc)
  • exclude
    • 打包产物 dist/
    • nodejs脚本 scripts/
    • umi 自动生成的内容 src/.umi/

🤔 为什么包括js文件,不能编写类型语法,会有类型提示?

js相关allowjscheckjs

复用配置references、extends

Project References - ts Docs

  • references - 参考
  • extends - 继承
__
extends继承其他配置文件,如基础配置 tsconfig.base.json
references一个对象的数组,指明要引用的工程。path属性可以指向到包含tsconfig.json文件的目录,或者直接指向到配置文件本身(名字是任意的)

这里 extends 才是我们平时理解的复用,多份 tsconfig 配置 作用于 单入口tsconfig.base.json.

因此不过多介绍 extends,把重点放在理解 references

references 是为了让我们针对多入口配置 单/多份tsconfig 作用于 不同的入口

  1. 我们可以想象 单元测试项目 结构:👇
/
├── src/
│   └── index.ts
├── test/
│   └── index-tests.ts
└── tsconfig.json

test 脚本运行在 nodejs 环境,src 源码运行在浏览器环境

👆 虽然可以分别创建不同的 tsconfig ,运行两次独立的tsc。但是这样并不优雅,并且启动多次 tsc 的损耗也不小

  1. 我们也可以想象 现代构建工具的项目 结构:👇
/
├── build/
│   ├── build.dev.ts
│   └── build.prod.ts
├── src/
│   └── index.ts
├── vite.config.ts
├── webpack.config.ts
└── tsconfig.json

除了 src 目录运行在浏览器环境,其他ts运行在 nodejs环境

  1. 我们再想象 SSR项目 :👇
/
├── service/
│   └── index.ts
├── src/
│   └── index.ts
└── tsconfig.json

前端项目和后端node项目放在同一个目录下开发

👆 这些情况都是现代前端项目常见且不可避免的问题,我们希望不给浏览器端代码使用 node api ,同理 不给 node 环境使用 浏览器api

此时可以定义一份 主tsconfigreferences 一份其他入口配置的 tsconfig

ViteDemo的references和extends

Vite 创建的应用项目时,分为 浏览器端 以及 本地构建服务的node环境

👇 viteDemo/tsconfig.json

json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": { "@/*": ["./src/*"] }
  },
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],

  "extends": "@vue/tsconfig/tsconfig.web.json",
  "references": [
    {
      "path": "./tsconfig.config.json"
    }
  ]
}

👆 1. 入口范围指定为业务代码,不包括构建工具文件 2. 使用 extends 复用 @vue/ 官方库中的浏览器环境web配置;3. 使用 references 复用同一个 tsc 进程而不是复用配置文件,处理其他入口(node)和相应的配置

👇 viteDemo/tsconfig.config.json

json
{
  "compilerOptions": {
    "composite": true,
    "types": ["node"]
  },
  "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],

  "extends": "@vue/tsconfig/tsconfig.node.json"
}

👆 同理 1.入口范围指定为vite构建工具文件 2. 使用 extends 复用 @vue/ 官方库中的 node 配置

tsc --init

👇 tsc --init 生成 tsconfig.json 文件

json
{
  "compilerOptions": {
    /* Basic Options */
    "target": "es5",                                /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
    "module": "commonjs",                           /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */

    /* Strict Type-Checking Options */
    "strict": true,                                 /* Enable all strict type-checking options. */

    "esModuleInterop": true,                        /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */

    /* Advanced Options */
    "skipLibCheck": true,                           /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true        /* Disallow inconsistently-cased references to the same file. */
  }
}

target

ts 转译后的 js 语法级别

dev 阶段一般用 ESNEXT

🤔 如何区分 devprod 阶段的 ts转译

不需要考虑 prod, 现代构建工具 ts转译不处理语法,而是直接擦除,由其他工具如 babelswc 做语法转译

🤔 ts 不参与构建阶段,库开发呢?

库开发一般也用其他成熟的构建工具(esbuild/tsup),项目简单到可以使用 tsc 时则,可以通过指令指定配置文件 tsc -p tsconfig.[build/dev].json

上面提到:

注意:只有直接使用 tsc 指令才会自动寻址,当写了指令参数如 tsc src/index.ts 将会理解为不需要自定义配置而使用默认配置(就是 tsc --init 自动生成的那份配置),即使项目目录中有 tsconfig.json

👇 src/index.ts 编写箭头函数 (es6语法)

并且配置 tsconfig.json 中的 "target": "es5""target": "ESNEXT"

ts
(()=>{ // es6 箭头函数
  const msg:string = 'im a ts file' // es5 const 常量声明
  console.log(msg)
})

👇 运行带参数指令 tsc src/index.ts 转译结果:

js
(function () {
  var msg = 'im a ts file'; // ❌ 不是 ESNEXT 语法
  console.log(msg);
});

👇 运行不带参数指令 tsc 转译结果:

js
"use strict";
(() => {
  const msg = 'im a ts file'; // ✨ ESNEXT 语法
  console.log(msg);
});

👆 "use strict"; 严格模式 由 "alwaysStrict": true 配置生效,一般不使用 ts 的转译功能,由 现代前段构建工具 处理是否输出严格模式

lib相关lib/skipLibCheck

根据 target 的值 lib 有相应的默认值

因为该项与 taget 自动匹配,不用手动配置,手动配置一般出于优化 ts扫描性能 的目的

lib: [ "[target].core.d.ts" ]

lib 用于指定要包含在编译中的库文件,常用配置是: ["esnext", "dom"],手动配置可以减少一点默认配置带来的额外冗余损耗

skipLibCheck 我们看到 tsc --init 生成的 默认配置 中是开启这个的

Understanding TypeScript’s skipLibCheck Once and For All

skipLibCheck 作用是:跳过 所有库文件(d.ts)的扫描检查(包括类型、语法检查),即使d.ts中编写了非法语法也不报错

👇 因此我们可以得出跳过的 好处 是:

  1. 节省编译/静态扫描耗时
  2. 忽略第三方库 d.ts的错误导致项目无法启动
  3. 忽略第三方库使用不同版本的typescript编写的 d.ts,在项目typescript 版本下不兼容而导致的报错,项目无法启动

👇 同时带来的 坏处/影响 是:

  1. 第三方库or业务代码自己编写的 d.ts,没有错误提示,当编写了非法语法的内容时,在项目中体现为库文件不生效,但是需要手动排查是否语法问题or其他问题

vite官方文档 👇

一些库(如:vue)不能很好地与 "isolatedModules": true 共同工作

你可以在上游仓库(vue)修复好之前暂时使用 "skipLibCheck": true 来缓解这个错误

types/typeRoots

lib 相同,作用于 ts 使用哪些库文件:

  • lib 指定:内置库文件,默认根据 target 自动匹配 vscode/node_modules/types
  • types/typeRoots 指定第三方库文件,默认所有 node_modules/@types/

和手动配置 lib 同理,因为默认所有 node_modules/@types/,不用手动配置,手动配置一般出于优化 ts扫描性能 的目的

配置了 types ,代表仅安装 @types 不会生效,需要手动添加到了 types

如:Vite 的 配置了 types: ["node"],安装 @types/node

因为 nodejs 暴露 api 是使用 js编写的,为了获得其包的类型定义,需要安装名为 @types/node 的第三方包

path相关rootDir/baseUrl/paths

  • rootDir - tsconfig 生效的目录
  • baseUrl - 用于引入模块时省略编写路径(会让业务代码的路径不清晰)(建议用别名
  • paths - 路径别名webpack 别名功能相同
json
"paths": {
  "@/*": "src/*"
}

js相关allowJs/checkJs

ts项目 默认不支持识别 js文件 ,而开启 allowJs 后,将允许 tsjs 混用

这适用于项目初期从 js 迁移到 ts,逐步迁移

允许使用 js 的话,有个小注意事项,就是 tsc 编译,依然会对 js 做编译生成 新的js

此时如果没有设置 输出目录,即输出目录为原文件位置,2个js将会 同名同后缀 而编译报错

而开启 checkJs 后,js 内容将具有隐式(自动)类型定义,如下:

js
let a = 1
a = '' // VSCode 报红类型错误

👆 VSCode 报红类型错误,并且执行 tsc 编译也将报错终止

在不开启 checkJS 时,因为 js弱类型语法,,这么赋值是合法的

因此有时我们会看到一些 include 配置 ['*.js'] ,确实是会让 js 具有一定的 ts 能力

jsx

jsx -tsconfig 官方文档

  • react: Emit .js files with JSX changed to the equivalent React.createElement calls
  • react-jsx: Emit .js files with the JSX changed to _jsx calls
  • react-jsxdev: Emit .js files with the JSX changed to _jsx calls
  • preserve: Emit .jsx files with the JSX unchanged
  • react-native: Emit .js files with the JSX unchanged

👆 这些模式只在代码生成阶段起作用,类型检查并不受影响

使用 React17 时,用 react-jsx/react-jsxdev 即可省略 import React

同时留意是否需要升级 typescript

esModuleInterop

允许 importcommonjs 混用,避免有些老旧的第三方库只有 commonjs 输出, 导致项目无法启动

我们看到 tsc --init 生成的 默认配置 中是开启这个的

isolatedModules

是否将每个文件作为单独的模块,即:不考虑每个文件的导入导出

因为 ts模块化 包括 变量空间类型空间,导入导出也是,因此 tsc 具有 全局分析 的能力,全局扫描后分析出需要导入的是类型还是变量

而在现代前端构建项目中,仅仅对 ts 做擦除处理,因此并不会有全局分析的过程,仅仅针对单个文件进行分析需要擦除的语法,此时若存在不清晰的导入导出,将会无法擦拭

如:

  • 从其它模块类型后未使用该类型
  • 重导出(expot { Type } from)其它模块的类型
  • 引入其它模块的const enum并使用
  • 使用namespace语法

因此在现代前端构建项目里,我们人为的让ts模块化的导入导出清晰起来,就是使用 import type {}

让 转移器(擦除:esbuild) 不需要分析 被导入文件 内容是类型还是变量

而开启 isolatedModules 就可以让静态扫描 IDE(VSCode) 给出相应的提示。即:不开启此配置也可以,但是需要人为约定编程规范

所以 Vite 等仅擦除的工具会建议开启,而使用全局扫描的 tsc 时则无所谓

理解Typescript配置项: isolateModules

  • tsc 的类型分析是项目级(全局分析)的,因此比起其它单文件分析类型的编译器,它能比其它的知道更多类型信息,因此对于非模块隔离的代码也能灵活处理
  • 项目级的类型分析比单文件的类型分析更加强大,但代价就是它的运行速度会比单文件分析更慢。
  • 如果使用 ts-loader, 它会用tsc分析整个项目。然而当它的 transpileOnly 选项为 true 时,它也将降级为仅对当前文件进行分析,此时也必须要求模块的隔离性,请启用 isolateModules

noEmit

不输出转译结果,现代前端项目,把静态类型扫描交给 IDE 处理,但是当需要在提交代码前hook 执行命令扫描时,需要使用到 tsc指令

此时 配置 noEmit 相当于仅执行类型扫描,不执行转译

与项目 devbuild 过程都独立,不影响项目编译性能

格式相关

__
noImplicitAny不允许隐式的any,默认false(允许)
noFallthroughCasesInSwitch检查switch语句包含正确的break
noImplicitReturns不允许隐式的return,设为true后,如果函数没有返回值则会提示
noUnusedLocals检查有没有未使用的局部变量
strictNullChecks检查空值,检查有可能为null的地方,对象链式调用确保不会是null

👆 和 eslint 作用有点重合

其他

__
removeComments将编译后的文件中的注释删掉,设为true即删掉注释
sourceMap生成 sourceMap 文件,这个文件里保存的,是转换后代码的位置,和对应的转换前的位置。有了它,出错的时候,通过断点工具可以直接显示原始代码,而不是转换后的代码(识别并使用.map文件的是浏览器)
alwaysStrice编译后的文件是否开启严格模式
module编译结果使用的模块化标准: None, CommonJS, AMD, System, UMD, ES6/ES2015, ES2020, ESNext
resolveJsonModule支持引入json,但是必须把编译后module设置为commonjs
noResolve不自动导入第三方类型如: import $ from 'jquery'将可以使用jquery库,但是没有语法提示,可能在明确没有错误的场景编译时使用(但是这样就不要用ts不就好了...)

👆 module - 和 target 转译js语法同理,现代前段构建工具 转译处理模块化语法,不需要 ts 的转译功能

👆 resolveJsonModule 同理,不需要 ts 的转译能力,而是开启识别非ts的文件即可👇

使用 .d.ts 库声明文件

ts
declare module '*.css';
declare module '*.json';

declare module 声明语法,一般是创建 env.d.ts