WordPressプラグインの移植
このページで分かること
- EmDashに移植する候補になるプラグインと、AstroやEmDashで置き換わるため移植しないプラグインの見分け方
- サンドボックス型とネイティブ型のパッケージの形式の違い
- WordPressのフック、オプション、独自テーブル、RESTルート、管理画面の対応づけと、移植の手順
このページの目次
WordPressのプラグインを移植するには、コンテンツに関する動作、保存データ、HTTPのルート、ユーザーインターフェースを切り分けます。そのうえで、それらの要件に対応するEmDashのプラグインの形式を選びます。
EmDashに移植する対象かの確認
移植の候補になるのは、コンテンツの検証、外部APIの呼び出し、バックグラウンド処理、独自の保存レコード、設定、管理画面のツールなど、WordPressのコアに依存しない動作を持つプラグインです。
AstroやEmDashがすでに置き換えている、WordPress固有の事柄を実装するだけのプラグインは移植しません。たとえば、PHPのページキャッシュ、WordPressのリライトルール、テーマのテンプレートの選択、WordPressのコアのグローバル変数の変更などです。
カスタム投稿タイプやフィールドを定義するだけで、実行時の動作がほとんどないプラグインは、プラグインではなく、EmDashのコレクションとシードファイルを作成します。
やさしい解説
WordPressのプラグインのすべてを、EmDashのプラグインに作り直す必要はありません。ページのキャッシュやURLのリライトのように、AstroやEmDashがすでに担っている機能は移植の対象外です。カスタム投稿タイプを作るだけのプラグイン(ACFでフィールドを足すだけ、など)は、EmDashではコレクションとシードファイルで置き換えます。移植するのは、外部APIの呼び出しや独自のデータの保存など、サイト固有の動作を持つものです。
サンドボックス型とネイティブ型の選択
まずプラグイン形式の選び方を読みます。2つの形式はフックの名前と PluginContext のAPIが共通ですが、ソースのパッケージは異なります。
| 要件 | サンドボックス型 | ネイティブ型 |
|---|---|---|
| レジストリからのインストール | 可 | 不可 |
| 分離された実行環境 | 可(ランナーを設定した場合) | 不可 |
| フック、ルート、KV、構造化ストレージ | 可 | 可 |
| Block Kitの管理画面のページ | 可 | 可 |
| 独自のReactの管理画面のコンポーネント | 不可 | 可 |
| 公開側の描画のためのAstroのコンポーネント | 不可 | 可 |
| そのままのページフラグメント | 不可 | 可 |
移植にネイティブ型でしか使えないビルド時の仕組みやユーザーインターフェースが必要な場合にだけ、ネイティブ型を選びます。
サンドボックス型のパッケージの形式
emdash-plugin init で、現在のサンドボックス型の形式が作成されます。
my-plugin/
├── emdash-plugin.jsonc
├── src/
│ └── plugin.ts
├── tests/
│ └── plugin.test.ts
├── package.json
└── tsconfig.json
マニフェストには、識別情報、公開者、権限(Capability)、許可するホスト、ストレージの宣言が含まれます。バージョンは通常、package.json から取得されます。
次のマニフェストは、インデックス付きのストレージのコレクションを1つと、content:afterSave に必要な権限を宣言しています。
{
"$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 で型付けしたプレーンなオブジェクトを既定のエクスポートにします。サンドボックス型のフックのハンドラーは { handler } を使い、サンドボックス型のルートのハンドラーは (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、ビルドの出力、サイトへの登録については、はじめてのサンドボックス型プラグインを参照してください。
ネイティブ型のパッケージの形式
ネイティブ型のパッケージは、astro.config.mjs 用の記述子のファクトリー関数と、definePlugin() で作る実行時のファクトリー関数の両方をエクスポートします。任意の管理画面用とAstro用のエントリーポイントは、パッケージの別のエクスポートにします。
my-native-plugin/
├── src/
│ ├── index.ts
│ ├── admin.tsx
│ └── astro/
│ └── index.ts
├── package.json
└── tsconfig.json
次の簡略化したネイティブ型のエントリーポイントは、必須の2つの部分を示しています。
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;
ネイティブ型のフックのハンドラーは、関数を直接指定できます。ネイティブ型のルートのハンドラーは、1つにまとめられたコンテキストの引数を受け取ります。記述子と実行時の両方にある id、version、権限、エントリーポイントは、一致させておきます。
Reactの管理画面のページ、Portable Textの描画コンポーネント、ページフラグメントを追加する前に、はじめてのネイティブ型プラグインを読みます。
WordPressの動作の対応づけ
フック
WordPressのアクションやフィルターは、名前だけでなく、目的に合わせて対応づけます。
| 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 |
フックのイベントには、それぞれ型の付いた独自の形があります。WordPressのコールバックの引数を置き換える前に、フックのリファレンスを確認します。
エントリーのデータを受け取るコンテンツのフックには、content:read が必要です。移植したプラグインが呼び出すAPIに合わせて、権限を追加します。
| 権限 | 使えるようになるAPI |
|---|---|
content:read |
コンテンツの読み取りと、エントリーのデータを渡すコンテンツのフックの登録 |
content:write |
コンテンツの作成、更新、公開、削除。コンテンツの読み取りも含みます |
media:read |
メディアのレコードの読み取り |
media:write |
メディアの作成と更新。メディアの読み取りも含みます |
network:request |
allowedHosts に記載したホストに対する ctx.http の使用 |
やさしい解説
WordPressの save_post や add_attachment などのフックには、EmDashに目的の近いフックがあります(例:save_post → content:afterSave)。ただし、EmDashのフックに渡されるデータの形はWordPressとは違うため、名前を置き換えるだけでは動きません。また、EmDashのプラグインは、使うAPIに応じた権限(content:read など)を宣言しておく必要があります。
オプションと独自テーブル
プラグインごとの小さな値には、ctx.kv を使います。キーはプラグインごとに分離されます。ユーザーの設定には settings:、内部の状態には state: を使うのが慣例です。
クエリで取得するプラグインのレコードには、宣言した ctx.storage.<collection> のコレクションを使います。ストレージの宣言は、サンドボックス型プラグインでは emdash-plugin.jsonc に、ネイティブ型プラグインでは definePlugin() に書きます。プラグインのコードからEmDashのデータベースを開いたり、SQLに値を埋め込んだりしないでください。
次の比較は、WordPressのグローバル変数を新しいプラグインに持ち込まずに、1つのオプションの値を移植する例です。
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.kv.set("settings:apiKey", newApiKey);
}
export async function readApiKey(ctx: PluginContext) {
return await ctx.kv.get<string>("settings:apiKey") ?? "";
}
WordPressの独自テーブルの場合は、ストレージを宣言する前に、絞り込みと並べ替えに使うフィールドを特定します。次のサンドボックス型のマニフェストの一部は、クエリで使う2つのフィールドの両方をインデックスにしています。
"storage": {
"jobs": { "indexes": ["status", "createdAt"] }
}
これで、実行時にジョブのレコードを保存し、クエリで取得できます。
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,
});
このクエリを実行する前に、マニフェストまたはネイティブ型のストレージの定義で、status と createdAt の両方をインデックスとして宣言します。
RESTエンドポイント
WordPressのRESTルートは、プラグインのルートに対応づけます。EmDashは、それを /_emdash/api/plugins/<plugin-id>/<route-name> に配置します。ルートが入力を受け付ける場合は inputSchema を定義し、JSONにシリアライズできるデータを返します。
設定と管理画面のページ
サンドボックス型プラグインは、Block Kitで管理画面のページを記述し、ルートとKVを通して値を読み書きします。管理画面のアプリケーションにReactを持ち込むことはありません。
ネイティブ型プラグインは、admin.settingsSchema を使って、フォームを自動生成できます。独自のReactのページ、ウィジェット、フィールドのウィジェット、一覧の列には、パッケージの adminEntry のエクスポートを使います。
ファイルとメディア
アップロードされたファイルや生成したファイルには、メディアのAPIを使います。サンドボックス型プラグインは、ファイルシステムにアクセスできません。ネイティブ型プラグインはホストのプロセスを共有しますが、デプロイ先のローカルにファイルを書き込むのは、環境を移しても使えるストレージの方法ではありません。
プラグインの移植
-
WordPressのフック、オプション、独自テーブル、cronのジョブ、RESTルート、管理画面のページ、ブロック、ショートコード、外部のホストを洗い出します。
-
Astroのルーティング、EmDashのコンテンツモデル、デプロイ先のプラットフォームが担う動作を取り除きます。
-
サンドボックス型とネイティブ型のどちらのパッケージの形式にするかを選びます。残った動作に必要なすべての権限と許可するホストを記録します。
-
KVのキーと、構造化ストレージのコレクションを定義します。
whereやorderByで使うすべてのフィールドにインデックスを追加します。 -
目に見える動作を1つずつ移植します。代表的なコンテンツと失敗する場合で、フックやルートをテストします。
-
Block Kitやネイティブ型の管理画面のUIは、土台になるルートとストレージの動作ができてから追加します。
-
インストール、アップグレード、有効化、無効化、データを削除する場合と削除しない場合のアンインストール、権限の変更をテストします。
次のステップ
- 信頼の取り決めとパッケージのメタデータについては、サンドボックス型プラグインのマニフェストを参照してください。
- コンテンツ、メディア、ネットワークのホスト、フックへのアクセスについては、権限(Capabilities)を参照してください。
- KVとインデックス付きのコレクションについては、ストレージを参照してください。
- ネイティブ型でしか作れないUIについては、React管理画面の拡張を参照してください。