TypeScript 配置分离:聊聊为什么你的前端项目需要多个 tsconfig
📝 Tip
单一的 tsconfig 配置可能导致前端业务代码意外使用 Node.js API,或构建脚本无法正确访问 Node.js 模块。通过配置分离,我们可以为不同类型的代码提供正确的类型检查和开发体验。
🤔 从一个可能大概也许应该maybe真实的 Bug 说起
小明最近在开发一个商城项目,他写了一段看似没问题的代码:
// src/utils/fileHelper.ts
import fs from 'fs';
export function saveUserData(data: any) {
// 试图在浏览器中使用 Node.js 的 fs 模块
fs.writeFileSync('userData.json', JSON.stringify(data));
}
// src/pages/UserProfile.tsx
import { saveUserData } from '../utils/fileHelper';
function UserProfile() {
const handleSave = () => {
saveUserData({ name: 'John' }); // 💥 运行时崩溃!
};
return <button onClick={handleSave}>保存</button>;
}
这段代码竟然能通过 TypeScript 的类型检查!但在浏览器中运行时必然会报错,因为浏览器环境中根本没有 fs
模块。
为什么会这样? 因为项目使用了单一的 tsconfig.json
,其中包含了 @types/node
的类型定义,导致前端代码误用了 Node.js API。
🌟 理想的开发体验是什么样的?
在良好的配置下,当小明试图在前端代码中使用 fs
模块时,应该立即得到类型错误:
import fs from 'fs'; // ❌ 错误:Cannot find module 'fs' or its corresponding type declarations.ts(2307)
而在构建脚本中,则应该能够正常使用 Node.js API:
// build/scripts/generateReport.ts
import fs from 'fs'; // ✅ 正确:可以使用 Node.js API
import path from 'path'; // ✅ 正确:可以使用 Node.js API
import webpack from 'webpack'; // ✅ 正确:可以使用构建工具相关模块
// ... 构建相关代码
💡 配置分离方案
📂 项目结构
my-project/
├── 📄 tsconfig.json # 基础配置
├── 📄 tsconfig.app.json # 前端业务代码配置
├── 📄 tsconfig.node.json # 构建脚本配置
├── 📁 src/ # 前端代码目录
│ ├── 📁 components/
│ └── 📁 pages/
└── 📁 build/ # 构建相关代码
└── 📁 scripts/
⚙️ 配置文件内容
1. tsconfig.json(基础配置)
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
2. tsconfig.app.json(前端业务代码配置)
{
"compilerOptions": {
// 👇 面向浏览器环境
"target": "ES2020",
"module": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
// 👇 现代前端工程化配置
"moduleResolution": "bundler",
"jsx": "preserve",
// 👇 严格的类型检查
"strict": true,
"noUnusedLocals": true,
// 👇 路径别名配置
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
3. tsconfig.node.json(构建脚本配置)
{
"compilerOptions": {
// 👇 面向 Node.js 环境
"target": "ESNext",
"module": "CommonJS",
// 👇 构建工具需要的类型
"types": ["node", "webpack"],
// 👇 Node.js 模块解析
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
// 👇 其他必要配置
"strict": true,
"skipLibCheck": true
},
"include": ["build/**/*"],
"exclude": ["node_modules"]
}
🎯 实际效果对比
🚫 未分离配置时:
// src/utils/api.ts
import fs from 'fs'; // ⚠️ 错误地通过类型检查
import path from 'path'; // ⚠️ 错误地通过类型检查
import axios from 'axios'; // ✅ 正确
export async function fetchData() {
// 可能误用 Node.js API
const configPath = path.join(__dirname, 'config.json');
const config = fs.readFileSync(configPath, 'utf-8'); // 💥 运行时错误
// 实际应该只使用浏览器 API
return axios.get('/api/data');
}
✅ 分离配置后:
// src/utils/api.ts
import fs from 'fs'; // ❌ 类型错误:找不到模块 'fs'
import path from 'path'; // ❌ 类型错误:找不到模块 'path'
import axios from 'axios'; // ✅ 正确
export async function fetchData() {
return axios.get('/api/data'); // ✅ 只使用浏览器环境的 API
}
// build/scripts/build.ts
import fs from 'fs'; // ✅ 正确:构建脚本可以使用 Node.js API
import path from 'path'; // ✅ 正确:构建脚本可以使用 Node.js API
import webpack from 'webpack'; // ✅ 正确:构建脚本可以使用构建工具
// ... 构建相关代码
🎁 配置分离带来的好处
-
🛡️ 更安全的类型检查
- 防止前端代码误用 Node.js API
- 避免构建脚本使用浏览器特有 API
-
💻 更好的开发体验
- IDE 提示更准确
- 错误提示更及时
- 代码补全更相关
-
🚀 更高的开发效率
- 减少运行时错误
- 提前发现类型问题
- 加快开发调试速度
📝 最佳实践建议
1. 使用 npm 脚本简化类型检查
{
"scripts": {
"type-check": "npm run type-check:app && npm run type-check:node",
"type-check:app": "tsc -p tsconfig.app.json --noEmit",
"type-check:node": "tsc -p tsconfig.node.json --noEmit"
}
}
2. 配置 VS Code 工作区
// .vscode/settings.json
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
🔍 常见问题
Q: 什么时候需要分离配置?
当你的项目包含以下情况时:
- 同时有前端代码和构建脚本
- 使用了现代构建工具(webpack/vite 等)
- 需要自定义构建流程
Q: 如何处理共享类型定义?
可以创建一个共享的类型目录:
shared/
└── types/
├── global.d.ts # 全局类型
└── common.ts # 共享类型
📚 延伸阅读
🎉 总结
TypeScript 配置分离不仅是一个技术选择,更是工程化的必要实践。通过合理的配置分离,我们可以:
- 避免环境 API 误用
- 提供更精准的类型检查
- 改善开发体验
- 提高代码质量