# 插件开发指南 > rust-blog 支持两种插件运行时:**WASM**(Rust 编写)和 **JavaScript**(QuickJS)。 > 本文档聚焦 JS 插件开发。 --- ## 快速开始 ### 1. 创建插件目录 ``` plugins/my-plugin/ ├── plugin.toml # 插件清单 └── index.js # JS 入口 ``` ### 2. 编写 plugin.toml ```toml [plugin] id = "com.example.my-plugin" # 全局唯一 ID(反向域名格式) name = "My Plugin" # 显示名称 version = "1.0.0" # 语义化版本 description = "插件描述" author = "Your Name" license = "MIT" runtime = "js" # 必须设为 "js" language = "javascript" # 或 "typescript" entry = "index.js" # JS 入口文件名(默认 index.js) [permissions] max_memory_mb = 8 # 内存限制(默认 32MB) timeout_ms = 2000 # 单次 Hook 执行超时(默认 5000ms) [hooks.on_post_creating] # 注册的 Hook,key 用下划线或短横线均可 priority = 10 # 优先级,数字越小越先执行 [hooks.filter_html] priority = 20 ``` ### 3. 编写 index.js ```javascript var Plugin = { on_post_creating: function(inputJson) { var input = JSON.parse(inputJson); // 修改输入数据 input.title = input.title.trim(); return JSON.stringify(input); }, filter_html: function(html) { // 在 后注入 OG 标签 return html.replace("", ''); } }; ``` ### 4. 部署 ```bash # 手动复制 cp -r plugins-examples-js/my-plugin/ plugins/my-plugin/ # 或用 justfile(需添加到 justfile) just plugins-js-build ``` --- ## Hook 类型 ### JSON Filter — 修改数据 接收 JSON 字符串,返回修改后的 JSON 字符串。 | Hook | 触发时机 | 数据内容 | |------|----------|----------| | `on_post_creating` | 文章创建前 | `{ title, content, excerpt, category_id, ... }` | | `on_post_updating` | 文章更新前 | 同上 + `id` | | `on_comment_creating` | 评论创建前 | `{ content, post_id, parent_id, ... }` | ```javascript on_post_creating: function(inputJson) { var input = JSON.parse(inputJson); input.excerpt = input.content.substring(0, 200); return JSON.stringify(input); // 必须返回 JSON 字符串 } ``` ### JSON Action — 通知/副作用 接收 JSON 字符串,无返回值。适合日志、通知、缓存清理等。 | Hook | 触发时机 | 数据内容 | |------|----------|----------| | `on_post_created` | 文章创建后 | `{ id, title, slug, ... }` | | `on_post_updated` | 文章更新后 | 同上 | | `on_post_deleted` | 文章删除后 | `{ id }` | | `on_comment_created` | 评论创建后 | `{ id, content, post_id, ... }` | | `on_login` | 用户登录后 | `{ email, success }` | ```javascript on_post_created: function(dataJson) { var data = JSON.parse(dataJson); Host.log("info", "New post: " + data.title); } ``` ### String Filter — 修改原始字符串 | Hook | 触发时机 | 输入 | 返回 | |------|----------|------|------| | `render_markdown` | Markdown 渲染(替代默认渲染器) | Markdown 原文 | HTML 字符串 | | `filter_html` | HTML 后处理 | HTML 字符串 | 修改后的 HTML | ```javascript render_markdown: function(content) { // 自定义渲染逻辑 return "

" + content + "

"; } ``` ### Route Handler — 自定义路由 ```javascript handle_route: function(routeJson) { var route = JSON.parse(routeJson); // route.path, route.method return JSON.stringify({ status: 200, body: JSON.stringify({ message: "Hello from plugin!" }) }); } ``` 需要在 `plugin.toml` 中配置 `match` 模式: ```toml [hooks.handle_route] match = "/api/v1/custom/*" # glob 风格,* 匹配单段路径 priority = 5 ``` --- ## 宿主 API 插件通过全局 `Host` 对象与宿主交互。 ### Host.log(level, message) 写入宿主日志。 ```javascript Host.log("info", "这条消息会出现在宿主日志中"); Host.log("warn", "警告信息"); Host.log("error", "错误信息"); ``` ### Host.getConfig(key) 读取宿主配置。返回字符串或 `null`。 ```javascript var env = Host.getConfig("app.env"); // "development" / "production" var port = Host.getConfig("app.port"); // "3000" var baseUrl = Host.getConfig("app.base_url"); // "http://localhost:3000" var maxSize = Host.getConfig("upload.max_size"); ``` 支持的 key: | Key | 说明 | |-----|------| | `app.host` | 监听地址 | | `app.port` | 监听端口 | | `app.env` | 运行环境 | | `app.base_url` | 站点 URL | | `jwt.access_expires` | Access Token 过期时间(秒) | | `jwt.refresh_expires` | Refresh Token 过期时间(秒) | | `upload.dir` | 上传目录 | | `upload.max_size` | 上传大小限制(字节) | | `plugin.max_memory_mb` | 插件内存限制 | | `plugin.default_timeout_ms` | 插件超时时间 | > 注意:`jwt.secret` 和 `database_url` 等敏感配置不暴露给插件。 --- ## TypeScript 开发 ### 1. 安装 SDK 类型定义 在插件目录创建 `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2021", "module": "ES2020", "strict": true, "noEmit": true, "baseUrl": "..", "paths": { "rust-blog-plugin-sdk": ["plugins-sdk-js"] } } } ``` ### 2. 编写 TypeScript ```typescript /// var Plugin: PluginHooks = { on_post_creating(inputJson: string): string { var input = JSON.parse(inputJson); input.excerpt = input.content.substring(0, 200); return JSON.stringify(input); } }; ``` ### 3. 编译为 JS ```bash # 用 esbuild 编译 npx esbuild plugins-examples-js/my-plugin/src/index.ts \ --outfile=plugins-examples-js/my-plugin/index.js \ --bundle --format=iife --target=es2021 # 或用 justfile just plugins-ts-build ``` --- ## 安全限制 JS 插件运行在 QuickJS 沙箱中: | 限制 | 说明 | |------|------| | **内存** | 默认 32MB,可在 `[permissions]` 中配置 | | **超时** | 默认 5000ms,超时自动中断 | | **无文件系统** | 不能读写文件 | | **无网络** | 不能发起 HTTP 请求 | | **隔离作用域** | 每个插件独立的全局对象,互不干扰 | --- ## 热重载 当 `PLUGIN_HOT_RELOAD=true` 时,修改 `plugins/` 目录下的 `.js` 或 `.wasm` 文件会自动触发插件重载: ``` PLUGIN_HOT_RELOAD=true cargo run ``` > 热重载仅监听文件变化,不会自动编译 TypeScript。开发 TS 插件时需手动或用 watch 模式运行 esbuild。 --- ## 完整示例 ### Welcome Email(JS) ```javascript // plugins/welcome-email/index.js var Plugin = { on_login: function(dataJson) { var data = JSON.parse(dataJson); if (data.success) { Host.log("info", "User logged in: " + data.email); } } }; ``` ### SEO Optimizer(JS) ```javascript // plugins/seo-optimizer-js/index.js var Plugin = { on_post_creating: function(inputJson) { var input = JSON.parse(inputJson); if (!input.excerpt || input.excerpt === "") { var plain = input.content .replace(/```[\s\S]*?```/g, "") .replace(/[#*_`]/g, "") .replace(/\s+/g, " ") .trim(); input.excerpt = plain.substring(0, 200); if (plain.length > 200) input.excerpt += "..."; } return JSON.stringify(input); }, filter_html: function(html) { var meta = ''; return html.replace("", "" + meta); } }; ``` --- ## 调试技巧 1. **查看日志** — `Host.log()` 输出到宿主 tracing 日志,开发环境默认打印到终端 2. **错误不崩溃** — 插件 Hook 抛异常时,宿主会跳过该插件继续执行,不影响请求 3. **禁用插件** — 在 `.env` 中设置 `PLUGIN_DISABLED=com.example.bad-plugin` 4. **超时测试** — 设置短超时 `[permissions] timeout_ms = 100` 来验证中断机制