跳转到内容
搜索文档

Pages Plugin

最后更新 查看 MarkdownAgent 设置

Cloudflare 维护多个官方 Pages Plugin,供你在 Pages 项目中使用:


编写 Pages Plugin

Pages Plugin 是一种 Pages Functions 可分发组件,包含内置路由和功能。开发者可以在 Pages 项目的任意位置包含 Plugin,并传入一些配置选项。Functions 的全部功能都可用于 Plugin,包括中间件、参数化路由和静态资源。

例如,Pages Plugin 可以:

  • 拦截 HTML 页面并注入第三方脚本。
  • 代理第三方服务的 API。
  • 验证授权标头。
  • 提供完整的 admin Web 应用体验。
  • 在 KV 或 Durable Objects 中存储数据。
  • 使用 CMS 数据对网页进行服务端渲染 (SSR)。
  • 报告错误并跟踪性能。

Pages Plugin 本质上是一个库,开发者可以用它通过 Functions 深度集成来增强现有 Pages 项目。

使用 Pages Plugin

开发者可以通过在应用的路由上挂载 Pages Plugin 来增强项目。Plugin 会提供通常应挂载的位置说明(例如,admin 界面可能挂载在 functions/admin/[[path]].ts,错误记录器可能挂载在 functions/_middleware.ts)。此外,每个 Plugin 可能需要一些配置(例如 API 令牌)。


静态表单示例

在本示例中,你将构建 Pages Plugin 并将其包含在项目中。

第一个 Plugin 应:

  • 拦截 HTML 表单。
  • 将表单提交存储到 KV
  • 使用开发者自定义响应回复提交。

1. 创建新的 Pages Plugin

创建包含以下内容的 package.json

{
	"name": "@cloudflare/static-form-interceptor",
	"main": "dist/index.js",
	"types": "index.d.ts",
	"files": ["dist", "index.d.ts", "tsconfig.json"],
	"scripts": {
		"build": "npx wrangler pages functions build --plugin --outdir=dist",
		"prepare": "npm run build"
	}
}

在我们的示例中,dist/index.js 将是 Plugin 的入口点。这是通过 npm run build 命令由 Wrangler 生成的文件。将 dist/ 目录添加到 .gitignore

接下来,创建 functions 目录并开始编写 Plugin 代码。functions 文件夹将挂载在开发者选择的某个路由上,因此请考虑如何组织文件。通常:

  • 若希望 Plugin 在开发者选择的单个路由上运行(例如 /foo),创建 functions/index.ts 文件。
  • 若希望 Plugin 挂载并处理某路径之后的所有请求(例如 /admin/login/admin/dashboard),创建 functions/[[path]].ts 文件。
  • 若希望 Plugin 拦截请求但回退到其他 Functions 或项目的静态资源,创建 functions/_middleware.ts 文件。

你可以根据需要自由使用任意数量的文件。Plugin 的结构与 Pages 项目中 Functions 的结构完全相同,只是处理程序在其参数对象中接收新属性 pluginArgs。此属性是挂载 Plugin 时开发者传递的初始化参数。你可以使用它接收 API 令牌、KV/Durable Object namespace 或 Plugin 工作所需的任何内容。

回到静态表单示例,若要拦截请求并覆盖 HTML 表单的行为,需要创建 functions/_middleware.ts。开发者可以将 Plugin 挂载在单个路由上,或挂载在整个项目上。

class FormHandler {
	element(element) {
		const name = element.getAttribute("data-static-form-name");
		element.setAttribute("method", "POST");
		element.removeAttribute("action");
		element.append(
			`<input type="hidden" name="static-form-name" value="${name}" />`,
			{ html: true },
		);
	}
}

export const onRequestGet = async (context) => {
	// We first get the original response from the project
	const response = await context.next();

	// Then, using HTMLRewriter, we transform `form` elements with a `data-static-form-name` attribute, to tell them to POST to the current page
	return new HTMLRewriter()
		.on("form[data-static-form-name]", new FormHandler())
		.transform(response);
};

export const onRequestPost = async (context) => {
	// Parse the form
	const formData = await context.request.formData();
	const name = formData.get("static-form-name");
	const entries = Object.fromEntries(
		[...formData.entries()].filter(([name]) => name !== "static-form-name"),
	);

	// Get the arguments given to the Plugin by the developer
	const { kv, respondWith } = context.pluginArgs;

	// Store form data in KV under key `form-name:YYYY-MM-DDTHH:MM:SSZ`
	const key = `${name}:${new Date().toISOString()}`;
	context.waitUntil(kv.put(name, JSON.stringify(entries)));

	// Respond with whatever the developer wants
	const response = await respondWith({ formData });
	return response;
};

2. 为 Pages Plugin 添加类型

为提供良好的开发者体验,应为 Plugin 添加 TypeScript 类型。这使开发者能够使用 IDE 的自动补全功能,并确保他们包含你期望的所有参数。

index.d.ts 中,导出一个接受 pluginArgs 并返回 PagesFunction 的函数。对于静态表单示例,接受两个属性:kv(KV namespace)和 respondWith(接受带有 formData 属性(FormData)的对象并返回 Promise<Response> 的函数):

export type PluginArgs = {
	kv: KVNamespace;
	respondWith: (args: { formData: FormData }) => Promise<Response>;
};

export default function (args: PluginArgs): PagesFunction;

3. 测试 Pages Plugin

我们仍在为 Pages Plugin 作者打造出色的测试体验。在这些部分整合完成之前,请耐心等待。同时,你可以创建示例项目并手动包含 Plugin 进行测试。

4. 发布 Pages Plugin

你可以按任意方式分发 Plugin。流行的方式包括在 npm 上发布、在 Developer Discord 的 #what-i-built 或 #pages-discussions 频道展示,以及在 GitHub 上开源。

确保包含生成的 dist/ 目录、类型文件 index.d.ts 以及说明开发者如何使用 Plugin 的 README.md


5. 安装 Pages Plugin

若要在应用中包含 Pages Plugin,首先需要将 Plugin 安装到项目中。

若项目尚未使用 npm,运行 npm init 创建 package.json 文件。Plugin 的 README.md 通常包含安装命令(例如 npm install --save @cloudflare/static-form-interceptor)。

6. 挂载 Pages Plugin

Plugin 的 README.md 可能包含如何在应用中挂载 Plugin 的说明。你需要:

  1. 创建 functions 目录(若尚未创建)。
  2. 决定 Plugin 运行的位置,并在 functions 目录中创建相应的文件。
  3. 在此文件中导入 Plugin 并导出 onRequest 方法,使用 Plugin 所需的任何参数进行初始化。

在静态表单示例中,你创建的 Plugin 已作为中间件创建。这意味着它可以在单个路由或整个项目上运行。若网站在 /contact 有一个联系表单,可以创建 functions/contact.ts 文件来拦截该路由。也可以创建 functions/_middleware.ts 文件来拦截所有其他路由以及可能创建的任何其他表单。作为开发者,你可以选择 Plugin 运行的位置。

Plugin 的默认导出是一个函数,接受与普通 Pages Functions 处理程序相同的 context 参数。

import staticFormInterceptorPlugin from "@cloudflare/static-form-interceptor";

export const onRequest = (context) => {
	return staticFormInterceptorPlugin({
		kv: context.env.FORM_KV,
		respondWith: async ({ formData }) => {
			// Could call email/notification service here
			const name = formData.get("name");
			return new Response(`Thank you for your submission, ${name}!`);
		},
	})(context);
};

7. 测试 Pages Plugin

你可以使用 wrangler pages dev 测试 Pages 项目,包括已安装的任何 Plugin。记得包含 Plugin 期望的任何 KV 绑定和环境变量。

Plugin 挂载在 /contact 路由上时,相应的 HTML 文件可能如下所示:

<!DOCTYPE html>
<html>
	<body>
		<h1>Contact us</h1>
		<!-- Include the `data-static-form-name` attribute to name the submission -->
		<form data-static-form-name="contact">
			<label>
				<span>Name</span>
				<input type="text" autocomplete="name" name="name" />
			</label>
			<label>
				<span>Message</span>
				<textarea name="message"></textarea>
			</label>
		</form>
	</body>
</html>

Plugin 应识别 data-static-form-name="contact" 属性,设置 method="POST",注入 <input type="hidden" name="static-form-name" value="contact" /> 元素,并捕获 POST 提交。

8. 部署 Pages 项目

确保新 Plugin 已添加到 package.json,且本地一切按预期工作。然后可以 git commitgit push 以触发 Cloudflare Pages 部署。

若某个 Plugin 出现问题,请在该 Plugin 的错误跟踪器上提交 issue。

若对 Plugin 一般有问题,我们欢迎在 Discord 的 #pages-discussions 频道提供反馈!我们很高兴看到你用 Plugin 构建的内容,并欢迎有关编写或开发者体验的任何反馈。若需要让 Plugin 更强大,请在 Discord 频道告知我们。


链式组合 Plugin

最后,与 Pages Functions 一般情况一样,可以链式组合 Plugin 以合并不同功能。文件系统中较高位置的中间件将在其他处理程序之前运行,单个文件可以像这样将 Functions 链接成数组:

import sentryPlugin from "@cloudflare/pages-plugin-sentry";
import cloudflareAccessPlugin from "@cloudflare/pages-plugin-cloudflare-access";
import adminDashboardPlugin from "@cloudflare/a-fictional-admin-plugin";

export const onRequest = [
	// Initialize a Sentry Plugin to capture any errors
	sentryPlugin({ dsn: "https://sentry.io/welcome/xyz" }),

	// Initialize a Cloudflare Access Plugin to ensure only administrators can access this protected route
	cloudflareAccessPlugin({
		domain: "https://test.cloudflareaccess.com",
		aud: "4714c1358e65fe4b408ad6d432a5f878f08194bdb4752441fd56faefa9b2b6f2",
	}),

	// Populate the Sentry plugin with additional information about the current user
	(context) => {
		const email =
			context.data.cloudflareAccessJWT.payload?.email || "service user";

		context.data.sentry.setUser({ email });

		return next();
	},

	// Finally, serve the admin dashboard plugin, knowing that errors will be captured and that every incoming request has been authenticated
	adminDashboardPlugin(),
];

这篇文档对您有帮助吗?