2026年09月28日,Cloudflare 製造的 Astro 原生 CMS「EmDash」的 1.0 正式發布。其後 10 月 2 日發布 1.1,10 月 7 日發布 1.2,短短不到兩週內連續推出更新。
內部部落格「ゆるろぐ」從 0.22 升級到 1.2 的內容寫在這篇文章裡。這次要整理的是 1.0 中變化最大的「外掛程式的機制」。
关于 EmDash 是什麼樣的 CMS,可以看最初的文章。
簡單來說就是「用 Astro + Cloudflare 重新製作的 WordPress」。
1.0 中外掛程式如何改變
0.x 時代的外掛程式簡單來說就是「寫在 Astro 設定檔中的 npm 套件」,由於與網站在同一個行程中運行,外掛程式擁有與網站程式碼相同的權限,就像 WordPress 一樣可以存取資料庫和環境變數。
1.0 版本導入了沙盒和分散式登錄機制。
這是一個非常重大的改進。
- 外掛程式預設在隔離的環境中執行,無法存取網站的內容、媒體、使用者、機密或網路。
- 所需的權限(Capability)由外掛程式端宣告,由網站管理員在安裝時核准。
- 核准的權限不只是「寫在紙上」,執行時環境會實際強制執行。
- 外掛程式可以從管理介面一鍵安裝。分發源是 AT Protocol 上的分散式登錄檔
Cloudflare 的部落格用 WordPress「所有外掛程式共享同一個行程」的模式來對比說明這一點。
越是了解 WordPress 外掛程式引發之事件的人,越會感受到這個論述的震撼。
外掛程式有兩種「執行方式」
首先要掌握的是,同一個外掛程式程式碼有信任(Trusted)和沙箱(Sandboxed)兩種執行模式。
Trusted | Sandboxed | |
|---|---|---|
登錄位置 |
|
|
執行環境 | 與網站相同的行程 | 隔離的 V8 isolate |
Capability | 僅限宣告(不強制執行) | 執行時強制 |
網路 | 無限制 | 已封鎖。僅允許寫入 |
資料存取 | 完整資料庫存取 | 自己的儲存空間/KV,以及授權範圍內的內容 |
Node.js API | 可使用 | 無法使用(僅限 Web API) |
資源限制 | 無 | CPU 50ms、子請求 10、30 秒、約 128MB |
適合的用途 | 自有程式碼、經過審查的 npm 套件 | 第三方、登錄表分發 |
「隔離」的含義是,為每個外掛程式準備一個專用的小型執行環境,並將其與網站主體分開運行。
在 Cloudflare Workers 上,Dynamic Workers 的機制在請求時為外掛程式啟動 Worker。
從 1.0 版本開始,在 Node.js 上運行時,也會在單獨的程序中啟動與 Workers 相同的運行時(workerd),然後加載外掛程式,因此「沙箱不再限於 Cloudflare」。
關鍵是外掛程式的程式碼在兩種模式下都相同,因此通常可以在本地環境中以信任模式開發,在生產環境中放入沙箱。
外掛程式結構:在構建時和執行時分離檔案
EmDash 外掛程式分為兩個部分,執行時機不同。
- descriptor(描述子):
id、version、所需的 Capability、storage 定義等中繼資料,由astro.config.mjs匯入,因此 在構建時在 Vite 中執行。不能具有副作用。 - definition(定義):hooks 或 routes 的内容。在请求时在服务器上运行
my-plugin/
├── src/
│ ├── index.ts # descriptor。astro.config.mjs から import される
│ └── sandbox-entry.ts # definePlugin({ hooks, routes })。実行時にロードされる
└── package.json # "." と "./sandbox" を exports で分ける
// src/index.ts
import type { PluginDescriptor } from "emdash";
export function myPlugin(): PluginDescriptor {
return {
id: "my-plugin",
version: "1.0.0",
format: "standard",
entrypoint: "@my-org/my-plugin/sandbox",
capabilities: ["content:read", "network:request"],
allowedHosts: ["api.example.com"],
options: {},
};
}
// src/sandbox-entry.ts
import { definePlugin, type PluginContext } from "emdash";
export default definePlugin({
hooks: {
"content:afterSave": async (event, ctx: PluginContext) => {
ctx.log.info(`saved ${event.collection}/${event.content.id}`);
},
},
});
在 descriptor 端用 entrypoint 写入 definition 的模块路径,EmDash 在运行时加载它。虽然想把它放在同一个文件中,但 Vite 的构建会将运行时代码卷入,所以分开才是正确做法。
Standard 和 Native
作为另一个维度,插件有 Standard 和 Native 两种格式。
- Standard:上述结构的格式。管理界面 UI 由下文所述的 Block Kit(JSON)描述。既能在 Trusted 中运行,也能在 Sandboxed 中运行,可以发布到注册表。
- Native:可以同时包含用 React 编写的管理界面、Portable Text(正文编辑器)的自定义块和用于网站端渲染的 Astro 组件。作为代价,只能在 Trusted 中运行,无法发布到注册表。
基本上用 Standard 编写,仅在「想向编辑器添加自定义块」或「想在网站 HTML 中插入组件」时才使用 Native。
在 Yuru Log 中,我们使用 Native 插件自制了 Amazon 产品卡和视频嵌入。这两者都是通过向编辑器添加专用块,然后用 Astro 组件渲染的,原理上无法在沙箱中实现(因为在运行时安装的插件无法引入构建时所需的 Astro 组件)。
Capability:权限词汇表
Capability 是沙箱的核心。插件以字符串形式声明「想访问什么」,管理员在安装时的对话框中确认和授予权限。
1.x 中,詞彙已**按照 <對象>:<操作> 的形式整理**。以下是一些常用的例子。
Capability | 可以做的事 |
|---|---|
| 內容的讀取 / 建立、編輯、刪除 |
| 發布、不發布、排程 |
| 媒體的讀取 / 上傳和刪除 |
| 對外部發送 HTTP 請求。僅限 |
| 讀取編輯中的未保存內容 / 提出修正建議 |
| 審查內容並阻止發布 |
此外還有分類、評論、重新導向、使用者、郵件發送等,共 27 種類。
storage 和 KV 無需宣告,隨時可用。各外掛程式有獨立的作用域,看不到其他外掛程式的資料。
0.x 的名稱(read:content、network:fetch 等)已被棄用,但仍可作為別名使用。執行期會將其正規化為現行名稱,因此現有外掛程式可直接執行,在組建時會發出警告。
Hooks:可以對哪些事件進行反應
1.2.0 的執行時中定義了 30 種類的 Hook。包括內容的保存、發布、排程、刪除,媒體上傳,郵件發送,評論投稿與審核,外掛本身的安裝與啟用,以及透過 cron 進行的定期執行。相比 0.x 版本增加了不少,特別是發布前後(content:beforePublish / afterPublish)現在是分開的 Hook,這是一項重大變化。
在 content:beforeSave 中執行 throw 可以取消保存,所以驗證類的外掛應寫在這裡。若想要停止發布本身,需要宣告 hooks.content-policy:register,然後使用發布前的 Hook。
1.2 版本的變更中有項容易被忽視但很重要的改動:升級後首次啟動時,現有外掛的 plugin:install 和 plugin:activate 會重新執行。在 install Hook 中寫入初始資料的外掛,必須寫成即使重複投入也不會損壞的形式。
Block Kit:不執行 JavaScript 的管理介面
在沙箱中,外掛的 JavaScript 無法在瀏覽器中執行。因此管理介面用 Block Kit 以 JSON 描述。外掛返回「標題、表格、表單、切換」等區塊的陣列,EmDash 側負責渲染。
流程如下。使用者打開外掛頁面時,EmDash 向外掛發送「頁面已打開」的請求,外掛返回區塊陣列。使用者提交表單時,這次是帶著輸入值的請求,外掛保存該資料後返回新的區塊陣列。僅透過請求與回應就能成立 UI,這種設計與沙箱的限制相容性很好。
除了管理介面頁面和儀表板小工具之外,1.x 版本還能用 Block Kit 製作編輯器旁的面板(editorPanels)。這是用來顯示各文章的狀態和操作於編輯畫面上。例如翻譯狀態顯示、SEO 或文案檢查、連結失效檢查、從本文生成摘要等,針對「正在編寫的文章」的功能都可在此整合。
去中心化登錄:沒有中央市集設計
外掛的發布機制也在 1.0 版本改變了。從使用者的角度看只是「在管理介面中搜尋,一鍵安裝」,但底層是建構在 Bluesky 的基礎(AT Protocol)上,具有相當獨特的架構。
- プラグイン的資訊存放在作者的帳戶中。作者使用 Bluesky 相關帳戶登入,對套件和發佈資訊進行簽名,並將其放在自己帳戶的資料區域中。不會存放在註冊表營運者的伺服器上
- Cloudflare 的註冊表只是「目錄之一」。
plugins.emdashcms.com只是彙整各作者公開的資訊以便搜尋,任何人都可以蒐集相同資訊來建立另一個目錄 - 安裝時驗證內容。EmDash 會檢查下載的外掛「是否與作者簽名公開的內容相同」。檢驗檔案雜湊值、套件名稱與版本、要求的 Capability 是否與簽名資訊相符,若是由 GitHub Actions 建立的,也會驗證該建置紀錄。由目錄方替換或遭竄改的內容會在此被過濾掉
- 審核只會「隱藏」內容。Cloudflare 的目錄使用 AI 模型(Clef)審查說明文字、連結和圖片,如有問題就不在目錄中顯示。但無法改變外掛內容或從作者那裡奪取所有權
也就是說「即使註冊表營運者消失,只要作者帳戶中保存了資訊,配布就會繼續」「營運者可以從目錄中隱藏,但無法竄改」的模式。
不同於 npm 和 WordPress.org 的中央市場,這是在沒有中央市集的前提下設計的特色。
目前只提供免費外掛,付費外掛則在 AT Protocol 有進展時,將採用同樣的分散式模型對應
翻譯機制應該可以做成外掛
ゆるろぐ 上我們運行著從日文文章用 LLM 生成英文草稿的機制(用 EmDash 建立多語言部落格的故事)。目前是用 Node 腳本,從 .env 讀取 EmDash 的 API 令牌和翻譯 API 金鑰,取得日文文章進行翻譯,然後建立帶有 translationOf 的英文草稿
將此套用到 1.x 的外掛規格,幾乎就直接變成 Standard 外掛了
- 觸發器:
content:afterPublish鉤子。日文文章發佈時就會啟動 - 文章讀寫:
content:read和content:write。翻譯結果建立為草稿 - 翻譯 API 呼叫:
network:request,以及在allowedHosts中新增一個 API 主機位址 - API 金鑰:在
settingsSchema的secret欄位中。從管理畫面輸入並加密儲存。不再需要.env或發行 API 令牌 - 翻譯狀態:在 storage 中儲存「翻譯了原文的哪個版本」,在
editorPanels的文章編輯畫面中顯示「有英文版本/原文已更新,需要重新翻譯」
原本需要手動執行的指令碼,現在只需要管理者批准 Capability 就能執行。而且執行時會保證翻譯 API 以外的主機無法存取。「處理 API 金鑰的程式碼要放在哪裡」這個煩惱從根本上消失了,這就是這個機制的優點所在。
設計上需要注意一點。沙盒中的外掛程式每次向主機提出請求(讀取設定、取得文章、寫入 storage)時,都會計為一次子請求,預設上限是 10 次。翻譯單篇文章應該還在範圍內,但如果要「批量翻譯未翻譯的文章」這樣的處理,在迴圈中執行ctx.content.get()很快就會達到上限。可能需要透過 cron 鉤子分次處理的設計。
另外,像切換語言這樣的網站端 UI 無法從沙盒產生,那部分需要在主題中實作。
總結
- EmDash 1.0 的外掛程式以沙盒為前提。宣告 Capability、管理者批准、執行時強制執行
- 同一份程式碼可在 Trusted 和 Sandboxed 兩種模式下執行。開發階段用 Trusted、正式環境用沙盒,這是基本做法
- 如果需要編輯器的自訂區塊或 Astro 元件,使用 Native(僅限 Trusted)
- descriptor(建置時)和 definition(執行時)必須分開在不同檔案中
- Capability 的名稱在 1.x 版本中改為
content:read格式。舊名稱仍以別名運作,但已不推薦使用 - 登錄表是建立在 AT Protocol 上的分散型系統。目錄可以隱藏,但無法被竄改
- 在沙箱中「一次主機呼叫 = 一次子請求」,上限為 10 次。需要意識到次數限制進行設計
在 WordPress 中,每安裝一個外掛程式,您就等於接受了「該外掛程式擁有網站的全部權限」。
只要存在一個有漏洞的外掛程式,整個網站都會面臨風險。
EmDash 1.0 針對沙箱型外掛程式進行了改進,在安裝時會顯示外掛程式可用的權限,執行時只允許已批准範圍內的操作。
對於尋求從 WordPress 遷移方案的使用者來說,外掛程式的安全性會成為選擇 EmDash 的重要原因之一。
目前外掛程式的數量仍然不多,還遠未達到 WordPress 那樣「隨處可得」的狀態,但我們期待外掛程式數量能逐漸增加。
既然公司內部網站已累積了實際運營經驗,我們打算將其納入公司提供的服務中。
從 DTP 跨足 Web 世界,轉眼間便掌握了標記語言、frontend 開發、專案指導,以及 accessibility 等各項技能——是名真正的「技術高人」。自 Liberogic 創立初期便展現多才多藝的能力,如今儼然成為公司內的活字典。最近正沉迷於利用 AI 提示詞探索「accessibility 對應能否更多依靠 AI?」的效率化研究。技術與思維都在不斷進化中。
二俣 歩
IAAP 認證網頁無障礙專家(WAS)/ 標記語言工程師 / Frontend 工程師 / 網頁總監