讲解

真实项目不会逐文件敲 tsc 参数,一切编译行为都收敛到 tsconfig.json。tsc 不带参数运行时读取当前目录的 tsconfig.json;tsc -p 路径 指定配置;tsc --init 生成带注释的模板。配置分三大块:compilerOptions(编译行为)、include/exclude(纳管哪些文件)、extends/references(继承与项目拆分)。

compilerOptions 里最值得理解的选项分四组。目标与模块:target 决定输出 JS 的语法级别(现代项目 es2022 起步),module + moduleResolution 决定模块系统(打包器项目用 esnext + bundler,纯 Node ESM 用 nodenext),lib 决定有哪些内置 API 类型(不写则按 target 带默认集,含 DOM)。严格性:strict 是一组开关的总开关(下一章逐个拆),新项目务必 true。输出:outDir 产物目录、rootDir 源码根、sourceMap 调试映射、declaration 产出 .d.ts(库项目开)、noEmit 只检查不产出(应用配 Babel/esbuild 时的标准姿势)。体验:skipLibCheck 跳过 .d.ts 检查(显著加速,几乎所有项目都开)、esModuleInterop 兼容 CommonJS 默认导入、forceConsistentCasingInFileNames 防大小写事故。

多项目仓库用 references(项目引用)做增量拆分;extends 则用于共享基座配置——@tsconfig/node22 这类官方预设 + 覆盖差异字段,是现代模板的常见形态。

示例

一份典型的应用级 tsconfig.json(json 块带 file: 约定,会写入沙盒参与后续编译):

// file: tsconfig.json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "esnext",
    "moduleResolution": "bundler",
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "outDir": "dist",
    "rootDir": "src",
    "sourceMap": true,
    "noEmit": true
  },
  "include": ["src"]
}

配一个源码文件(注意 rootDir/include 指向 src/):

// file: src/config-check.ts
export function appName(): string {
  return "ohmydocs";
}

用这份配置对整个项目做类型检查(bash 块在沙盒中执行,tsc 来自仓库本地依赖):

tsc -p tsconfig.json && echo "类型检查通过"

生成一份模板配置看看官方默认值(输出到 /dev/null 演示 --init 的行为,实际项目直接生成后编辑):

mkdir -p init-demo && cd init-demo && tsc --init >/dev/null && grep -c '"' tsconfig.json && cd ..

常见坑

  • include 与文件实际位置不符:rootDir 设了 src 但 include 没纳管,或文件在 src 之外,tsc 报「不在 rootDir 下」——三者要对齐。
  • target 太低坑了语法:默认 target 很老(历史原因),async/await 会被降级成状态机;显式写 es2022 以上。
  • 复制网上的十年老配置:很多流传配置带着 module: commonjs + target: es5,与现代打包器项目不匹配;从官方模板或框架脚手架出发。
  • 多份 tsconfig 各自为政:用 extends 收敛公共项,差异项显式覆盖;tsconfig.app.json / tsconfig.node.json 分工是 Vite 模板的好榜样。

小结

tsconfig 是编译行为的单一事实来源:记牢 target/module/moduleResolution/strict/skipLibCheck/noEmit 六个高频项,extends 复用基座。下一章拆开 strict 家族逐个看。