Vite 为什么不默认做 TypeScript 类型检查?
Vite 支持 TypeScript,但默认只转译,不做完整类型检查。很多人迁移后会疑惑:页面能跑,tsc --noEmit 却报错。原因是 Vite 把开发反馈速度放在前面,类型检查需要理解整个项目的类型图,如果塞进每次模块转换,HMR 会明显变慢。
Vite 负责转译,不负责兜底
.ts、.tsx 文件会被 esbuild 快速转成 JavaScript,类型标注会被擦除,JSX 和新语法也会被处理。但 esbuild 不会像 TypeScript 编译器那样做完整类型分析,所以“Vite 能运行”不等于“类型安全”。
tstype User = { id: number } const user: User = { id: '1' } // 需要 tsc 才能稳定拦住
tsconfig 怎么配
Vite 项目建议使用 moduleResolution: "bundler",它更贴近现代打包器对 ESM、exports 和条件导出的解析方式。isolatedModules 建议打开,能提前暴露不适合单文件转译的写法。noEmit 让 TypeScript 只检查,不输出文件,避免和 Vite 构建产物混在一起。
json{ "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "isolatedModules": true, "noEmit": true, "skipLibCheck": true }, "include": ["src", "vite.config.ts"] }
skipLibCheck 是现实取舍。业务项目开启它可以减少依赖声明文件带来的噪音和耗时;基础库、SDK 或类型要求极高的项目要谨慎,因为它可能掩盖第三方类型问题。
类型检查放到哪里
本地开发可以依赖编辑器提示,也可以接入 vite-plugin-checker。团队协作时,CI 必须执行 tsc --noEmit,否则有人关闭编辑器提示后,类型错误仍可能进入主干。
tsimport checker from 'vite-plugin-checker' export default defineConfig({ plugins: [checker({ typescript: true })] })
路径别名要同时配置 Vite 和 TypeScript。resolve.alias 影响运行和构建,paths 影响编辑器和类型检查;只配一边,就会出现能运行但编辑器报错,或编辑器正常但构建失败。
追问
为什么 Vite 不直接检查类型?
因为 Vite 的核心链路是按需转译模块,而完整类型检查是项目级静态分析。把它放进每次请求,会拖慢冷启动和 HMR。取舍是先获得运行反馈,再用编辑器、checker 或 CI 保证类型安全。踩坑点是把 Vite build 通过当成类型通过。
vite-plugin-checker 和 tsc --noEmit 怎么选?
本地想尽快看到错误,可以用 vite-plugin-checker。上线前要稳定兜底,应该跑 tsc --noEmit。插件更贴近开发体验,但大项目里也会占内存和 CPU。边界是合并门禁不要依赖某个开发者的编辑器。
isolatedModules 为什么会让一些写法报错?
它要求每个文件都能被独立安全转译,这和 esbuild 的工作方式一致。只导出类型却按值导入、部分 namespace 写法,都可能因此暴露问题。限制看起来烦,但能提前发现运行时会消失的类型代码。取舍是少一点 TS 写法自由,换来构建链路一致。
路径别名为什么要配两份?
Vite 和 TypeScript 是两个解析系统。只配 Vite,编辑器和 tsc 可能找不到模块;只配 TS,构建时可能解析失败。很多人以为是缓存问题,其实是配置源不一致。monorepo 里还要看 workspace 包的 exports 和声明文件。
生产构建前还需要 ESLint 吗?
需要,ESLint 和 TypeScript 关注点不同。TS 主要检查类型关系,ESLint 更擅长发现 Hook 依赖、未处理 Promise、禁用 API 和潜在逻辑问题。取舍是规则越多 CI 越慢,所以要区分阻断规则和建议规则。不要把 lint 只当格式化工具。