Files
raisfast/docs/plugin-dev-guide.md
T

8.0 KiB
Raw Blame History

插件开发指南

rust-blog 支持两种插件运行时:WASMRust 编写)和 JavaScriptQuickJS)。 本文档聚焦 JS 插件开发。


快速开始

1. 创建插件目录

plugins/my-plugin/
├── plugin.toml    # 插件清单
└── index.js       # JS 入口

2. 编写 plugin.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

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) {
        // 在 <head> 后注入 OG 标签
        return html.replace("<head>", '<head><meta property="og:type" content="article">');
    }
};

4. 部署

# 手动复制
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, ... }
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 }
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
render_markdown: function(content) {
    // 自定义渲染逻辑
    return "<p>" + content + "</p>";
}

Route Handler — 自定义路由

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 模式:

[hooks.handle_route]
match = "/api/v1/custom/*"    # glob 风格,* 匹配单段路径
priority = 5

宿主 API

插件通过全局 Host 对象与宿主交互。

Host.log(level, message)

写入宿主日志。

Host.log("info", "这条消息会出现在宿主日志中");
Host.log("warn", "警告信息");
Host.log("error", "错误信息");

Host.getConfig(key)

读取宿主配置。返回字符串或 null

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.secretdatabase_url 等敏感配置不暴露给插件。


TypeScript 开发

1. 安装 SDK 类型定义

在插件目录创建 tsconfig.json

{
  "compilerOptions": {
    "target": "ES2021",
    "module": "ES2020",
    "strict": true,
    "noEmit": true,
    "baseUrl": "..",
    "paths": {
      "rust-blog-plugin-sdk": ["plugins-sdk-js"]
    }
  }
}

2. 编写 TypeScript

/// <reference path="../../plugins-sdk-js/index.d.ts" />

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

# 用 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 EmailJS

// 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 OptimizerJS

// 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 = '<meta property="og:type" content="article">';
        return html.replace("<head>", "<head>" + meta);
    }
};

调试技巧

  1. 查看日志Host.log() 输出到宿主 tracing 日志,开发环境默认打印到终端
  2. 错误不崩溃 — 插件 Hook 抛异常时,宿主会跳过该插件继续执行,不影响请求
  3. 禁用插件 — 在 .env 中设置 PLUGIN_DISABLED=com.example.bad-plugin
  4. 超时测试 — 设置短超时 [permissions] timeout_ms = 100 来验证中断机制