tsconfig配置理解
tsconfig.json 将会把一个文件夹转换为「项目」,如果不指定任何 exclude 或者 files,则包含在 tsconfig.json 中的所有文件夹中的所有文件都会被包含在编译中。
和 node 指令运行 js 时寻找 node_modules 一样
运行方(tsc)指令来运行 ts 会有一个寻找 tsconfig.json 的过程
运行方(转译ts的工具)在这里是: tsx/esno,其他的如:ts-node、tsc、webpack-ts-loader ...
🤔 其他具有 配置文件 并且 默认识别配置文件 机制的前端构建工具(webpack、vite),其实也有这种寻找机制(只会从当前执行命令根目录寻找),而因为 tsconfig的寻址 和 node_modules寻址 完全一致因此拿出来对比
都没找到配置文件时会有写死的 默认配置,对应 node_modules 的全局依赖
注意:只有直接使用 tsc 指令才会自动寻址,当写了指令参数如 tsc src/index.ts 将会理解为不需要自定义配置而使用默认配置(就是 tsc --init 自动生成的那份配置),即使项目目录中有 tsconfig.json
顶层配置项
我们使用 tsc --init 生成出来的 tsconfig.json 只有 compilerOptions ,但其实和 compilerOptions 同级的其他配置也是需要了解的
外层 tsconfig 配置
{
"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 项目,什么都不配置时有一份默认配置
这也是很多入门教程:
- 创建
src/index.ts编写内容 - 直接运行
npx tsc指令 - 查看生成的
src/index.js内容
看这些步骤和结果,可以猜测: TS 至少默认设置了 入口、出口、转译级别
而入口的配置就是 tsconfig.json 外层的 files、include、exclude 属性
这里要注意:
- 不指定files选项值时,includes的默认值为
["**/*"](包含当前项目中所有文件) - 指定了files选项值时,includes 的默认值为
[](根据依赖分析自动识别需要包含的文件)
如:files 设置为 ['src/index.ts'] 则该文件依赖的其他文件也会被归纳为编译对象
即 index.ts 依赖 user.ts 时,不需要在 files 中指定 user.ts , user.ts 会自动纳入待编译文件
而 files 配置项在复杂项目时并不适用,此时我们使用 include、exclude 指定目录,而不是配置 files
exclude 的默认值是 ["node_modules", "bower_components", "jspm_packages"] + outDir选项指定的值, 如果有一些文件实在不想被 TS 扫描,才手动配置(node_modules不会被覆盖而是合并)
注意以下情况,exclude 中指定的被忽略文件,将会无效,依然被扫描:👇
import操作符types操作符///<reference操作符- 在
files选项中添加配置的方式
如 umi 项目配置:👇
{
"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文件,不能编写类型语法,会有类型提示?
复用配置references、extends
references- 参考extends- 继承
| _ | _ |
|---|---|
| extends | 继承其他配置文件,如基础配置 tsconfig.base.json |
| references | 一个对象的数组,指明要引用的工程。path属性可以指向到包含tsconfig.json文件的目录,或者直接指向到配置文件本身(名字是任意的) |
这里 extends 才是我们平时理解的复用,多份 tsconfig 配置 作用于 单入口,tsconfig.base.json.
因此不过多介绍 extends,把重点放在理解 references上
references 是为了让我们针对多入口配置 单/多份tsconfig 作用于 不同的入口
- 我们可以想象 单元测试项目 结构:👇
/
├── src/
│ └── index.ts
├── test/
│ └── index-tests.ts
└── tsconfig.jsontest 脚本运行在 nodejs 环境,src 源码运行在浏览器环境
👆 虽然可以分别创建不同的 tsconfig ,运行两次独立的tsc。但是这样并不优雅,并且启动多次 tsc 的损耗也不小
- 我们也可以想象 现代构建工具的项目 结构:👇
/
├── build/
│ ├── build.dev.ts
│ └── build.prod.ts
├── src/
│ └── index.ts
├── vite.config.ts
├── webpack.config.ts
└── tsconfig.json除了 src 目录运行在浏览器环境,其他ts运行在 nodejs环境
- 我们再想象 SSR项目 :👇
/
├── service/
│ └── index.ts
├── src/
│ └── index.ts
└── tsconfig.json前端项目和后端node项目放在同一个目录下开发
👆 这些情况都是现代前端项目常见且不可避免的问题,我们希望不给浏览器端代码使用 node api ,同理 不给 node 环境使用 浏览器api
此时可以定义一份 主tsconfig ,references 一份其他入口配置的 tsconfig
ViteDemo的references和extends
在 Vite 创建的应用项目时,分为 浏览器端 以及 本地构建服务的node环境
👇 viteDemo/tsconfig.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
{
"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 文件
{
"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
🤔 如何区分
dev和prod阶段的ts转译?
不需要考虑
prod, 现代构建工具 ts转译不处理语法,而是直接擦除,由其他工具如babel、swc做语法转译
🤔 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"
(()=>{ // es6 箭头函数
const msg:string = 'im a ts file' // es5 const 常量声明
console.log(msg)
})👇 运行带参数指令 tsc src/index.ts 转译结果:
(function () {
var msg = 'im a ts file'; // ❌ 不是 ESNEXT 语法
console.log(msg);
});👇 运行不带参数指令 tsc 转译结果:
"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中编写了非法语法也不报错
👇 因此我们可以得出跳过的 好处 是:
- 节省编译/静态扫描耗时
- 忽略第三方库
d.ts的错误导致项目无法启动 - 忽略第三方库使用不同版本的
typescript编写的d.ts,在项目typescript版本下不兼容而导致的报错,项目无法启动
👇 同时带来的 坏处/影响 是:
- 第三方库or业务代码自己编写的
d.ts,没有错误提示,当编写了非法语法的内容时,在项目中体现为库文件不生效,但是需要手动排查是否语法问题or其他问题
vite官方文档 👇
一些库(如:
vue)不能很好地与"isolatedModules": true共同工作你可以在上游仓库(
vue)修复好之前暂时使用"skipLibCheck": true来缓解这个错误
types/typeRoots
和 lib 相同,作用于 ts 使用哪些库文件:
lib指定:内置库文件,默认根据 target 自动匹配vscode/node_modules/typestypes/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别名功能相同
"paths": {
"@/*": "src/*"
}js相关allowJs/checkJs
ts项目 默认不支持识别 js文件 ,而开启 allowJs 后,将允许 ts 和 js 混用
这适用于项目初期从 js 迁移到 ts,逐步迁移
允许使用
js的话,有个小注意事项,就是tsc编译,依然会对js做编译生成新的js此时如果没有设置
输出目录,即输出目录为原文件位置,2个js将会同名同后缀而编译报错
而开启 checkJs 后,js 内容将具有隐式(自动)类型定义,如下:
let a = 1
a = '' // VSCode 报红类型错误👆 VSCode 报红类型错误,并且执行 tsc 编译也将报错终止
在不开启 checkJS 时,因为 js 是弱类型语法,,这么赋值是合法的
因此有时我们会看到一些 include 配置 ['*.js'] ,确实是会让 js 具有一定的 ts 能力
jsx
react: Emit.jsfiles with JSX changed to the equivalent React.createElement callsreact-jsx: Emit.jsfiles with the JSX changed to _jsx callsreact-jsxdev: Emit.jsfiles with the JSX changed to _jsx callspreserve: Emit.jsxfiles with the JSX unchangedreact-native: Emit.jsfiles with the JSX unchanged
👆 这些模式只在代码生成阶段起作用,类型检查并不受影响
使用 React17 时,用 react-jsx/react-jsxdev 即可省略 import React
同时留意是否需要升级 typescript
esModuleInterop
允许 import 和 commonjs 混用,避免有些老旧的第三方库只有 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 相当于仅执行类型扫描,不执行转译
与项目 dev 和 build 过程都独立,不影响项目编译性能
格式相关
| _ | _ |
|---|---|
| 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 库声明文件
declare module '*.css';
declare module '*.json';declare module 声明语法,一般是创建 env.d.ts