ネイティブプラグインは、EmDash が Astro サイトと同じプロセスにインポートする npm パッケージです。このチュートリアルでは、コンテンツの保存をログに記録するプラグインを作成し、サイトにインストールして、astro.config.mjs に登録します。
React 管理コンポーネント、Astro レンダリングコンポーネント、信頼できるページフラグメントなど、インプロセスの機能がプラグインに必要な場合は、ネイティブ形式を使用します。フック、ルート、ストレージ、Block Kit で機能をまかなえる場合は、サンドボックス化プラグインから始めてください。プラグイン形式の選択で各形式を比較しています。
前提条件
pnpm を使用し、開発サーバーを実行できる EmDash サイトから始めます。
コマンドでは、サイトのディレクトリを my-emdash-site とし、その隣に plugin-activity を作成します。my-emdash-site は、お使いのサイトのディレクトリ名に置き換えてください。
パッケージを作成して登録する
-
サイトの隣にネイティブパッケージのディレクトリを作成します。
mkdir -p ../plugin-activity/src cd ../plugin-activity -
パッケージのメタデータとビルドスクリプトを追加します。
{ "name": "@example/plugin-activity", "version": "0.1.0", "type": "module", "main": "./dist/index.mjs", "exports": { ".": { "types": "./dist/index.d.mts", "import": "./dist/index.mjs" } }, "files": ["dist"], "scripts": { "build": "tsdown src/index.ts --format esm --dts --clean", "dev": "tsdown src/index.ts --format esm --dts --watch", "typecheck": "tsc --noEmit" }, "peerDependencies": { "emdash": "*" }, "devDependencies": { "emdash": "*", "tsdown": "^0.20.0", "typescript": "^5.9.0" } }npm パッケージ名は
@example/plugin-activityです。以下のランタイムコードでは、プラグイン API ルートの URL に収まるよう、スコープなしのプラグイン IDplugin-activityを使用します。 -
TypeScript の設定を追加します。
{ "compilerOptions": { "target": "ES2022", "module": "preserve", "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "declaration": true, "outDir": "./dist", "rootDir": "./src" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } -
パッケージの依存関係をインストールします。
pnpm install -
コンテンツ保存フックを含む
src/index.tsを作成します。import { definePlugin } from "emdash"; import type { PluginDescriptor } from "emdash"; export interface ActivityPluginOptions { logUpdates?: boolean; } export function activityPlugin( options: ActivityPluginOptions = {}, ): PluginDescriptor<ActivityPluginOptions> { return { id: "plugin-activity", version: "0.1.0", format: "native", entrypoint: "@example/plugin-activity", options, }; } export function createPlugin(options: ActivityPluginOptions = {}) { return definePlugin({ id: "plugin-activity", version: "0.1.0", capabilities: ["content:read"], hooks: { "content:afterSave": async (event, ctx) => { if (!event.isNew && options.logUpdates === false) return; ctx.log.info("Content saved", { collection: event.collection, contentId: event.content.id, isNew: event.isNew, }); }, }, }); } export default createPlugin;content:afterSaveにはcontent:read機能が必要です。この機能がない場合、EmDash はフックをスキップします。 -
パッケージをビルドします。
pnpm build -
ローカルパッケージをサイトにインストールします。
cd ../my-emdash-site pnpm add ../plugin-activity -
ディスクリプターファクトリーを EmDash インテグレーションに登録します。
import { defineConfig } from "astro/config"; import emdash from "emdash/astro"; import { activityPlugin } from "@example/plugin-activity"; export default defineConfig({ integrations: [ emdash({ plugins: [activityPlugin({ logUpdates: true })], }), ], });ネイティブのディスクリプターは
sandboxedではなくpluginsに指定します。sandboxed配列にネイティブのディスクリプターを指定すると、EmDash は拒否します。 -
サイトを起動し、管理パネルでエントリを保存します。
pnpm devサーバーログには、コレクション、コンテンツ ID、エントリが新規作成されたかどうかとともに
Content savedが出力されます。
ディスクリプターとランタイムの境界
パッケージのエクスポートには 2 つの役割があります。EmDash は、それぞれを別の段階で使用します。
- ディスクリプターファクトリーの
activityPlugin()は、Astro が設定を評価している間に実行されます。シリアライズ可能なビルド時メタデータ(id、version、format、entrypoint、options)を返します。React と Astro のエントリポイントも、このディスクリプターに含めます。 - 名前付きエクスポートの
createPlugin()は、EmDash の初期化時に実行されます。EmDash はentrypointからこれをインポートし、シリアライズされたoptionsを渡して、definePlugin()が解決したプラグインを期待します。
名前付きエクスポートの createPlugin は必須です。デフォルトエクスポートはパッケージの利用者には便利かもしれませんが、EmDash のネイティブローダーは createPlugin を名前でインポートします。
id と version は、ディスクリプターと definePlugin() で同一にしてください。プラグイン ID には、plugin-activity のようなスコープなしのケバブケースを使用し、npm スコープはパッケージ名と entrypoint に残します。こうすると、ID を API ルート URL の単一のプラグインセグメントとして使えます。
プラグインの識別とバージョニングに、使用できる ID とバージョンの形式が記載されています。
ランタイムの動作は definePlugin() に記述します。
capabilitiesとallowedHostsstoragehooksとroutesadminの設定、ページ、ウィジェット、Portable Text の宣言
ディスクリプターには、Astro がビルド時にインポートまたは公開する必要のある静的なエントリを含めます。どの管理フィールドで、ディスクリプターとランタイムの宣言を対応させる必要があるかは、各トピックのガイドを参照してください。
ネイティブルートハンドラー
ネイティブルートハンドラーは、単一の RouteContext を受け取ります。これは、検証済みの入力とリクエストデータを、通常の PluginContext と組み合わせたものです。
routes: {
status: {
permission: "plugins:read",
handler: async (ctx) => ({
pluginId: ctx.plugin.id,
callerId: ctx.user?.id ?? null,
}),
},
},
同等のサンドボックス化ハンドラーは、(routeCtx, ctx) を 2 つの引数として受け取ります。認証、権限、入力スキーマ、ルート URL については、それ以外は共通の API ルートの規約に従います。
ネイティブルートが request.body を宣言する場合は、definePluginRoute() でラップします。このヘルパーは、
ボディモードから ctx.input を推論します。response: "raw" を持つネイティブルートは pluginResponse() を返します。
どちらのヘルパーも emdash からインポートします。共通の API ルートガイドに、ボディモード、制限、レスポンス
ポリシー、互換性のデフォルトが記載されています。
シークレットとバインディングを読み取る
ネイティブプラグインのフックハンドラーとルートハンドラーは、Astro コンテキストではなくプラグインコンテキストを受け取るため、その中では Astro.locals を使えません。シークレットとプラットフォームのバインディングは、ランタイム環境から読み取ってください。
デプロイのシークレットは process.env から読み取ります。Node.js では、ホスティングプラットフォームのシークレットマネージャーが提供します。Cloudflare Workers では、wrangler secret put で設定したシークレットは、Worker のシークレットで説明されているとおり、互換性日付が 2025-04-01 以降の nodejs_compat フラグを通じて process.env に渡されます。
サイト管理者が入力する認証情報には、代わりに生成される設定フォームで secret フィールドを宣言します。EmDash は、保存前に EMDASH_ENCRYPTION_KEY で値を暗号化するため、管理者がフィールドを保存する前にこのキーを設定してください。プラグインは ctx.settings.get() で値を読み取ります。
Cloudflare Workers では、キュー、R2 バケット、send_email などのバインディングは、cloudflare:workers の env 上のオブジェクトです。Astro は Node.js で astro.config.mjs を読み込み、そのファイルはディスクリプターファクトリーのためにプラグインパッケージをインポートします。Node.js は cloudflare:workers を読み込めないため、モジュールの先頭ではなくハンドラー内でインポートしてください。次のフックは、保存された各エントリの ID を、ACTIVITY_QUEUE としてバインドされたキューに送信します。
hooks: {
"content:afterSave": async (event) => {
const { env } = await import("cloudflare:workers");
await env.ACTIVITY_QUEUE.send({ contentId: event.content.id });
},
},
cloudflare:workers モジュールは Workers ランタイムでのみ解決されるため、これをインポートするプラグインは、Cloudflare アダプターを使用するサイトでのみ動作します。
このインポートの型チェックを行うには、@cloudflare/workers-types をパッケージの devDependencies と、tsconfig.json の compilerOptions.types に追加します。次に、プラグインが読み取る各バインディングを Cloudflare.Env インターフェースで宣言します。次の宣言は、前の例のキューに型を付けます。
declare namespace Cloudflare {
interface Env {
ACTIVITY_QUEUE: Queue;
}
}
ほかのサーフェスを追加する
- React 管理ページとウィジェットでは、設定、カスタムページ、ダッシュボードウィジェット、フィールドウィジェット、エディターパネル、一覧の列を扱います。
- Portable Text レンダリングコンポーネントでは、プラグインブロック用の Astro コンポーネントを登録します。
- ページフラグメントでは、信頼できるスクリプトや HTML を公開ページに追加します。
- ネイティブプラグインの配布では、ビルドとソースのエントリポイントを npm 向けにパッケージ化します。