O
Obsidian 插件参考架构
作者:鹿Sir源码系统v1
提供 Obsidian 插件的生产级参考架构与最佳实践项目布局,含分层架构、服务层、命令注册、事件管理与设置模式等完整代码模板。当用户设计新的 Obsidian 插件、审查插件项目结构或为 Obsidian 开发建立架构标准时触发。触发词:Obsidian 架构、Obsidian 项目结构、Obsidian 最佳实践、组织 Obsidian 插件。
下载量
273
点赞
64
价格
免费
技能文档
---
name: dicklesworthstone-obsidian-reference-architecture
title: Obsidian 插件参考架构
category: 源码系统
description: 提供 Obsidian 插件的生产级参考架构与最佳实践项目布局,含分层架构、服务层、命令注册、事件管理与设置模式等完整代码模板。当用户设计新的 Obsidian 插件、审查插件项目结构或为 Obsidian 开发建立架构标准时触发。触发词:Obsidian 架构、Obsidian 项目结构、Obsidian 最佳实践、组织 Obsidian 插件。
---
# Obsidian 插件参考架构
## 概述
本技能提供一套可用于生产环境的 Obsidian 插件架构模式,覆盖项目目录布局、分层架构、核心组件实现与常见问题处理,帮助快速搭建结构清晰、职责分明的插件工程。
## 前置条件
- 理解分层架构思想
- 掌握 TypeScript 与 Obsidian API 基础知识
- 已完成插件项目的初始搭建
## 项目目录结构
```
my-obsidian-plugin/
├── src/
│ ├── main.ts # 插件入口
│ ├── types.ts # TypeScript 类型定义
│ ├── constants.ts # 常量与配置
│ │
│ ├── settings/
│ │ ├── settings.ts # 设置接口与默认值
│ │ ├── settings-tab.ts # 设置界面组件
│ │ └── settings-migration.ts # 设置版本迁移
│ │
│ ├── services/
│ │ ├── vault-service.ts # 仓库(Vault)操作
│ │ ├── metadata-service.ts # Frontmatter/缓存操作
│ │ ├── search-service.ts # 搜索功能
│ │ └── api-service.ts # 外部 API 集成
│ │
│ ├── commands/
│ │ ├── index.ts # 命令注册
│ │ ├── note-commands.ts # 笔记相关命令
│ │ └── utility-commands.ts # 工具类命令
│ │
│ ├── ui/
│ │ ├── modals/
│ │ │ ├── input-modal.ts
│ │ │ └── confirm-modal.ts
│ │ ├── views/
│ │ │ ├── sidebar-view.ts
│ │ │ └── view-registry.ts
│ │ └── components/
│ │ ├── status-bar.ts
│ │ └── ribbon-icon.ts
│ │
│ ├── events/
│ │ ├── event-manager.ts # 事件注册
│ │ └── event-handlers.ts # 事件处理器实现
│ │
│ └── utils/
│ ├── debounce.ts
│ ├── async-queue.ts
│ ├── cache.ts
│ └── logger.ts
│
├── tests/
│ ├── services/
│ │ └── vault-service.test.ts
│ ├── commands/
│ │ └── note-commands.test.ts
│ └── setup.ts # 测试配置
│
├── styles/
│ └── styles.css # 插件样式
│
├── docs/
│ └── ARCHITECTURE.md # 架构文档
│
├── manifest.json # 插件清单
├── versions.json # 版本兼容性
├── package.json # Node 依赖
├── tsconfig.json # TypeScript 配置
├── esbuild.config.mjs # 构建配置
├── .eslintrc.js # Lint 规则
└── .gitignore
```
## 分层架构
```
┌─────────────────────────────────────────┐
│ UI 层 │
│ (视图、模态框、组件) │
├─────────────────────────────────────────┤
│ 命令层 │
│ (命令、事件处理器) │
├─────────────────────────────────────────┤
│ 服务层 │
│ (业务逻辑、数据访问) │
├─────────────────────────────────────────┤
│ 基础设施层 │
│ (缓存、日志、工具) │
└─────────────────────────────────────────┘
```
## 技能工作流
按以下步骤依次实现参考架构的各个核心组件。
### 步骤1:实现插件入口
创建 `src/main.ts`,作为插件的生命周期入口,负责加载设置、初始化服务并注册 UI 组件与命令。
```typescript
// src/main.ts
import { Plugin } from 'obsidian';
import { MyPluginSettings, DEFAULT_SETTINGS, MyPluginSettingsTab } from './settings';
import { VaultService } from './services/vault-service';
import { registerCommands } from './commands';
import { EventManager } from './events/event-manager';
import { registerViews } from './ui/views/view-registry';
import { Logger } from './utils/logger';
export default class MyPlugin extends Plugin {
settings: MyPluginSettings;
private logger: Logger;
private vaultService: VaultService;
private eventManager: EventManager;
async onload() {
this.logger = new Logger(this.manifest.id);
this.logger.info('Loading plugin');
// 加载设置
await this.loadSettings();
// 初始化服务
this.vaultService = new VaultService(this.app);
// 初始化事件管理器
this.eventManager = new EventManager(this);
// 注册 UI 组件
this.addSettingTab(new MyPluginSettingsTab(this.app, this));
registerViews(this);
registerCommands(this);
// 布局就绪后再注册事件
this.app.workspace.onLayoutReady(() => {
this.eventManager.registerAll();
this.logger.info('Plugin ready');
});
}
onunload() {
this.logger.info('Unloading plugin');
// 清理由 Obsidian 自动处理
}
async loadSettings() {
const data = await this.loadData();
this.settings = Object.assign({}, DEFAULT_SETTINGS, data);
}
async saveSettings() {
await this.saveData(this.settings);
}
// 供其他插件调用的公开 API
getVaultService(): VaultService {
return this.vaultService;
}
}
```
### 步骤2:实现服务层模式
创建 `src/services/vault-service.ts`,将仓库文件、目录、元数据等操作集中封装到一个服务类,避免业务代码直接散落调用 Obsidian API。
```typescript
// src/services/vault-service.ts
import { App, TFile, TFolder, Vault, CachedMetadata } from 'obsidian';
export class VaultService {
constructor(private app: App) {}
get vault(): Vault {
return this.app.vault;
}
// 文件操作
async readFile(file: TFile): Promise<string> {
return this.vault.read(file);
}
async writeFile(file: TFile, content: string): Promise<void> {
await this.vault.modify(file, content);
}
async createFile(path: string, content: string): Promise<TFile> {
await this.ensureFolder(path);
return this.vault.create(path, content);
}
getFileByPath(path: string): TFile | null {
const file = this.vault.getAbstractFileByPath(path);
return file instanceof TFile ? file : null;
}
// 目录操作
private async ensureFolder(filePath: string): Promise<void> {
const folderPath = filePath.substring(0, filePath.lastIndexOf('/'));
if (!folderPath) return;
const folder = this.vault.getAbstractFileByPath(folderPath);
if (!folder) {
await this.vault.createFolder(folderPath);
}
}
// 查询操作
getMarkdownFiles(): TFile[] {
return this.vault.getMarkdownFiles();
}
getFilesInFolder(folderPath: string): TFile[] {
return this.getMarkdownFiles().filter(f => f.path.startsWith(folderPath + '/'));
}
// 元数据操作
getMetadata(file: TFile): CachedMetadata | null {
return this.app.metadataCache.getFileCache(file);
}
getFrontmatter(file: TFile): Record<string, any> | null {
return this.getMetadata(file)?.frontmatter || null;
}
}
```
### 步骤3:实现命令注册模式
创建 `src/commands/` 目录,统一注册命令。普通命令用 `callback`,编辑器命令用 `editorCallback`,需条件显示的命令用 `checkCallback`。
```typescript
// src/commands/index.ts
import { Plugin } from 'obsidian';
import { registerNoteCommands } from './note-commands';
import { registerUtilityCommands } from './utility-commands';
export function registerCommands(plugin: Plugin): void {
registerNoteCommands(plugin);
registerUtilityCommands(plugin);
}
// src/commands/note-commands.ts
import { Plugin, MarkdownView, Editor } from 'obsidian';
export function registerNoteCommands(plugin: Plugin): void {
// 普通命令
plugin.addCommand({
id: 'my-plugin-action',
name: 'Perform Action',
callback: () => {
// 具体动作实现
},
});
// 编辑器命令
plugin.addCommand({
id: 'my-plugin-editor-action',
name: 'Editor Action',
editorCallback: (editor: Editor, view: MarkdownView) => {
// 编辑器相关动作
},
});
// 条件命令
plugin.addCommand({
id: 'my-plugin-conditional',
name: 'Conditional Action',
checkCallback: (checking: boolean) => {
const view = plugin.app.workspace.getActiveViewOfType(MarkdownView);
if (view) {
if (!checking) {
// 执行动作
}
return true;
}
return false;
},
});
}
```
### 步骤4:实现事件管理器模式
创建 `src/events/event-manager.ts`,将所有事件注册集中管理,配合防抖工具避免高频触发,并通过 `registerEvent` 保证自动清理、防止事件泄漏。
```typescript
// src/events/event-manager.ts
import { Plugin, TFile, TAbstractFile } from 'obsidian';
import { debounce } from '../utils/debounce';
export class EventManager {
constructor(private plugin: Plugin) {}
registerAll(): void {
this.registerVaultEvents();
this.registerWorkspaceEvents();
}
private registerVaultEvents(): void {
// 防抖后的文件修改处理器
const onModify = debounce((file: TAbstractFile) => {
if (file instanceof TFile) {
this.handleFileModify(file);
}
}, 500);
this.plugin.registerEvent(
this.plugin.app.vault.on('modify', onModify)
);
this.plugin.registerEvent(
this.plugin.app.vault.on('create', (file) => {
if (file instanceof TFile) {
this.handleFileCreate(file);
}
})
);
this.plugin.registerEvent(
this.plugin.app.vault.on('delete', (file) => {
this.handleFileDelete(file);
})
);
this.plugin.registerEvent(
this.plugin.app.vault.on('rename', (file, oldPath) => {
if (file instanceof TFile) {
this.handleFileRename(file, oldPath);
}
})
);
}
private registerWorkspaceEvents(): void {
this.plugin.registerEvent(
this.plugin.app.workspace.on('file-open', (file) => {
if (file) {
this.handleFileOpen(file);
}
})
);
}
private handleFileModify(file: TFile): void {
// 处理文件修改
}
private handleFileCreate(file: TFile): void {
// 处理文件创建
}
private handleFileDelete(file: TAbstractFile): void {
// 处理文件删除
}
private handleFileRename(file: TFile, oldPath: string): void {
// 处理文件重命名
}
private handleFileOpen(file: TFile): void {
// 处理文件打开
}
}
```
### 步骤5:实现设置模式
创建 `src/settings/` 目录,用类型化的设置接口与默认值管理配置,并通过 `PluginSettingTab` 提供设置界面;设置结构变更时实现版本迁移。
```typescript
// src/settings/settings.ts
export interface MyPluginSettings {
enabled: boolean;
apiEndpoint: string;
maxItems: number;
excludeFolders: string[];
settingsVersion: number;
}
export const DEFAULT_SETTINGS: MyPluginSettings = {
enabled: true,
apiEndpoint: '',
maxItems: 100,
excludeFolders: [],
settingsVersion: 1,
};
// src/settings/settings-tab.ts
import { App, PluginSettingTab, Setting } from 'obsidian';
import type MyPlugin from '../main';
export class MyPluginSettingsTab 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: 'My Plugin Settings' });
new Setting(containerEl)
.setName('Enable Plugin')
.setDesc('Turn the plugin features on or off')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enabled)
.onChange(async (value) => {
this.plugin.settings.enabled = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Max Items')
.setDesc('Maximum number of items to display')
.addSlider(slider => slider
.setLimits(10, 500, 10)
.setValue(this.plugin.settings.maxItems)
.setDynamicTooltip()
.onChange(async (value) => {
this.plugin.settings.maxItems = value;
await this.plugin.saveSettings();
}));
}
}
```
## 数据流图
```
用户动作(命令/事件)
│
▼
┌─────────────────┐
│ UI 层 │
│ (模态框/视图) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 命令处理器 │
│ (编排调度) │
└────────┬────────┘
│
▼
┌─────────────────┐ ┌─────────────┐
│ 服务层 │────▶│ 缓存 │
│ (业务逻辑) │ │ (内存) │
└────────┬────────┘ └─────────────┘
│
▼
┌─────────────────┐
│ Obsidian API │
│ (Vault/Cache) │
└─────────────────┘
```
## 预期产出
- 结构清晰的项目目录
- 明确的职责分离
- 可复用的服务层
- 集中管理的事件处理
- 类型安全的设置
## 常见问题处理
| 问题 | 原因 | 解决方案 |
|------|------|----------|
| 循环依赖 | 导入关系错误 | 使用接口隔离 |
| 类型缺失 | 定义不完整 | 创建 types.ts |
| 事件泄漏 | 事件未注册清理 | 使用 registerEvent |
| 设置丢失 | 缺少迁移逻辑 | 实现版本迁移 |
## 快速搭建脚本与参考资料
目录骨架生成脚本与外部参考链接见 [references/补充参考.md](references/补充参考.md)。使用说明
# Obsidian 插件参考架构 一套 Obsidian 插件的生产级参考架构:提供标准目录布局、四层架构与入口/服务/命令/事件/设置五大核心模式的完整代码模板,帮助快速搭建造型规范的插件工程。 ## 使用 向支持技能的助手说: ``` 帮我按参考架构创建一个 Obsidian 插件项目结构 ``` 或用自带的快速脚本生成目录骨架: ```bash bash setup-plugin-structure.sh # 见 SKILL.md「快速搭建脚本」章节 ``` ## 工作原理 技能内含一份结构化架构参考:先给出推荐的项目目录树与分层架构图(UI 层 → 命令层 → 服务层 → 基础设施层),再按工作流分五步给出各核心组件(插件入口、VaultService、命令注册、事件管理器、设置管理)的可直接复用的 TypeScript 代码模板,并附常见问题(循环依赖、事件泄漏、设置丢失等)的解决方案。
支持平台:Qoder · QoderWork · Claude · Codex 等 AI 编程助手