WordPress プラグインの移植

このページ

WordPress プラグインを移植するには、コンテンツの振る舞い、保存データ、HTTP ルート、ユーザーインターフェースを分離します。そのうえで、それらの要件を満たす EmDash プラグイン形式を選びます。

プラグインが EmDash に属するか確認する

適した候補は、コンテンツ検証、外部 API 呼び出し、バックグラウンド処理、カスタム保存レコード、設定、管理ツールなど、WordPress コアから独立した振る舞いを持つものです。

Astro や EmDash がすでに置き換えている WordPress の関心事だけを実装するプラグインは移植しないでください。例として、PHP ページキャッシュ、WordPress のリライトルール、テーマテンプレート選択、WordPress コアのグローバル変更があります。

カスタム投稿タイプやフィールドを定義するだけでランタイム振る舞いがほとんどないプラグインは、プラグインではなく EmDash のコレクションと seed ファイルを作成してください。

sandboxed か native かを選ぶ

まず プラグイン形式の選択 から始めます。両形式は hook 名と PluginContext API を共有しますが、ソースパッケージは異なります。

要件SandboxedNative
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

マニフェストには ID、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 を default export します。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 の両方を export します。任意の admin および Astro エントリポイントは別パッケージ export です。

my-native-plugin/
├── src/
│   ├── index.ts
│   ├── admin.tsx
│   └── astro/
│       └── index.ts
├── package.json
└── tsconfig.json

次の簡略 native エントリポイントは、必要な 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;

native の hook ハンドラは関数を直接指定できます。native のルートハンドラは結合された 1 つのコンテキスト引数を受け取ります。デスクリプタとランタイムの id、version、capabilities、entrypoint は一致させてください。

React 管理ページ、Portable Text レンダラ、ページフラグメントを追加する前に 最初の native プラグイン を読んでください。

WordPress の振る舞いを対応付ける

Hooks

WordPress の action や filter は名前だけでなく意図を対応付けます:

WordPressEmDash
register_activation_hook()初回インストールは plugin:install、有効化は plugin:activate
register_uninstall_hook()plugin:uninstall
wp_insert_post_datacontent:beforeSave
save_postcontent:afterSave
before_delete_postcontent:beforeDelete
deleted_postcontent:afterDelete
wp_handle_upload_prefiltermedia:beforeUpload
add_attachmentmedia:afterUpload

hook イベントにはそれぞれ型付きの形があります。WordPress コールバック引数を移植する前に hook リファレンス を確認してください。

エントリデータを受け取るコンテンツ hook には content:read が必要です。移植後に呼ぶ API に応じて capability を追加します:

Capability利用可能になる API
content:readコンテンツの読み取り、エントリデータを公開するコンテンツ hook の登録
content:writeコンテンツの作成・更新・公開・削除(読み取りも含む)
media:readメディアレコードの読み取り
media:writeメディアの作成・更新(読み取りも含む)
network:requestallowedHosts に列挙されたホスト向けの ctx.http

オプションとカスタムテーブル

ユーザー設定には ctx.settings、小さな内部値には ctx.kv を使います。どちらもプラグインごとに分離されます。資格情報は admin.settingsSchema の secret フィールドとして宣言し、EmDash が暗号化できるようにします。

クエリ可能なプラグインレコードには、宣言済みの ctx.storage.<collection> コレクションを使います。ストレージ宣言は sandboxed では emdash-plugin.jsonc、native では 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.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 ページ、ウィジェット、フィールドウィジェット、一覧列にはパッケージ export の adminEntry を使います。

ファイルとメディア

アップロードまたは生成ファイルにはメディア API を使います。sandboxed プラグインにファイルシステムアクセスはありません。native プラグインはホストプロセスを共有しますが、デプロイ先ローカルファイルへの書き込みは移植性のあるストレージ戦略ではありません。

プラグインを移植する

  1. WordPress の hooks、オプション、カスタムテーブル、cron、REST ルート、管理ページ、ブロック、ショートコード、外部ホストを洗い出します。

  2. Astro ルーティング、EmDash コンテンツモデル、デプロイプラットフォームに属する振る舞いを削除します。

  3. sandboxed または native パッケージ形式を選び、残る振る舞いに必要な capability と許可ホストを記録します。

  4. KV キーと構造化ストレージコレクションを定義します。where または orderBy で使うフィールドごとにインデックスを追加します。

  5. 観測可能な振る舞いを 1 つずつ移植し、代表的なコンテンツと失敗ケースで hook またはルートをテストします。

  6. 基盤のルートとストレージが動いてから Block Kit または native 管理 UI を追加します。

  7. インストール、アップグレード、有効化、無効化、データ削除あり/なしのアンインストール、capability 変更をテストします。

次のステップ