插件开发文档
博客系统 v1.0+ 支持可扩展的插件系统
概述
插件系统允许开发者扩展博客功能,无需修改核心代码。每个插件可以是纯前端插件(注入 CSS/JS)或全栈插件(包含后端 API)。
目录结构
plugins/
└── my-plugin/
├── manifest.json # 插件配置(必需)
├── main.js # 前端入口(可选)
├── server.js # 后端入口(可选)
├── style.css # 样式文件(可选)
└── partials/ # Handlebars 片段(可选)
└── my-partial.hbs
manifest.json
插件配置文件:
{
"name": "my-plugin",
"displayName": "我的插件",
"version": "1.0.0",
"description": "我的插件描述",
"author": "作者名",
"frontend": {
"entry": "main.js",
"style": "style.css"
},
"backend": {
"entry": "server.js"
},
"hooks": ["head_css", "after_header", "body_js"],
"hookContents": {
"head_css": "<link rel='stylesheet' href='/plugins/my-plugin/style.css'>",
"after_header": "<div class='plugin-banner'>欢迎访问我的博客</div>",
"body_js": ""
},
"themeAware": false,
"compatibility": {
"minEngineVersion": "1.0.0",
"themes": ["default"]
}
}
字段说明
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
| name | string | 是 | 插件唯一名称(目录名) |
| displayName | string | 否 | 插件显示名称 |
| version | string | 是 | 版本号 |
| description | string | 否 | 插件描述 |
| author | string | 否 | 作者 |
| frontend | object | 否 | 前端配置,包含 entry(入口文件)、style(样式文件) |
| backend | object | 否 | 后端配置,包含 entry(入口文件) |
| hooks | string[] | 否 | 声明使用的 Hook |
| hookContents | object | 否 | Hook 对应的内容 |
| themeAware | boolean | 否 | 是否响应主题变更 |
| compatibility | object | 否 | 兼容性配置,包含 minEngineVersion、themes |
前端插件 (main.js)
纯前端逻辑,可在页面加载时执行:
// main.js - 前端入口
// 插件初始化
(function() {
'use strict';
// 获取插件上下文
const pluginContext = window.__pluginRuntime;
const pluginName = 'my-plugin';
const themeName = pluginContext?.themeName || 'default';
// 监听主题变更(如果启用 themeAware)
if (pluginContext?.themeAware) {
document.addEventListener('theme:changed', function(e) {
console.log('Theme changed:', e.detail);
init();
});
}
function init() {
console.log(`[${pluginName}] Initialized for theme: ${themeName}`);
// 你的插件逻辑
const container = document.querySelector('.plugin-hook-container[data-plugin-hook="after_header"]');
if (container) {
container.innerHTML += '<div class="my-plugin-element">插件内容</div>';
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
后端插件 (server.js)
提供后端 API 路由和数据处理能力:
API 路径规则:插件的 API 自动挂载到
/plugin-api/{插件名}/前缀下。 例如router.get('/data', ...)最终可通过GET /plugin-api/my-plugin/data访问。
// server.js - 后端入口
/**
* @param {express.Router} router - Express 路由实例
* @param {Object} options - 插件选项
* @param {Object} options.db - 数据库实例
* @param {Object} options.config - 插件配置对象
* @param {string} options.pluginDir - 插件目录路径
* @param {string} options.pluginName - 插件名称
*/
export function registerRoutes(router, { db, config, pluginDir, pluginName }) {
// 路由会自动挂载到 /plugin-api/{pluginName}/
// 例如:GET /plugin-api/my-plugin/data
router.get('/data', async (req, res) => {
try {
const data = await getPluginData(db);
res.json({ success: true, data });
} catch (err) {
res.status(500).json({ success: false, error: err.message });
}
});
router.post('/save', async (req, res) => {
try {
const { key, value } = req.body;
await saveData(db, key, value);
res.json({ success: true });
} catch (err) {
res.status(500).json({ success: false, error: err.message });
}
});
console.log(`[${pluginName}] Routes registered`);
}
/**
* 主题变更回调(可选)
*/
export async function onThemeChanged(oldTheme, newTheme) {
console.log(`[my-plugin] Theme changed: ${oldTheme} -> ${newTheme}`);
}
/**
* 插件停用回调(可选)
*/
export async function onDeactivate() {
console.log('[my-plugin] Deactivating...');
}
Hook 系统
可用 Hook 列表
| Hook 名称 | 位置 | 说明 |
|---|---|---|
| head_css | <head> 内 |
注入 CSS 链接 |
| after_header | <header> 后 |
紧跟 header 后 |
| before_nav_end | 导航内部末尾 | 导航栏末尾 |
| before_content | <main> 前 |
主内容区之前 |
| after_content | <main> 后 |
主内容区之后 |
| after_post_content | 文章内容后 | 文章正文之后 |
| sidebar_top | 侧边栏顶部 | 侧边栏开始处 |
| sidebar_bottom | 侧边栏底部 | 侧边栏结束处 |
| before_footer | <footer> 前 |
Footer 之前 |
| body_js | <body> 末尾 |
注入 JS 脚本 |
Hook 注入方式
方式一:manifest.json 声明
{
"name": "simple-plugin",
"hooks": ["head_css", "after_header"],
"hookContents": {
"head_css": "<link rel='stylesheet' href='/plugins/simple-plugin/style.css'>",
"after_header": "<div class='simple-plugin-banner'>欢迎访问</div>"
}
}
方式二:前端动态注入
// main.js
function injectContent() {
const headHook = document.querySelector('.plugin-hook-container[data-plugin-hook="head_css"]');
if (headHook) {
headHook.innerHTML += '<link rel="stylesheet" href="/plugins/my-plugin/dynamic.css">';
}
}
主题感知 (themeAware)
如果插件需要适配不同主题,设置 themeAware: true:
{
"name": "adaptive-plugin",
"themeAware": true,
"compatibility": {
"themes": ["default", "dark-mode", "minimal"]
}
}
前端会自动收到主题变更事件:
document.addEventListener('theme:changed', function(e) {
const { theme } = e.detail;
// 重新渲染插件 UI
});
样式隔离
建议使用插件名前缀避免样式冲突:
.my-plugin-container {
padding: 16px;
}
.my-plugin-title {
font-size: 18px;
font-weight: bold;
}
/* 使用 BEM 命名 */
.my-plugin__item {
margin: 8px 0;
}
.my-plugin__item--active {
background: var(--primary);
}
安装插件
方式一:直接放置
- 将插件目录复制到
plugins/目录 - 确保目录名与
manifest.json中的name一致 - 在后台管理页面启用插件
方式二:上传安装
- 进入 后台管理 → 插件管理
- 点击「上传插件」按钮
- 选择插件 ZIP 包(包含完整插件目录)
- 系统自动解压并安装
- 启用插件
最佳实践
- 样式隔离:使用唯一前缀命名 CSS 类
- 资源路径:使用相对路径引用插件内部资源
- 错误处理:后端 API 必须有完整的错误处理
- 主题兼容:测试多个主题下的显示效果
- 清理资源:实现
onDeactivate清理临时数据