Appearance
Monorepo 调试技巧
调试挑战
在 Monorepo 环境中调试面临独特的挑战:
- 多包依赖关系: 需要理解包之间的依赖关系
- 复杂的工作流: 多个包同时开发和调试
- 环境一致性: 确保所有包在相同环境中运行
- 错误追踪: 跨包的错误追踪和调试
调试工具设置
1. IDE 配置
VS Code 配置
json
// .vscode/settings.json
{
"typescript.preferences.includePackageJsonAutoImports": "auto",
"typescript.surveys.enabled": false,
"files.watcherExclude": {
"**/node_modules/**": true,
"**/dist/**": true,
"**/build/**": true,
"**/.nuxt/**": true
},
"search.exclude": {
"**/node_modules": true,
"**/dist": true,
"**/build": true
}
}
调试配置
json
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug App",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}/packages/my-app",
"program": "${workspaceFolder}/packages/my-app/src/index.js",
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
]
}
]
}
2. 调试依赖设置
json
// package.json (root)
{
"devDependencies": {
"debug": "^4.3.4",
"source-map-support": "^0.5.21",
"tapable": "^2.2.1"
}
}
日志记录策略
1. 结构化日志
javascript
// packages/logging-utils/src/index.js
const debug = require('debug');
class Logger {
constructor(namespace) {
this.log = debug(`myorg:${namespace}`);
this.error = debug(`myorg:${namespace}:error`);
this.warn = debug(`myorg:${namespace}:warn`);
}
info(message, ...data) {
this.log(message, ...data);
}
error(message, ...data) {
this.error(message, ...data);
}
}
// 使用示例
const logger = new Logger('my-package');
logger.info('Processing data', { userId: 123 });
2. 跨包日志关联
javascript
// packages/tracing-utils/src/index.js
class TraceContext {
constructor(traceId = this.generateId()) {
this.traceId = traceId;
}
generateId() {
return Math.random().toString(36).substr(2, 9);
}
log(message, packageInfo) {
console.log(`[${this.traceId}] [${packageInfo}] ${message}`);
}
}
// 在包之间传递追踪上下文
const trace = new TraceContext();
myService.processData(trace);
源码映射配置
1. TypeScript 源码映射
json
// tsconfig.json
{
"compilerOptions": {
"sourceMap": true,
"inlineSourceMap": false,
"sourceRoot": "/",
"mapRoot": "./"
}
}
2. Webpack 源码映射
javascript
// webpack.config.js
module.exports = {
devtool: 'source-map', // 或 'eval-source-map' 用于开发
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: 'ts-loader',
options: {
configFile: path.resolve(__dirname, 'tsconfig.json'),
transpileOnly: false,
compilerOptions: {
sourceMap: true
}
}
}
]
}
]
}
};
调试特定包
1. 选择性调试
bash
# 只调试特定包
pnpm --filter my-package run debug
# 调试受影响的包
nx affected --target=debug
# 调试依赖链
nx affected --target=debug --with-deps
2. 环境变量控制
javascript
// packages/env-debug/src/index.js
const DEBUG_MODE = process.env.DEBUG_MODE === 'true';
const DEBUG_PACKAGES = (process.env.DEBUG_PACKAGES || '').split(',');
function shouldDebug(packageName) {
if (!DEBUG_MODE) return false;
if (DEBUG_PACKAGES.length === 0) return true;
return DEBUG_PACKAGES.includes(packageName);
}
module.exports = { shouldDebug };
依赖调试
1. 依赖版本检查
bash
# 检查依赖版本
pnpm why <package-name>
# 检查依赖树
pnpm list --depth 0
# 检查冲突依赖
pnpm audit
2. 本地包链接调试
json
// 临时覆盖包版本到本地
{
"pnpm": {
"overrides": {
"@myorg/utils": "link:../utils"
}
}
}
网络和 API 调试
1. HTTP 请求调试
javascript
// packages/http-debug/src/index.js
const axios = require('axios');
// 拦截请求和响应
axios.interceptors.request.use(
config => {
console.log('API Request:', config.method, config.url, config.data);
return config;
},
error => {
console.error('API Request Error:', error);
return Promise.reject(error);
}
);
axios.interceptors.response.use(
response => {
console.log('API Response:', response.status, response.data);
return response;
},
error => {
console.error('API Response Error:', error.response?.status, error.message);
return Promise.reject(error);
}
);
2. Mock 服务调试
javascript
// packages/mock-server/src/index.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// Mock 特定端点
app.get('/api/users/:id', (req, res) => {
console.log('Mock request for user:', req.params.id);
res.json({
id: req.params.id,
name: 'Mock User',
email: 'mock@example.com'
});
});
// 代理其他请求
app.use('/api', createProxyMiddleware({
target: 'http://localhost:3000',
changeOrigin: true,
logLevel: 'debug'
}));
app.listen(3001, () => {
console.log('Mock server running on port 3001');
});
性能调试
1. 性能分析
javascript
// packages/performance-debug/src/index.js
function measurePerformance(fn, name) {
return async function(...args) {
const start = performance.now();
try {
const result = await fn.apply(this, args);
const end = performance.now();
console.log(`${name} took ${end - start} milliseconds`);
return result;
} catch (error) {
const end = performance.now();
console.error(`${name} failed after ${end - start} milliseconds`, error);
throw error;
}
};
}
// 使用示例
const debuggedFunction = measurePerformance(myFunction, 'MyFunction');
2. 内存调试
javascript
// packages/memory-debug/src/index.js
function checkMemoryUsage(label = '') {
const usage = process.memoryUsage();
console.log(`${label} Memory Usage:`, {
rss: `${Math.round(usage.rss / 1024 / 1024)} MB`,
heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)} MB`,
heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)} MB`,
external: `${Math.round(usage.external / 1024 / 1024)} MB`
});
}
// 定期检查内存
setInterval(() => checkMemoryUsage('Periodic'), 30000);
调试脚本
1. 调试工具脚本
bash
#!/bin/bash
# scripts/debug-info.sh
echo "=== Monorepo Debug Info ==="
echo "Node Version: $(node --version)"
echo "PNPM Version: $(pnpm --version)"
echo "Current Branch: $(git branch --show-current)"
echo "Uncommitted Changes:"
git status --porcelain
echo -e "\n=== Affected Packages ==="
npx nx print-affected --base=HEAD~1
echo -e "\n=== Dependency Issues ==="
pnpm audit --audit-level=high
2. 包依赖分析
javascript
// scripts/analyze-deps.js
const fs = require('fs');
const path = require('path');
function analyzeDependencies() {
const packagesDir = './packages';
const packageNames = fs.readdirSync(packagesDir);
packageNames.forEach(pkg => {
const pkgPath = path.join(packagesDir, pkg, 'package.json');
if (fs.existsSync(pkgPath)) {
const pkgJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
console.log(`\n${pkgJson.name}:`);
console.log(' Dependencies:', Object.keys(pkgJson.dependencies || {}));
console.log(' Dev Dependencies:', Object.keys(pkgJson.devDependencies || {}));
}
});
}
analyzeDependencies();
浏览器调试
1. 前端包调试
javascript
// packages/frontend-debug/src/index.js
// 开发模式下的额外调试信息
if (process.env.NODE_ENV === 'development') {
// 启用 React DevTools
if (typeof window !== 'undefined') {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
}
// 全局调试工具
window.debugTools = {
dumpState: () => console.log('Current State:', window.appState),
reloadModule: (moduleName) => System.import(moduleName),
toggleDebug: () => {
localStorage.setItem('debug', !localStorage.getItem('debug'));
location.reload();
}
};
}
2. Source Map 验证
bash
# 验证 source map 文件
npx sm-validator dist/bundle.js.map
# 分析 bundle 内容
npx webpack-bundle-analyzer dist/stats.json
CI/CD 调试
1. CI 调试模式
yaml
# .github/workflows/debug.yml
name: Debug Build
on:
workflow_dispatch: # 手动触发
jobs:
debug:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: DEBUG=* pnpm nx affected:build --verbose
- run: |
echo "=== System Info ==="
node --version
npm --version
pnpm --version
2. 远程调试配置
json
// package.json (for debugging)
{
"scripts": {
"debug": "node --inspect-brk=0.0.0.0:9229 ./dist/index.js",
"debug:dev": "nodemon --inspect=0.0.0.0:9229 --exec npx babel-node src/index.js"
}
}
故障排除
1. 常见问题诊断
bash
# 清理缓存
pnpm store prune
npx nx reset
# 重新安装依赖
rm -rf node_modules pnpm-lock.yaml
pnpm install
# 检查链接
pnpm list --link
2. 调试清单
- [ ] 检查 Node.js 和包管理器版本
- [ ] 验证 package.json 依赖关系
- [ ] 检查构建输出和错误日志
- [ ] 确认环境变量设置
- [ ] 验证 TypeScript 编译
- [ ] 检查源码映射配置
- [ ] 确认包间引用路径
调试最佳实践
1. 调试日志约定
javascript
// 统一的调试日志格式
function createDebugger(namespace) {
return {
log: (message, ...data) => {
console.log(`[${new Date().toISOString()}] [${namespace}] ${message}`, ...data);
},
error: (message, ...data) => {
console.error(`[${new Date().toISOString()}] [${namespace}] ERROR: ${message}`, ...data);
},
warn: (message, ...data) => {
console.warn(`[${new Date().toISOString()}] [${namespace}] WARN: ${message}`, ...data);
}
};
}
2. 条件调试
javascript
// packages/conditional-debug/src/index.js
const DEBUG = process.env.NODE_ENV === 'development' ||
process.env.DEBUG === 'true';
function debugOnly(fn) {
if (DEBUG) {
return fn;
}
return () => {};
}
// 使用示例
const debugLog = debugOnly((message) => console.log(message));
debugLog('This only runs in debug mode');
总结
Monorepo 调试需要考虑多包环境的复杂性,关键要点包括:
- 工具配置: 正确设置 IDE 和调试工具
- 日志策略: 实施结构化和可追踪的日志记录
- 源码映射: 确保源码映射正确配置
- 依赖管理: 有效管理包间依赖关系
- 性能监控: 监控性能和内存使用
- 故障排除: 建立系统性的故障排除流程
通过实施这些调试技巧,可以显著提高在 Monorepo 环境中开发和维护的效率。