TypeScript 不是浏览器或 Node.js 原生支持的语言,因此它必须经过编译(Compile) 步骤转换为 JavaScript 后才能运行。理解这一流程和核心配置是工程化的基础,否则很容易在遇到报错时一头雾水。
22.4.1 从 .ts 到 .js:编译过程概览
一次完整的 TypeScript 编译过程大致分为几步:
- 解析(Parsing)
TypeScript 编译器 tsc 读取 .ts 文件,构建出抽象语法树(AST),这个阶段也会检查基础语法错误。
- 类型检查(Type Checking)
基于 tsconfig.json 中指定的规则,对整个项目的类型关系进行校验。这一步完全独立于代码生成,即使类型检查不通过,也可以选择继续输出 JavaScript(由 noEmitOnError 控制)。
- 降级转换(Transformation)
将 TypeScript 独有的语法(如类型注解、接口、泛型、枚举)擦除;将 ES6+ 语法(如箭头函数、async/await)按配置的 target 转换成对应版本的 ECMAScript 语法。
- 代码生成(Emit)
根据配置输出浏览器或 Node.js 可以直接运行的 .js 文件,同时还可选择生成 .d.ts 类型声明文件、.map Source Map 文件等。
22.4.2 核心工具:tsc 与 tsx
- tsc:官方编译器,负责类型检查 + 代码编译。常用于生产构建前的编译步骤。
常用命令:
tsc # 读取 tsconfig.json 编译整个项目
tsc --watch # 监视模式,文件变更时自动重编译
tsc -p tsconfig.json # 指定配置文件
- tsx / ts-node:第三方工具,可以在开发阶段直接运行
.ts文件而无需预先编译,内部使用 esbuild 或 SWC 做快速转译。两者大幅缩短了开发循环,适合脚本开发和本地调试。
22.4.3 tsconfig.json:一切编译行为的开关
tsconfig.json 是 TypeScript 项目的“大脑”,它决定了编译器如何工作、检查哪些规则、输出什么格式。理解它,就能控制整个编译流程。一个实用的基础配置通常长这样:
{
"compilerOptions": {
"target": "ES2020", // 输出 JS 的目标版本
"module": "ESNext", // 使用的模块系统
"moduleResolution": "bundler", // 模块解析策略
"strict": true, // 开启所有严格模式选项
"esModuleInterop": true, // 兼容 CommonJS 模块
"skipLibCheck": true, // 跳过 .d.ts 检查,加快编译
"outDir": "./dist", // 输出目录
"rootDir": "./src", // 源码根目录
"sourceMap": true, // 生成 Source Map,便于调试
"declaration": true, // 生成 .d.ts 声明文件
"jsx": "react-jsx" // JSX 转换模式(React 17+ 用)
},
"include": ["src"], // 编译包含的文件
"exclude": ["node_modules"] // 排除的目录
}
几个最关键的选项解读:
target
决定编译后的 JavaScript 语法版本。比如设置为 ES2015,所有箭头函数会被转为普通 function;设置为 ES2022 则保留更现代的语法。现代构建工具(如 Vite、Webpack)通常会在此基础上再做一次打包和 polyfill,所以很多项目设为 ESNext,把降级交给打包工具处理。
module与moduleResolution
这两个选项成对出现,控制模块语法如何转换以及如何解析模块路径。
- 纯 Node.js 项目常用
module: "commonjs",moduleResolution: "node"。 - 如果使用 ESM 或现代打包器,常用
module: "ESNext"配合moduleResolution: "bundler"(TypeScript 5.0+),以避免不必要的模块转换。
strict
强烈建议设为 true。它会同时开启 noImplicitAny、strictNullChecks、strictFunctionTypes 等子规则,能在编译期捕获大量潜在 bug。初期学习阶段觉得麻烦可以暂时关闭个别子规则,但团队项目必须开启。
esModuleInterop
CommonJS 模块(如 module.exports = ...)在 ESM 默认导入(import x from 'x')时需要此选项。开启后几乎不会遇到 “无法调用 default” 之类的奇怪错误。
include与exclude
精确控制编译器扫描哪些文件。通常让编译器只处理 src 目录下的源码,避免去检查 node_modules 或测试文件——这样可以大幅提升编译速度,并减少不相关的类型错误。
22.4.4 与前端构建工具的配合
在实际项目中,你几乎不会直接用 tsc 输出最终产物,而是将 TypeScript 集成到 Vite、Webpack 等打包流水线中。
- Vite
使用 esbuild 进行快速转译,默认不执行类型检查(只做语法转换,效率极高)。类型检查通过 vue-tsc 或其他 tsc --noEmit 命令在单独的过程(如 CI 或 Git Hook)中完成。
典型配置:vite.config.ts 引入官方 @vitejs/plugin-vue 或仅用 vite-plugin-checker 做异步检查。
- Webpack
结合 ts-loader 或 babel-loader(配合 @babel/preset-typescript)处理 .ts 文件。ts-loader 可同时做类型检查,但构建速度较慢;babel-loader 只转译不检查,速度更快,团队常结合 fork-ts-checker-webpack-plugin 在独立线程中执行类型检查。
无论哪种方案,记住一条核心原则:转译(transpile)和类型检查(type check)可以解耦。开发时利用快速转译工具(如 esbuild、SWC、Babel)获得秒级热更新,而将耗时的类型检查放在构建前或提交前执行,这才是现代前端工程的最佳实践。
22.4.5 运行时的类型安全性补充
必须清楚一个事实:TypeScript 的类型系统是编译时的,代码编译成 JavaScript 后所有类型信息会被擦除,运行时没有任何强制类型保障。如果你从外部数据源(API 返回值、用户输入)获得数据,编译器无法预先知道它的结构,这是类型安全的断点。
为了避免“编译时一切正确,运行时报错”的情况,常用策略有:
- 接口合约工具:如
zod、io-ts,在运行时校验数据结构和类型,并推导出 TypeScript 类型。 - 代码生成:根据后端 Swagger/OpenAPI 规范自动生成请求和响应类型,避免手动维护类型声明。
- 防御式编程:关键路径上仍要用
typeof、instanceof或条件判断确保数据的真实类型。
理解了 TS 的编译流程和配置,就拿到了在项目中真正用好 TypeScript 的钥匙——用最少的配置、最合理的工具链,把类型安全的价值发挥到最大,而不是被配置细节绊住脚步。