obsidian-plugin-dev

Obsidian 插件开发指南,提供项目初始化模板、配置文件、常见问题解决方案和 API 使用示例。当需要开发 Obsidian 插件、解决 Obsidian 插件开发问题、或查询 Obsidian API 用法时使用。触发场景:(1) 创建新的 Obsidian 插件项目,(2) 配置 esbuild/TypeScript 构建环境,(3) 解决 Node.js 模块加载问题,(4) 使用 Obsidian API(文件操作、frontmatter、UI 组件、命令注册、事件监听),(5) 调试 Obsidian 插件。

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "obsidian-plugin-dev" with this command: npx skills add hhu3637kr/skills/hhu3637kr-skills-obsidian-plugin-dev

Obsidian 插件开发指南

项目初始化

1. 创建目录结构

plugin-name/
├── manifest.json           # 插件清单(必需)
├── package.json            # npm 配置
├── tsconfig.json           # TypeScript 配置
├── esbuild.config.mjs      # 构建配置
├── main.ts                 # 插件入口(必需)
├── styles.css              # 样式文件(可选)
└── src/                    # 源代码目录
    ├── components/         # UI 组件
    ├── services/           # 服务层
    └── utils/              # 工具函数

2. manifest.json 模板

{
  "id": "plugin-name",
  "name": "Plugin Display Name",
  "version": "1.0.0",
  "minAppVersion": "0.16.0",
  "description": "插件描述",
  "author": "作者名",
  "authorUrl": "https://github.com/username",
  "isDesktopOnly": false
}

3. package.json 模板

{
  "name": "plugin-name",
  "version": "1.0.0",
  "description": "插件描述",
  "main": "main.js",
  "scripts": {
    "dev": "node esbuild.config.mjs",
    "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "@types/node": "^20.11.5",
    "esbuild": "^0.20.0",
    "obsidian": "latest",
    "tslib": "^2.8.1",
    "typescript": "^5.3.3"
  }
}

4. tsconfig.json 模板

{
  "compilerOptions": {
    "baseUrl": ".",
    "inlineSourceMap": true,
    "inlineSources": true,
    "module": "ESNext",
    "target": "ES6",
    "allowJs": true,
    "noImplicitAny": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "isolatedModules": true,
    "strictNullChecks": true,
    "lib": ["DOM", "ES5", "ES6", "ES7"]
  },
  "include": ["**/*.ts"]
}

5. esbuild.config.mjs 模板(关键配置)

import esbuild from "esbuild";
import process from "process";

const banner = `/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
*/
`;

const prod = (process.argv[2] === 'production');

const context = await esbuild.context({
  banner: {
    js: banner,
  },
  entryPoints: ['main.ts'],
  bundle: true,
  external: [
    'obsidian',
    'electron',
    '@codemirror/autocomplete',
    '@codemirror/collab',
    '@codemirror/commands',
    '@codemirror/language',
    '@codemirror/lint',
    '@codemirror/search',
    '@codemirror/state',
    '@codemirror/view',
    '@lezer/common',
    '@lezer/highlight',
    '@lezer/lr',
  ],
  format: 'cjs',
  target: 'es2018',
  logLevel: "info",
  sourcemap: prod ? false : 'inline',
  treeShaking: true,
  outfile: 'main.js',
  platform: 'node',  // 关键:允许使用 Node.js 模块
});

if (prod) {
  await context.rebuild();
  process.exit(0);
} else {
  await context.watch();
}

重要说明

  • platform: 'node' 是关键配置,允许插件使用 Node.js 内置模块(如 httpfspath
  • 不要使用 builtin-modules 包将 Node.js 模块标记为外部依赖,否则运行时会找不到这些模块
  • external 数组只包含 Obsidian 相关的模块

插件入口模板 (main.ts)

import { Plugin, Notice, PluginSettingTab, App, Setting } from 'obsidian';

interface MyPluginSettings {
  setting1: string;
  setting2: boolean;
}

const DEFAULT_SETTINGS: MyPluginSettings = {
  setting1: 'default',
  setting2: true
};

export default class MyPlugin extends Plugin {
  settings: MyPluginSettings;

  async onload() {
    console.log('Loading plugin...');

    // 加载设置
    await this.loadSettings();

    // 添加设置页面
    this.addSettingTab(new MyPluginSettingTab(this.app, this));

    // 注册命令
    this.addCommand({
      id: 'my-command',
      name: '我的命令',
      callback: () => {
        new Notice('命令执行成功!');
      }
    });

    // 添加状态栏项
    const statusBarItem = this.addStatusBarItem();
    statusBarItem.setText('插件已加载');

    // 注册事件监听
    this.registerEvent(
      this.app.vault.on('modify', (file) => {
        console.log('File modified:', file.path);
      })
    );
  }

  async onunload() {
    console.log('Unloading plugin...');
  }

  async loadSettings() {
    this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
  }

  async saveSettings() {
    await this.saveData(this.settings);
  }
}

class MyPluginSettingTab extends PluginSettingTab {
  plugin: MyPlugin;

  constructor(app: App, plugin: MyPlugin) {
    super(app, plugin);
    this.plugin = plugin;
  }

  display(): void {
    const { containerEl } = this;
    containerEl.empty();

    containerEl.createEl('h2', { text: '插件设置' });

    new Setting(containerEl)
      .setName('设置项 1')
      .setDesc('设置项描述')
      .addText(text => text
        .setPlaceholder('请输入...')
        .setValue(this.plugin.settings.setting1)
        .onChange(async (value) => {
          this.plugin.settings.setting1 = value;
          await this.plugin.saveSettings();
        }));

    new Setting(containerEl)
      .setName('设置项 2')
      .setDesc('开关类型设置')
      .addToggle(toggle => toggle
        .setValue(this.plugin.settings.setting2)
        .onChange(async (value) => {
          this.plugin.settings.setting2 = value;
          await this.plugin.saveSettings();
        }));
  }
}

常见问题及解决方案

问题 1: MCP Server 启动失败 / Node.js 模块无法加载

症状:插件使用 httpws 等 Node.js 模块时报错

原因:esbuild 配置不正确,Node.js 模块被标记为外部依赖

解决方案

// esbuild.config.mjs
// 1. 添加 platform: 'node'
platform: 'node',

// 2. 不要使用 builtin-modules
// 错误做法:
// import builtins from "builtin-modules";
// external: [...builtins]

// 3. 只将 Obsidian 相关模块标记为外部
external: [
  'obsidian',
  'electron',
  '@codemirror/*',
  '@lezer/*',
]

问题 2: WebSocket 类型冲突

症状:TypeScript 报错 WebSocket 类型冲突

原因ws 库的 WebSocket 与浏览器全局 WebSocket 类型冲突

解决方案

// 使用别名导入
import { WebSocketServer, WebSocket as WebSocketNode } from 'ws';

// 使用时
private _wsClients: Set<WebSocketNode> = new Set();

问题 3: PluginSettingTab 继承错误

症状SpecConfirmSettingTab doesn't extend PluginSettingTab properly

原因:未正确继承 PluginSettingTab 或未调用父类构造函数

解决方案

import { PluginSettingTab, App } from 'obsidian';

class MySettingTab extends PluginSettingTab {
  plugin: MyPlugin;

  constructor(app: App, plugin: MyPlugin) {
    super(app, plugin);  // 必须调用父类构造函数
    this.plugin = plugin;
  }

  display(): void {
    const { containerEl } = this;  // containerEl 来自父类
    containerEl.empty();
    // ...
  }
}

问题 4: 缺少依赖

症状:构建时报错缺少 tslib 或类型定义

解决方案

npm install --save-dev tslib @types/node @types/ws

问题 5: openLinkText 参数类型错误

症状app.workspace.openLinkText() 第二个参数类型不匹配

解决方案

// 第二个参数是 sourcePath,应为字符串
await app.workspace.openLinkText(file.path, '');
// 或
await app.workspace.openLinkText(file.path, file.path);

Obsidian API 常用功能

文件操作

// 读取文件
const content = await this.app.vault.read(file);

// 修改文件
await this.app.vault.modify(file, newContent);

// 创建文件
await this.app.vault.create(path, content);

// 删除文件
await this.app.vault.delete(file);

// 获取文件
const file = this.app.vault.getAbstractFileByPath(path);

Frontmatter 操作

import { TFile } from 'obsidian';

// 读取 frontmatter
const cache = this.app.metadataCache.getFileCache(file);
const frontmatter = cache?.frontmatter;

// 更新 frontmatter(推荐方式,原子操作)
await this.app.fileManager.processFrontMatter(file, (fm) => {
  fm.status = '已确认';
  fm.updated = new Date().toISOString();
});

UI 组件

import { Notice, Modal, Setting } from 'obsidian';

// 显示通知
new Notice('操作成功!');
new Notice('操作成功!', 5000);  // 5秒后消失

// 创建模态框
class MyModal extends Modal {
  constructor(app: App) {
    super(app);
  }

  onOpen() {
    const { contentEl } = this;
    contentEl.createEl('h2', { text: '标题' });

    new Setting(contentEl)
      .addButton(btn => btn
        .setButtonText('确认')
        .setCta()
        .onClick(() => {
          this.close();
        }));
  }

  onClose() {
    const { contentEl } = this;
    contentEl.empty();
  }
}

// 使用模态框
new MyModal(this.app).open();

命令注册

this.addCommand({
  id: 'unique-command-id',
  name: '命令显示名称',
  // 简单回调
  callback: () => {
    // 执行操作
  },
  // 或者检查回调(返回 false 禁用命令)
  checkCallback: (checking: boolean) => {
    const file = this.app.workspace.getActiveFile();
    if (file) {
      if (!checking) {
        // 执行操作
      }
      return true;
    }
    return false;
  }
});

事件监听

// 文件修改
this.registerEvent(
  this.app.vault.on('modify', (file) => {
    console.log('Modified:', file.path);
  })
);

// 文件创建
this.registerEvent(
  this.app.vault.on('create', (file) => {
    console.log('Created:', file.path);
  })
);

// 文件删除
this.registerEvent(
  this.app.vault.on('delete', (file) => {
    console.log('Deleted:', file.path);
  })
);

// 活动文件变化
this.registerEvent(
  this.app.workspace.on('active-leaf-change', (leaf) => {
    // 处理活动文件变化
  })
);

构建和安装

开发模式

npm run dev

生产构建

npm run build

安装到 Obsidian

  1. 将以下文件复制到 Obsidian vault 的 .obsidian/plugins/plugin-name/ 目录:

    • main.js
    • manifest.json
    • styles.css(如有)
  2. 在 Obsidian 设置中启用插件

调试

  • 打开 Obsidian 开发者控制台:Ctrl+Shift+I(Windows/Linux)或 Cmd+Option+I(Mac)
  • 查看 Console 标签页中的日志输出

参考资源

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

intent-confirmation

No summary provided by upstream source.

Repository SourceNeeds Review
General

skill-creator

No summary provided by upstream source.

Repository SourceNeeds Review
General

spec-execute

No summary provided by upstream source.

Repository SourceNeeds Review
General

exp-reflect

No summary provided by upstream source.

Repository SourceNeeds Review