你的第一個沙箱外掛程式

本頁內容

本指南從零開始建置一個最小的沙箱外掛程式。該外掛程式記錄每次內容儲存並公開一個 API 路由。它在由設定的沙箱執行器提供的隔離執行環境中執行。當網站操作員將其從 sandboxed: [] 移動到 plugins: [] 時(例如在沒有沙箱執行器的平台上),相同的程式碼也會在處理程序內執行。

如果你還沒有在沙箱和原生之間做出決定,請先閱讀選擇外掛程式格式

兩個部分

一個沙箱外掛程式包含:

  1. emdash-plugin.jsonc — 手動編輯的清單:身份、信任契約(capabilities、hosts、storage)和設定檔欄位。無程式碼。
  2. src/plugin.ts — 執行環境:鉤子和路由。僅從 emdash/plugin 匯入型別;無執行時 emdash 匯入。

emdash-plugin build 讀取兩者並產生網站使用的 dist/ 工件。

以下範例顯示了完整外掛程式的檔案配置:

my-plugin/
├── emdash-plugin.jsonc   # 身份 + 信任契約 + 設定檔
├── src/
│   └── plugin.ts         # 鉤子、路由 — 在沙箱執行環境中執行
├── package.json
└── tsconfig.json

設定套件

  1. 建立目錄和 package.json。建置命令是 emdash-plugin build;無需編寫 tsdown 呼叫。

    {
    	"name": "@my-org/plugin-hello",
    	"version": "0.1.0",
    	"type": "module",
    	"main": "dist/index.mjs",
    	"exports": {
    		".": {
    			"import": "./dist/index.mjs",
    			"types": "./dist/index.d.mts"
    		},
    		"./sandbox": "./dist/plugin.mjs"
    	},
    	"files": ["dist", "emdash-plugin.jsonc"],
    	"scripts": {
    		"build": "emdash-plugin build",
    		"dev": "emdash-plugin dev"
    	},
    	"peerDependencies": {
    		"emdash": ">=0.13.0"
    	},
    	"devDependencies": {
    		"@emdash-cms/plugin-cli": "0.2.0",
    		"emdash": ">=0.13.0",
    		"typescript": "^5.9.0"
    	}
    }

    "." 是網站匯入的產生描述符;"./sandbox" 是建置的執行時檔案。emdash-plugin build 產生兩者。

  2. 新增 tsconfig.json

    {
    	"compilerOptions": {
    		"target": "ES2022",
    		"module": "preserve",
    		"moduleResolution": "bundler",
    		"strict": true,
    		"esModuleInterop": true,
    		"verbatimModuleSyntax": true,
    		"skipLibCheck": true,
    		"types": []
    	},
    	"include": ["src/**/*"],
    	"exclude": ["node_modules"]
    }

編寫清單

emdash-plugin.jsonc 包含外掛程式的身份(slug)、其信任契約(capabilitiesallowedHostsstorage)、設定檔欄位和發布者固定

以下範例顯示了 hello 外掛程式的完整清單:

{
	"$schema": "./node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",

	"slug": "plugin-hello",
	"publisher": "did:plc:abc123def456", // your Atmosphere account DID

	"license": "MIT",
	"author": { "name": "Jane Doe", "url": "https://example.com" },
	"security": { "email": "security@example.com" },

	"capabilities": [],
	"allowedHosts": [],
	"storage": { "events": { "indexes": ["timestamp"] } }
}

關於此清單的說明:

  • slug 是 URL 安全的 ID,不是 npm 套件名稱。 /^[a-z][a-z0-9_-]*$/,最大 64 個字元。它是外掛程式路由 URL(/_emdash/api/plugins/<slug>/...)中的單一路徑段,也是為儲存索引產生的 SQL 識別符的一部分,因此 @/、前導數字和大寫字母都會失敗。將無作用域的 slugplugin-hello)與有作用域的 npm 套件名稱配對。
  • storage 提前宣告集合。 ctx.storage.events 在執行時工作只是因為 events 在這裡宣告了。存取未宣告的集合會拋出錯誤。
  • 省略了 version 建置從 package.json 讀取它,因此有一個唯一的真實來源。參見清單參考
  • 信任契約是同意。 稍後變更 capabilitiesallowedHostsstorage 需要版本升級 — 已安裝的網站已同意舊契約。

編寫執行環境

src/plugin.ts 預設匯出一個用 satisfies SandboxedPlugin 標註的簡單物件。emdash/plugin 僅提供型別,因此沙箱外掛程式對 emdash 沒有執行時相依性。

以下範例將每次內容儲存記錄到外掛程式儲存中,並公開一個傳回最後十次儲存的 recent 路由:

import type { SandboxedPlugin } from "emdash/plugin";

export default {
	hooks: {
		"content:afterSave": {
			handler: async (event, ctx) => {
				ctx.log.info("Content saved", {
					collection: event.collection,
					id: event.content.id,
				});

				await ctx.storage.events.put(`save-${Date.now()}`, {
					timestamp: new Date().toISOString(),
					collection: event.collection,
					contentId: event.content.id,
				});
			},
		},
	},

	routes: {
		recent: {
			handler: async (_routeCtx, ctx) => {
				const result = await ctx.storage.events.query({ limit: 10 });
				return { events: result.items };
			},
		},
	},
} satisfies SandboxedPlugin;

關於執行時檔案的說明:

  • satisfies SandboxedPlugin 型別化一切。 它從鉤子名稱推斷 event(具有完整的規範事件型別)和 ctx 作為 PluginContext,因此處理程式不需要參數標註。像 "content:afterSav" 這樣的拼寫錯誤的鉤子鍵是編譯錯誤。
  • 鉤子處理程式接受 (event, ctx) 事件形狀取決於鉤子名稱;請參閱鉤子指南
  • 路由處理程式接受 (routeCtx, ctx) — 兩個參數。routeCtx{ input, request, requestMeta? }ctx 是相同的 PluginContext。路由可在 /_emdash/api/plugins/<slug>/<route-name> 存取。
  • ctx.storage.events 起作用是因為 events 在清單中宣告了。
  • ctx.kv 始終可用 — 每個外掛程式的鍵值儲存,具有 getsetdeletelist(prefix)

註冊外掛程式

在網站的 astro.config.mjs 中,匯入外掛程式的預設匯出並傳遞它。沙箱外掛程式放在 sandboxed: [] 中;處理程序內外掛程式放在 plugins: [] 中。沙箱外掛程式在兩者中都有效。以下範例使用 sandboxed:

import { defineConfig } from "astro/config";
import emdash from "emdash/astro";
import { sandbox } from "@emdash-cms/cloudflare";
import hello from "@my-org/plugin-hello";

export default defineConfig({
	integrations: [
		emdash({
			sandboxed: [hello],
			sandboxRunner: sandbox(),
		}),
	],
});

sandboxRunner 是可插拔的部分。該範例使用來自 @emdash-cms/cloudflaresandbox(),這是當今大多數網站使用的執行器。如果沒有設定執行器(或設定的執行器在目前平台上不可用),sandboxed: [] 外掛程式在啟動時會被跳過 — 將外掛程式移至 plugins: [] 以在處理程序內執行它。

建置和執行

從外掛程式目錄:

emdash-plugin validate   # 首先檢查清單架構
emdash-plugin build      # 產生 dist/

對於編輯迴圈,執行 emdash-plugin dev(在儲存時重建,在建置失敗時保留最後的良好 dist/)。在網站中,安裝或連結外掛程式(pnpm add file:../plugin-hello 或工作區連結)並啟動開發伺服器。在管理員中儲存一段內容,你應該在日誌中看到 Content saved …GET /_emdash/api/plugins/plugin-hello/recent 傳回最後十個儲存事件。

接下來閱讀什麼