移植 WordPress 外掛程式時,應將內容行為、儲存資料、HTTP 路由與使用者介面分開處理,再選擇符合這些需求的 EmDash 外掛程式格式。
判斷外掛程式是否適合 EmDash
合適的外掛程式通常擁有獨立於 WordPress 核心的行為,例如內容驗證、外部 API 呼叫、背景處理、自訂儲存記錄、設定或管理工具。
不要移植僅用於實作 Astro 或 EmDash 已取代之 WordPress 功能的外掛程式。例如 PHP 頁面快取、WordPress 重寫規則、佈景主題範本選擇或修改 WordPress 核心全域變數。
若外掛程式只定義自訂文章類型或欄位、幾乎沒有執行階段行為,應改為建立 EmDash 集合與 seed 檔案,而非外掛程式。
選擇 sandboxed 或 native
從選擇外掛程式格式開始。兩種格式共用 hook 名稱與 PluginContext API,但原始碼套件結構不同。
| 需求 | Sandboxed | Native |
|---|---|---|
| 從 Registry 安裝 | 是 | 否 |
| 隔離執行環境 | 是(需設定 runner) | 否 |
| hooks、路由、KV、結構化儲存 | 是 | 是 |
| Block Kit 管理頁 | 是 | 是 |
| 自訂 React 管理元件 | 否 | 是 |
| 用於公開渲染的 Astro 元件 | 否 | 是 |
| 原始頁面片段 | 否 | 是 |
僅當移植需要 native 專屬的建置時或 UI 能力時,才選擇 native。
Sandboxed 套件格式
emdash-plugin init 會建立目前的 sandboxed 格式:
my-plugin/
├── emdash-plugin.jsonc
├── src/
│ └── plugin.ts
├── tests/
│ └── plugin.test.ts
├── package.json
└── tsconfig.json
資訊清單包含識別、publisher、capabilities、允許的主機與儲存宣告。版本通常來自 package.json。
以下資訊清單宣告一個具索引的儲存集合,以及 content:afterSave 所需的 capability:
{
"$schema": "./node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",
"slug": "read-time",
"publisher": "did:plc:abc123def456",
"license": "MIT",
"author": { "name": "Example Author" },
"security": { "email": "security@example.com" },
"capabilities": ["content:read"],
"allowedHosts": [],
"storage": {
"calculations": { "indexes": ["contentId", "updatedAt"] }
}
}
src/plugin.ts 預設匯出 SandboxedPlugin 類型的 plain object。sandboxed hook 處理器使用 { handler };sandboxed 路由處理器接收 (routeCtx, ctx):
import type { SandboxedPlugin } from "emdash/plugin";
export default {
hooks: {
"content:afterSave": {
handler: async (event, ctx) => {
await ctx.storage.calculations.put(event.content.id, {
contentId: event.content.id,
updatedAt: new Date().toISOString(),
});
},
},
},
routes: {
recent: {
handler: async (_routeCtx, ctx) => {
const result = await ctx.storage.calculations.query({
orderBy: { updatedAt: "desc" },
limit: 10,
});
return { items: result.items };
},
},
},
} satisfies SandboxedPlugin;
該路由位於 /_emdash/api/plugins/read-time/recent。查詢若要依某欄位篩選或排序,須先將該儲存欄位宣告為索引。
使用 emdash-plugin build 建置套件;不要為此格式手寫 src/index.ts 描述符。產生的 package.json、建置輸出與站點註冊見你的第一個 sandboxed 外掛程式。
Native 套件格式
native 套件同時匯出供 astro.config.mjs 使用的描述符 factory,以及以 definePlugin() 建構的執行階段 factory。選用的 admin 與 Astro 進入點為獨立套件匯出。
my-native-plugin/
├── src/
│ ├── index.ts
│ ├── admin.tsx
│ └── astro/
│ └── index.ts
├── package.json
└── tsconfig.json
以下精簡 native 進入點展示兩個必要部分:
import { definePlugin } from "emdash";
import type { PluginDescriptor } from "emdash";
export interface ReadTimeOptions {
wordsPerMinute?: number;
}
export function readTimePlugin(options: ReadTimeOptions = {}): PluginDescriptor {
return {
id: "read-time",
version: "0.1.0",
format: "native",
entrypoint: "@example/plugin-read-time",
capabilities: ["content:read"],
options,
};
}
export function createPlugin(options: ReadTimeOptions = {}) {
return definePlugin({
id: "read-time",
version: "0.1.0",
capabilities: ["content:read"],
admin: {
settingsSchema: {
wordsPerMinute: {
type: "number",
label: "Words per minute",
default: options.wordsPerMinute ?? 200,
min: 1,
},
},
},
hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Content saved", { id: event.content.id });
},
},
});
}
export default createPlugin;
native hook 處理器可直接為函式。native 路由處理器接收一個合併的上下文引數。請保持描述符與執行階段中 id、version、capabilities 與 entrypoint 一致。
在新增 React 管理頁、Portable Text 渲染器或頁面片段之前,請先閱讀你的第一個 native 外掛程式。
對應 WordPress 行為
Hooks
對應 WordPress action 或 filter 的意圖,而不只是名稱:
| WordPress | EmDash |
|---|---|
register_activation_hook() | 首次安裝用 plugin:install,啟用用 plugin:activate |
register_uninstall_hook() | plugin:uninstall |
wp_insert_post_data | content:beforeSave |
save_post | content:afterSave |
before_delete_post | content:beforeDelete |
deleted_post | content:afterDelete |
wp_handle_upload_prefilter | media:beforeUpload |
add_attachment | media:afterUpload |
hook 事件有各自的型別形狀。在轉換 WordPress 回呼引數前,請查閱 hook 參考。
接收項目資料的内容 hook 需要 content:read。依移植程式呼叫的 API 新增 capability:
| Capability | 可用的 API |
|---|---|
content:read | 讀取內容,註冊暴露項目資料的内容 hook |
content:write | 建立、更新、發佈或刪除內容(亦含讀取) |
media:read | 讀取媒體記錄 |
media:write | 建立或更新媒體(亦含讀取) |
network:request | 對 allowedHosts 中列出的主機使用 ctx.http |
選項與自訂資料表
使用者設定使用 ctx.settings,小型內部值使用 ctx.kv。兩者皆依外掛程式隔離。在 admin.settingsSchema 中將憑證宣告為 secret 欄位,以便 EmDash 加密。
可查詢的外掛程式記錄使用已宣告的 ctx.storage.<collection> 集合。sandboxed 的儲存宣告在 emdash-plugin.jsonc,native 在 definePlugin() 中。不要開啟 EmDash 資料庫,也不要在外掛程式程式碼中拼接 SQL。
以下對照在不向新外掛程式暴露 WordPress 全域變數的情況下移植一個選項值:
WordPress
$api_key = get_option('read_time_api_key', '');
update_option('read_time_api_key', $new_api_key); EmDash
import type { PluginContext } from "emdash/plugin";
export async function saveApiKey(ctx: PluginContext, newApiKey: string) {
await ctx.settings.set("apiKey", newApiKey);
}
export async function readApiKey(ctx: PluginContext) {
return await ctx.settings.get<string>("apiKey") ?? "";
} 對於 WordPress 自訂資料表,在宣告儲存前先找出用於篩選與排序的欄位。以下 sandboxed 資訊清單片段為查詢使用的兩個欄位都建立索引:
"storage": {
"jobs": { "indexes": ["status", "createdAt"] }
}
執行階段即可儲存並查詢 job 記錄:
await ctx.storage.jobs.put("job-123", {
status: "pending",
createdAt: new Date().toISOString(),
});
const pending = await ctx.storage.jobs.query({
where: { status: "pending" },
orderBy: { createdAt: "asc" },
limit: 50,
});
執行該查詢前,請在資訊清單或 native 儲存定義中將 status 與 createdAt 均宣告為索引。
REST 端點
將 WordPress REST 路由對應為外掛程式路由。EmDash 將其掛載在 /_emdash/api/plugins/<plugin-id>/<route-name>。若路由接受輸入,請定義 inputSchema,並回傳可 JSON 序列化的資料。
設定與管理頁
sandboxed 外掛程式以 Block Kit 描述管理頁,並透過路由與 KV 讀寫值。它們不會向管理應用程式交付 React。
native 外掛程式可用 admin.settingsSchema 產生表單。自訂 React 頁、小工具、欄位小工具或列表欄請使用套件匯出 adminEntry。
檔案與媒體
上傳或產生的檔案請使用媒體 API。sandboxed 外掛程式無法存取檔案系統。native 外掛程式與宿主程序共用,但寫入部署端本機檔案並非可移植的儲存策略。
移植外掛程式
-
盤點 WordPress hooks、選項、自訂資料表、cron、REST 路由、管理頁、區塊、短代碼與外部主機。
-
移除屬於 Astro 路由、EmDash 內容模型或部署平台的行為。
-
選擇 sandboxed 或 native 套件格式,並記錄剩餘行為所需的 capability 與允許的主機。
-
定義 KV 鍵與結構化儲存集合。為
where或orderBy使用的每個欄位新增索引。 -
每次移植一種可觀察的行為,以代表性內容與失敗情境測試 hook 或路由。
-
僅在底層路由與儲存可用後,再新增 Block Kit 或 native 管理 UI。
-
測試安裝、升級、啟用、停用、含/不含資料刪除的解除安裝,以及 capability 變更。
下一步
- Sandboxed 外掛程式資訊清單:信任契約與套件中繼資料。
- Capabilities:內容、媒體、網路主機與 hooks 的存取。
- Storage:KV 與索引集合。
- React 管理頁:native 專屬 UI。