INTEGRITY Документация

Pages Plugins

Cloudflare поддерживает набор официальных Pages Plugins для использования в ваших проектах Pages:


Разработка Pages Plugin

Pages Plugin представляет собой распространяемый пакет Pages Functions со встроенной маршрутизацией и функциональностью. Разработчики могут подключить Plugin в любом месте своего проекта Pages и передать ему параметры конфигурации. Plugins получают доступ ко всем возможностям Functions, включая middleware, параметризованные маршруты и статические ресурсы.

Например, Pages Plugin может:

По сути, Pages Plugin представляет собой библиотеку, с помощью которой разработчики могут глубоко интегрировать Functions в существующий проект Pages.

Использование плагина Pages

Разработчики могут расширять свои проекты, подключая Pages Plugin к маршруту своего приложения. Плагины Pages содержат инструкции о том, куда их обычно следует подключать (например, интерфейс администратора может быть подключен по адресу functions/admin/[[path]].ts, а логгер ошибок может быть подключён по адресу functions/_middleware.ts). Кроме того, каждый плагин может принимать определённую конфигурацию (например, с помощью API токена).


Пример статической формы

В этом примере вы создадите Pages Plugin, а затем подключите его к проекту.

Первый плагин должен:

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. Это сгенерированный файл, собранный Wrangler с npm run build команду. Добавьте dist/ директорию в .gitignore.

Далее создайте functions директорию и начните писать код вашего Plugin. functions папка будет подключена разработчиком к определённому маршруту, поэтому продумайте структуру файлов заранее. Как правило:

Вы можете использовать сколько угодно файлов. Структура Plugin точно такая же, как у Functions в проекте Pages, за исключением того, что обработчики получают новое свойство объекта параметров, pluginArgs. Это свойство представляет собой параметр инициализации, который разработчик передает при подключении Plugin. С его помощью можно получать API-токены, пространства имен KV/Durable Object или любые другие данные, необходимые вашему 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, и 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 Plugins. Пожалуйста, потерпите, пока все эти элементы не будут готовы. А пока вы можете создать тестовый проект и вручную подключить к нему свой плагин для тестирования.

4. Опубликуйте Pages Plugin

Plugin можно распространять любым удобным способом. Среди популярных вариантов: публикация на npm, показав его в каналах #what-i-built или #pages-discussions в нашем Discord для разработчиков, и опубликовали исходный код на GitHub.

Убедитесь, что вы подключили сгенерированный dist/ директории ваши типы (typings) index.d.ts, а также README.md с инструкциями о том, как разработчики могут использовать ваш Plugin.


5. Установите Pages Plugin

Если вы хотите включить Pages Plugin в приложение, сначала установите этот Plugin в проект.

Если вы еще не используете npm в вашем проекте выполните npm init чтобы создать package.json файл. У Plugin README.md обычно включает команду установки (например, npm install --save @cloudflare/static-form-interceptor).

6. Подключите свой Pages Plugin

README.md плагина обычно содержит инструкции по его подключению к вашему приложению. Вам нужно будет:

  1. Создайте functions директорию, если у вас ее еще нет.
  2. Решите, где должен запускаться этот Plugin, и создайте соответствующий файл в functions каталог.
  3. Импортируйте Plugin и экспортируйте onRequest метод в этом файле, инициализировав плагин с нужными ему аргументами.

В примере со статической формой созданный вами Plugin уже был реализован как middleware. Это значит, что он может работать как на одном маршруте, так и во всем проекте. Если бы на вашем сайте была одна контактная форма по адресу /contact, вы могли бы создать functions/contact.ts файл, чтобы перехватывать только этот маршрут. Также можно создать functions/_middleware.ts файл, чтобы перехватывать все остальные маршруты и любые формы, которые вы создадите в будущем. Вы как разработчик сами решаете, где может выполняться этот Plugin.

Plugin экспортирует по умолчанию функцию, которая принимает тот же параметр контекста, что и обычный обработчик Pages Functions.

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, включая все установленные Plugins. Не забудьте указать привязки KV и переменные окружения, которые ожидает Plugin.

После подключения 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>

Плагин должен подхватить data-static-form-name="contact" атрибут, задайте method="POST", вставьте в <input type="hidden" name="static-form-name" value="contact" /> элемент и перехватить POST отправки.

8. Разверните свой проект Pages

Убедитесь, что новый плагин добавлен в ваш package.json и что всё работает локально так, как вы и ожидаете. После этого можно git commit и git push чтобы запустить развертывание Cloudflare Pages.

Если у вас возникла проблема с каким-либо отдельным Plugin, создайте issue в баг-трекере этого Plugin.

Если у вас возникают какие-либо проблемы с Plugins в целом, поделитесь, пожалуйста, отзывом в канале #pages-discussions в Discord! Нам не терпится увидеть, что вы создадите с помощью Plugins, и мы будем рады любым отзывам об опыте разработки. Напишите нам в канале Discord, если вам нужно что-то, что сделает Plugins ещё мощнее.


Свяжите свой Plugin в цепочку

Наконец, как и в случае с Pages Functions в целом, Plugins можно объединять в цепочку, чтобы совместить разные возможности. Middleware, определённый выше по структуре файловой системы, выполняется раньше остальных обработчиков, а отдельные файлы могут объединять 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(),
];