Skip to content

插件调试技巧

调试是插件开发过程中不可或缺的环节。本文介绍 Obsidian 插件开发的各种调试技巧,帮助你快速定位和解决问题。

开发者工具

打开开发者工具

Obsidian 基于 Electron,可以使用 Chrome 开发者工具进行调试:

平台快捷键
Windows/LinuxCtrl + Shift + I
macOSCmd + Option + I

也可以通过菜单打开: 视图 → 切换开发者工具

开发者工具面板

Console 面板

查看日志输出和错误信息:

javascript
// 在插件代码中使用
console.log('普通日志');
console.info('信息日志');
console.warn('警告日志');
console.error('错误日志');

// 输出对象
console.log('插件设置:', this.settings);

// 表格形式输出
console.table([{ name: 'Alice', age: 25 }, { name: 'Bob', age: 30 }]);

// 分组输出
console.group('API 调用');
console.log('请求参数:', params);
console.log('响应数据:', response);
console.groupEnd();

// 计时
console.time('操作耗时');
// ... 一些操作
console.timeEnd('操作耗时');

Sources 面板

查看和调试源代码:

  1. 在左侧文件树中找到你的插件
  2. 路径通常是 vault/.obsidian/plugins/你的插件名/
  3. 点击文件打开源码

Network 面板

监控网络请求:

javascript
// 发送 API 请求时
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log('API 响应:', data);

断点调试

设置断点

  1. 打开 Sources 面板
  2. 找到你的插件代码文件
  3. 点击行号左侧设置断点
  4. 触发相关功能,代码会在断点处暂停

断点类型

类型说明使用场景
普通断点代码执行到此暂停一般调试
条件断点满足条件时暂停循环或频繁调用
日志点输出信息不暂停查看变量值
异常断点抛出异常时暂停捕获错误

设置条件断点

  1. 右键点击行号
  2. 选择「Add conditional breakpoint」
  3. 输入条件表达式,如 id === 'target-id'

调试控制

操作快捷键说明
继续F8继续执行到下一个断点
单步跳过F10执行当前行,不进入函数
单步进入F11进入函数内部
单步跳出Shift+F11跳出当前函数

实时修改代码

热重载

使用 Obsidian 的热重载功能:

  1. 在插件中添加:
typescript
import { Plugin } from 'obsidian';

export default class MyPlugin extends Plugin {
  onload() {
    // 开发模式下添加热重载
    if (this.app.isMobile) return;

    this.registerEvent(
      this.app.vault.on('modify', (file) => {
        if (file.path.includes('main.js')) {
          // 提示用户重新加载
          new Notice('代码已更新,请重新加载插件');
        }
      })
    );
  }
}
  1. 或者使用 Hot Reload 插件自动重载

控制台直接执行

在 Console 中直接执行代码:

javascript
// 获取当前活动文件
const activeFile = app.workspace.getActiveFile();
console.log('当前文件:', activeFile);

// 获取插件实例
const plugin = app.plugins.plugins['your-plugin-id'];
console.log('插件设置:', plugin.settings);

// 手动调用插件方法
plugin.yourMethod();

日志记录

创建日志工具

typescript
// utils/logger.ts
export class Logger {
  private prefix: string;
  private enabled: boolean;

  constructor(prefix: string, enabled = true) {
    this.prefix = prefix;
    this.enabled = enabled;
  }

  log(message: string, ...args: any[]) {
    if (this.enabled) {
      console.log(`[${this.prefix}] ${message}`, ...args);
    }
  }

  warn(message: string, ...args: any[]) {
    if (this.enabled) {
      console.warn(`[${this.prefix}] ${message}`, ...args);
    }
  }

  error(message: string, ...args: any[]) {
    console.error(`[${this.prefix}] ${message}`, ...args);
  }

  time(label: string) {
    if (this.enabled) {
      console.time(`[${this.prefix}] ${label}`);
    }
  }

  timeEnd(label: string) {
    if (this.enabled) {
      console.timeEnd(`[${this.prefix}] ${label}`);
    }
  }
}

// 使用
const logger = new Logger('MyPlugin', true);
logger.log('插件已加载');
logger.time('数据处理');
// ... 处理逻辑
logger.timeEnd('数据处理');

带日志级别的调试

typescript
enum LogLevel {
  DEBUG,
  INFO,
  WARN,
  ERROR
}

class DebugLogger {
  private level: LogLevel;

  constructor(level: LogLevel = LogLevel.INFO) {
    this.level = level;
  }

  debug(message: string, ...args: any[]) {
    if (this.level <= LogLevel.DEBUG) {
      console.debug(`[DEBUG] ${message}`, ...args);
    }
  }

  info(message: string, ...args: any[]) {
    if (this.level <= LogLevel.INFO) {
      console.info(`[INFO] ${message}`, ...args);
    }
  }

  warn(message: string, ...args: any[]) {
    if (this.level <= LogLevel.WARN) {
      console.warn(`[WARN] ${message}`, ...args);
    }
  }

  error(message: string, ...args: any[]) {
    if (this.level <= LogLevel.ERROR) {
      console.error(`[ERROR] ${message}`, ...args);
    }
  }
}

常见问题排查

插件无法加载

症状:插件列表中找不到插件或显示错误

排查步骤

  1. 检查 manifest.json 格式:
json
{
  "id": "my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "minAppVersion": "0.15.0",
  "description": "插件描述",
  "author": "作者名",
  "authorUrl": "https://example.com",
  "isDesktopOnly": false
}
  1. 检查 main.js 是否存在
  2. 查看 Console 中的错误信息
  3. 检查 TypeScript 编译是否有错误

代码不生效

症状:修改代码后没有变化

排查步骤

  1. 确认代码已编译:
    bash
    npm run build
  2. 重新加载插件:关闭再启用插件
  3. 检查是否修改了正确的文件
  4. 清除缓存后重启 Obsidian

事件监听器不触发

症状:注册的事件没有响应

排查步骤

typescript
// 检查事件是否正确注册
this.registerEvent(
  this.app.vault.on('create', (file) => {
    console.log('文件创建:', file.path);  // 添加日志
  })
);

// 确保使用正确的 this 绑定
this.registerEvent(
  this.app.workspace.on('file-open', this.handleFileOpen.bind(this))
);

内存泄漏

症状:插件运行时间长了变慢

排查步骤

  1. 使用开发者工具的 Memory 面板
  2. 检查是否有未清理的事件监听器:
typescript
// 错误示例
setInterval(() => {
  // 如果在 onload 中注册,onunload 时需要清理
}, 1000);

// 正确做法
this.registerInterval(
  window.setInterval(() => {
    // ...
  }, 1000)
);
  1. 检查是否有未释放的资源:
typescript
export default class MyPlugin extends Plugin {
  private debounceTimer: number;

  onload() {
    this.debounceTimer = window.setTimeout(() => {}, 1000);
  }

  onunload() {
    // 清理定时器
    if (this.debounceTimer) {
      window.clearTimeout(this.debounceTimer);
    }
  }
}

性能调试

使用 Performance 面板

  1. 打开开发者工具
  2. 切换到 Performance 面板
  3. 点击录制按钮
  4. 执行需要测试的操作
  5. 停止录制,分析结果

性能标记

typescript
// 使用 performance API
performance.mark('operation-start');

// 执行操作
for (let i = 0; i < 10000; i++) {
  // ...
}

performance.mark('operation-end');
performance.measure('operation', 'operation-start', 'operation-end');

// 查看结果
const measures = performance.getEntriesByName('operation');
console.log('耗时:', measures[0].duration, 'ms');

防抖与节流

typescript
// 防抖 - 延迟执行
function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: number;
  return function(this: any, ...args: Parameters<T>) {
    clearTimeout(timeout);
    timeout = window.setTimeout(() => func.apply(this, args), wait);
  };
}

// 节流 - 固定频率执行
function throttle<T extends (...args: any[]) => any>(
  func: T,
  limit: number
): (...args: Parameters<T>) => void {
  let inThrottle: boolean;
  return function(this: any, ...args: Parameters<T>) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// 使用示例
this.registerEvent(
  this.app.workspace.on('file-open', debounce((file) => {
    console.log('文件打开:', file.path);
  }, 300))
);

调试技巧总结

开发模式设置

typescript
// 在插件中区分开发/生产模式
const isDev = process.env.NODE_ENV === 'development';

export default class MyPlugin extends Plugin {
  async onload() {
    if (isDev) {
      // 开发模式下的调试代码
      this.addCommand({
        id: 'debug-info',
        name: '显示调试信息',
        callback: () => this.showDebugInfo()
      });
    }
  }

  showDebugInfo() {
    const info = {
      settings: this.settings,
      activeFile: this.app.workspace.getActiveFile(),
      vault: this.app.vault.getRoot().path
    };
    console.log('调试信息:', info);
  }
}

错误边界

typescript
// 包装可能出错的操作
async safeExecute<T>(operation: () => Promise<T>): Promise<T | null> {
  try {
    return await operation();
  } catch (error) {
    console.error('操作失败:', error);
    new Notice('操作失败,请查看控制台');
    return null;
  }
}

// 使用
const result = await this.safeExecute(async () => {
  const data = await this.fetchData();
  return this.processData(data);
});

模拟数据

typescript
// 在开发时使用模拟数据
class MockDataService {
  private useMock = true;

  async fetchData(): Promise<Data[]> {
    if (this.useMock) {
      return this.getMockData();
    }
    return this.getRealData();
  }

  private getMockData(): Data[] {
    return [
      { id: 1, name: 'Test 1' },
      { id: 2, name: 'Test 2' },
    ];
  }

  private async getRealData(): Promise<Data[]> {
    // 实际获取数据
  }
}

相关内容

最后更新:2026年2月22日编辑此页反馈问题