Skip to content
On this page

Monorepo 调试技巧

调试挑战

在 Monorepo 环境中调试面临独特的挑战:

  1. 多包依赖关系: 需要理解包之间的依赖关系
  2. 复杂的工作流: 多个包同时开发和调试
  3. 环境一致性: 确保所有包在相同环境中运行
  4. 错误追踪: 跨包的错误追踪和调试

调试工具设置

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 调试需要考虑多包环境的复杂性,关键要点包括:

  1. 工具配置: 正确设置 IDE 和调试工具
  2. 日志策略: 实施结构化和可追踪的日志记录
  3. 源码映射: 确保源码映射正确配置
  4. 依赖管理: 有效管理包间依赖关系
  5. 性能监控: 监控性能和内存使用
  6. 故障排除: 建立系统性的故障排除流程

通过实施这些调试技巧,可以显著提高在 Monorepo 环境中开发和维护的效率。