分发原生插件

本页内容

原生插件是安装在宿主项目中并在 astro.config.mjs 中注册的 npm 包。包需要为其描述符与 createPlugin() 构建的服务器入口。若还附带 React 或 Astro 组件,请将它们导出为单独的源入口,以便宿主能为正确环境编译它们。

包布局

以下布局将服务器运行时与浏览器和 Astro 源码分开:

plugin-activity/
├── src/
│   ├── index.ts
│   ├── admin/
│   │   ├── index.tsx
│   │   └── ActivityPage.tsx
│   └── astro/
│       ├── index.ts
│       └── ActivityBlock.astro
├── dist/
│   ├── index.mjs
│   └── index.d.mts
├── package.json
├── tsconfig.json
└── README.md

dist/ 是生成的。请在已发布的 tarball 中保留 src/admin/ 与 src/astro/,因为宿主的 Vite 与 Astro 构建必须处理这些入口。

包导出

以下 package.json 构建服务器入口并发布全部三个入口:

{
	"name": "@example/plugin-activity",
	"version": "0.1.0",
	"type": "module",
	"main": "./dist/index.mjs",
	"exports": {
		".": {
			"types": "./dist/index.d.mts",
			"import": "./dist/index.mjs"
		},
		"./admin": "./src/admin/index.tsx",
		"./astro": "./src/astro/index.ts"
	},
	"files": ["dist", "src/admin", "src/astro"],
	"scripts": {
		"build": "tsdown src/index.ts --format esm --dts --clean",
		"dev": "tsdown src/index.ts --format esm --dts --watch",
		"typecheck": "tsc --noEmit",
		"prepublishOnly": "pnpm typecheck && pnpm build"
	},
	"peerDependencies": {
		"@cloudflare/kumo": "*",
		"@emdash-cms/admin": "*",
		"@lingui/core": "*",
		"@lingui/react": "*",
		"@tanstack/react-query": "*",
		"astro": ">=6.0.0-beta.0",
		"emdash": "*",
		"react": "^18.0.0 || ^19.0.0"
	},
	"devDependencies": {
		"@types/react": "^19.0.0",
		"tsdown": "^0.20.0",
		"typescript": "^5.9.0"
	},
	"keywords": ["emdash", "emdash-plugin"],
	"license": "MIT"
}

当插件没有受信任的 React UI 时,移除 ./admin、src/admin 以及仅管理端的 peerDependencies。当没有 Portable Text 渲染器时,移除 ./astro、src/astro 与 astro peer。为每个导出的源入口导入的宿主拥有库添加 peerDependency;这可防止第二个 React、Kumo、Lingui 或 React Query 实例进入管理包。

入口有不同的消费者:

导出何时需要消费者
.始终Astro 配置导入描述符工厂;EmDash 在运行时导入具名 createPlugin()。
./admin设置了 adminEntry宿主的浏览器构建导入 React 组件映射。
./astro设置了 componentsEntry宿主的 Astro 构建导入 blockComponents。

描述符与运行时中的模块说明符必须与这些导出匹配:

export function activityPlugin(): PluginDescriptor {
	return {
		id: "plugin-activity",
		version: "0.1.0",
		format: "native",
		entrypoint: "@example/plugin-activity",
		adminEntry: "@example/plugin-activity/admin",
		componentsEntry: "@example/plugin-activity/astro",
	};
}

export function createPlugin() {
	return definePlugin({
		id: "plugin-activity",
		version: "0.1.0",
		admin: {
			entry: "@example/plugin-activity/admin",
		},
	});
}

保持 npm 包版本、描述符版本与 definePlugin() 版本同步。向站点管理员显示的版本来自插件定义,而不是自动来自 package.json。

插件标识与版本

definePlugin() 接受包含小写字母、数字与连字符的无作用域 ID,或形如 @scope/name 的作用域 ID。对站点插件使用无作用域、kebab-case 的 ID,因为 ID 也占用 /_emdash/api/plugins/<plugin-id>/<route> 中的一个路径段。

以下值显示可接受形式以及插件 ID 与 npm 包名之间的推荐分离:

id: "plugin-activity"; // 推荐:在插件路由 URL 中有效
id: "@example/plugin-activity"; // definePlugin() 接受,但不是一个 URL 段

entrypoint: "@example/plugin-activity"; // npm 包可以保持作用域

版本必须以语义化 major.minor.patch 序列开头。对描述符与运行时都使用完整语义版本:

version: "1.0.0"; // 有效
version: "1.2.3-beta.1"; // 有效的预发布
version: "1.0"; // 无效:缺少补丁版本

TypeScript 配置

原生脚手架会创建合适的 tsconfig.json。若插件在脚手架之后添加 React 与 Astro 源码,请包含两种 JSX 环境:

{
	"compilerOptions": {
		"target": "ES2022",
		"module": "preserve",
		"moduleResolution": "bundler",
		"strict": true,
		"declaration": true,
		"outDir": "./dist",
		"rootDir": "./src",
		"jsx": "react-jsx",
		"types": ["astro/client"]
	},
	"include": ["src/**/*"],
	"exclude": ["node_modules", "dist"]
}

打包前对源入口运行 pnpm typecheck。build 脚本仅编译 src/index.ts;宿主在消费包时编译导出的管理端与 Astro 源码。

检查包

发布前测试确切的 tarball 内容。以下命令假定名为 my-emdash-site 的临时站点位于插件目录旁。

  1. 构建并类型检查包。

    pnpm typecheck
    pnpm build
  2. 创建 npm tarball 并查看 npm 打印的文件列表。

    npm pack

    对于示例包,npm 会创建 example-plugin-activity-0.1.0.tgz。

  3. 确认输出包含 dist/index.mjs、dist/index.d.mts,以及从导出的 ./admin 与 ./astro 模块可达的每个源文件。

  4. 将生成的 tarball 安装到临时 EmDash 站点。

    cd ../my-emdash-site
    pnpm add ../plugin-activity/example-plugin-activity-0.1.0.tgz
  5. 按照 创建并注册包,在临时站点的 astro.config.mjs 中导入并注册描述符工厂。然后构建宿主站点。

    pnpm build

    打开每个插件管理表面,并渲染每个贡献的 Portable Text 块。在构建前注册可使 Astro 解析 tarball 的 ./admin 与 ./astro 导出;仅服务器的包测试无法发现缺失的浏览器或 .astro 源文件。

README 内容

为运营者提供足够信息,使其无需阅读源码即可安装并评估包。包括:

  • 一句话描述与支持的 EmDash 版本
  • 安装命令与完整的 astro.config.mjs 注册
  • 原生信任边界以及插件为何需要原生执行
  • 每个已声明能力与允许的主机,以及使用它的功能
  • 设置及其默认值
  • 必需的布局组件,例如用于 body-end 片段的 EmDashBodyEnd
  • 需要运营者操作的更改的升级步骤

不要将能力声明描述为隔离边界。它们对 ctx API 设门,但原生代码仍可使用宿主进程可用的导入、环境变量与直接网络调用。

发布到 npm

tarball 测试通过后发布:

npm publish --access public

作用域包的首次公开发布需要 --access public。后续发布使用语义化版本。将构造函数选项、已存数据、必需的宿主更改、包导出或插件信任要求的更改视为兼容性决策。若升级需要新能力或允许的主机,即使原生安装没有能力同意提示,也请在发布说明中指出。

从 npm 安装

运营者将已发布的包安装到 EmDash 站点:

pnpm add @example/plugin-activity

然后按 创建并注册包 所示,在 astro.config.mjs 中导入并注册其描述符工厂。仅安装依赖不会激活插件;更改 Astro 配置并部署站点才完成安装。

针对宿主站点开发

以监视模式构建插件:

pnpm dev

从宿主站点安装本地目录:

pnpm add ../plugin-activity

在 astro.config.mjs 中注册插件的描述符工厂,然后启动宿主开发服务器。更改描述符元数据或包导出后重启服务器。若包管理器文件依赖在你的设置中复制文件而不是链接,请在重建后重新安装;工作区依赖或 pnpm link 可在开发期间保持本地包连接。

注册表边界

原生包不能发布到 EmDash 注册表。注册表插件使用沙箱包格式、签名发布工作流与安装同意流程。若插件不再需要 React 管理代码、Astro 渲染器、受信任片段或其他进程内依赖,请在通过注册表发布前 转换为沙箱格式。