今天在 GitHub Trending 上看到一个有意思的项目:EmDash(emdash-cms/emdash),一个用 Astro 和 Cloudflare 从零重建、专为解决 WordPress 时代痛点而生的全栈 TypeScript CMS。

一、项目概述

EmDash 将自己定位为「WordPress 的现代重写版本」:它保留了让 WordPress 占据统治地位的核心优势——可扩展性、顺手的后台体验、繁荣的插件生态——但把这些能力建立在无服务器(serverless)、类型安全(type-safe)的现代基础之上。

项目的核心主张很清晰:

  • 沙箱化插件:插件运行在隔离的 Worker 沙箱中,每个插件需要声明能力清单(capability manifest),从根本上解决了 WordPress 插件「一损俱损」的安全问题。
  • 结构化内容:不把内容存成序列化 HTML,而是使用 Portable Text 这种结构化 JSON 格式,让内容天然解耦于展示层。
  • 为 Agent 而生:内置 MCP Server,让 Claude、ChatGPT 等 AI 工具可以直接操作站点;同时提供 agent skills 与 CLI,支持 AI 辅助开发插件与主题。
  • 随处可运行:通过 Kysely(SQL)和 S3 API(存储)等可移植抽象,既能跑在 Cloudflare(D1 + R2 + Workers)上,也能跑在任意带 SQLite 的 Node.js 服务器上。

仓库是一个 pnpm monorepo,作者为 Matt Kane,采用 MIT 许可证,目前处于 beta preview 阶段。

二、技术原理

架构设计

EmDash 本质上是一个 Astro 集成(integration)。把它加进 astro.config.mjs,你就得到了一整套 CMS 能力:后台面板、REST API、鉴权、媒体库与插件系统。

// astro.config.mjs
import emdash from "emdash/astro";
import { d1 } from "emdash/db";

export default defineConfig({
	integrations: [emdash({ database: d1() })],
});

内容类型(content type)定义在数据库里,而非写死在代码中。非开发人员可以通过后台 UI 创建和修改集合(collection),每个集合会对应一张真实的 SQL 表,并带有类型化的列。开发者则可以从实时 schema 生成 TypeScript 类型:

npx emdash types

查询内容时直接使用 Astro 的 Live Collections,无需额外构建步骤,也无需单独的 API 层:

---
import { getEmDashCollection } from "emdash";
const { entries: posts } = await getEmDashCollection("posts");
---

{posts.map((post) => <article>{post.data.title}</article>)}

沙箱化插件:用最小权限取代「全量信任」

WordPress 插件默认拥有数据库、文件系统和用户数据的完整访问权,一个漏洞插件就能拖垮整站。EmDash 反其道而行:插件通过 Cloudflare 的 Dynamic Worker Loaders 运行在隔离的 Worker 沙箱中,每个插件必须声明自己需要的能力。

export default () =>
	definePlugin({
		id: "notify-on-publish",
		capabilities: ["read:content", "email:send"],
		hooks: {
			"content:afterSave": async (event, ctx) => {
				if (event.content.status !== "published") return;
				await ctx.email.send({
					to: "[email protected]",
					subject: `New post: ${event.content.title}`,
				});
			},
		},
	});

上面这个插件声明了 read:contentemail:send 两项能力,那么它只能做这两件事,多一分都不行。这种能力清单(capability manifest)模型,正是现代最小权限原则(least privilege)在 CMS 插件体系中的落地。

结构化内容:Portable Text 取代 DOM 耦合

WordPress 把富文本存成嵌在注释里的 HTML,导致内容与 DOM 表示强绑定。EmDash 改用 Portable Text——一种结构化的 JSON 格式。内容可以原样渲染成网页、移动 App、邮件或 API 响应,而不必先解析 HTML。富文本编辑基于 TipTap,存储层则是 Portable Text。

可移植平台抽象

Cloudflare同样支持
数据库D1SQLite、Turso/libSQL、PostgreSQL
存储R2AWS S3、任意 S3 兼容服务、本地文件系统
会话KVRedis、基于文件
插件Worker isolates(沙箱)进程内(安全模式)

这种分层抽象让 EmDash「在 Cloudflare 上跑得最好,但并不被它锁死」。

三、安装与快速开始

环境要求:Node.js ≥ 22,以及 pnpm 11+(仓库通过 packageManager 字段锁定)。

最快捷的开始方式是用官方脚手架:

npm create emdash@latest

或者一键部署到自己的 Cloudflare 账号(需 Dynamic Workers,目前仅付费账号可用,起步 $5/月;也可注释掉 wrangler.jsonc 中的 worker_loaders 块来关闭插件)。

如果想本地体验 Demo(Node.js + SQLite,无需 Cloudflare 账号):

git clone https://github.com/emdash-cms/emdash.git && cd emdash
pnpm install
pnpm build

# 运行 demo(Node.js + SQLite)
pnpm --filter emdash-demo seed
pnpm --filter emdash-demo dev

随后打开后台:http://localhost:4321/_emdash/admin

官方还提供了三套起步模板:Blog(经典博客,含侧边栏、搜索、RSS)、Marketing(转化导向落地页)、Portfolio(作品集),均支持深色/浅色模式。

四、使用方法与实战

定义内容集合与类型

内容模型由数据库驱动。在后台用可视化 schema builder 创建集合后,通过一条命令即可生成对应的 TypeScript 类型,获得端到端的类型安全:

npx emdash types

Agent 工作流:让 AI 直接管站点

EmDash 面向 AI 工具做了原生支持:

  • 内置 MCP Server:Claude、ChatGPT 等可直接与站点交互(读取/发布内容、管理 schema)。
  • Agent Skills:提供用于 AI 辅助开发插件与主题的能力文件。
  • CLI 程序化管理:让 agent 以编程方式管理内容与 schema。

例如,用前文示例的 notify-on-publish 插件,当文章状态变为 published 时自动给编辑发邮件——整个过程由能力清单约束,不会越权访问数据库或文件系统。

从 WordPress 迁移

EmDash 提供 WordPress 导入向导,支持从 WXR 导出、WordPress REST API 或 WordPress.com 导入文章、页面、媒体与分类法;还附带 gutenberg-to-portable-text 工具把古腾堡块转换为 Portable Text,并提供 agent skills 帮助移植插件和主题。

五、常见问题与解决方案

Q1:插件无法运行 / 沙箱报错? EmDash 的安全沙箱依赖 Cloudflare 的 Dynamic Workers,目前仅付费账号可用(起步 $5/月)。若暂时不想付费,可注释掉 wrangler.jsonc 中的 worker_loaders 块,插件会退回到进程内的安全模式(in-process safe mode)。

Q2:npm 安装/构建报 native 依赖错误? 仓库的 Dockerfile 注释指出,better-sqlite3 在无法命中 GitHub Releases 预编译二进制时,会回退到从源码编译,这需要 python3makeg++。在精简镜像或离线环境中需提前安装该工具链。

Q3:内容类型改了,前端类型不同步? 记得在后台修改 schema 后重新运行 npx emdash types 生成 TypeScript 类型;前端通过 getEmDashCollection 读取的集合字段以生成的类型为准。

Q4:想跑本地 Demo 但不想配 Cloudflare? 直接用 pnpm --filter emdash-demo seed && pnpm --filter emdash-demo dev,Demo 使用 Node.js + SQLite,无需任何 Cloudflare 账号即可体验完整后台。

Q5:需要多语言/国际化? 仓库内置 Lingui(含 pseudo locale 用于暴露未包裹文案)与 Lunaria(文档翻译状态追踪),后台支持多语言消息目录。

六、总结

EmDash 是一次有意思的「考古翻新」:它承认 WordPress 在产品设计上的成功,却拒绝继承它时代的技术包袱——PHP 与 JS 双栈、插件全权限、HTML 耦合内容。通过 Astro 集成、Cloudflare 无服务器底座、Worker 沙箱插件与 Portable Text 结构化内容,它把安全性、类型安全和可移植性做成了一等公民。

对于一个仍在 beta preview 阶段的项目,它已经给出了相当完整的蓝图:可视化 schema、MCP Server、WordPress 迁移路径、三套模板。如果你既想要 WordPress 那样顺手的内容管理能力,又不想被它的安全与运维噩梦捆绑,EmDash 值得加入观察列表。

仓库地址:https://github.com/emdash-cms/emdash 文档:https://docs.emdashcms.com/