页面片段

本页内容

page:fragments 钩子允许插件向公开页面贡献原始 HTML、脚本或样式表。这是添加分析标签、第三方小部件、自定义 CSS 以及任何需要直接向访问者浏览器提供 JavaScript 或标记的内容的正确工具。

它仅限于原生插件,因为其输出作为第一方代码在浏览器中运行,不受任何沙箱边界限制。如果你只需要贡献结构化元数据 — meta 标签、OpenGraph、JSON-LD、白名单中的 <link> rels — 请改用 page:metadata,它对沙箱和原生插件都可用。参见 Hooks: page:metadata

能力

page:fragments 需要 hooks.page-fragments:register 能力:

return definePlugin({
	id: "analytics-gtm",
	version: "1.0.0",
	capabilities: ["hooks.page-fragments:register"],
	// ...
});

该能力还必须出现在描述符中。

片段渲染位置

模板通过包含来自 emdash/ui 的相关组件来选择接收片段:

  • <EmDashHead /> — 渲染 placement: "head" 的片段以及所有 page:metadata 贡献。
  • <EmDashBodyStart /> — 渲染 placement: "body:start" 的片段。
  • <EmDashBodyEnd /> — 渲染 placement: "body:end" 的片段。

省略其中一个组件的模板会静默忽略针对该位置的片段 — 你的插件不会中断,片段只是不会出现。请在插件的 README 中记录你的位置要求。

贡献类型

三种贡献类型:

type PageFragmentContribution =
	| {
			kind: "external-script";
			placement: PagePlacement;
			src: string;
			async?: boolean;
			defer?: boolean;
			attributes?: Record<string, string>;
			key?: string;
	  }
	| {
			kind: "inline-script";
			placement: PagePlacement;
			code: string;
			attributes?: Record<string, string>;
			key?: string;
	  }
	| {
			kind: "html";
			placement: PagePlacement;
			html: string;
			key?: string;
	  };

PagePlacement"head" | "body:start" | "body:end"

示例

外部脚本

注入第三方标签管理器:

"page:fragments": async (event, ctx) => {
	const containerId = await ctx.kv.get<string>("settings:gtmContainerId");
	if (!containerId) return null;

	return {
		kind: "external-script",
		placement: "head",
		src: `https://www.googletagmanager.com/gtm.js?id=${containerId}`,
		async: true,
	};
},

内联脚本

<body> 顶部运行一小段 JavaScript:

"page:fragments": async (event, ctx) => {
	if (event.page.kind !== "content") return null;
	return {
		kind: "inline-script",
		placement: "body:start",
		code: `window.contentId = ${JSON.stringify(event.page.content?.id)};`,
	};
},

HTML 片段

<body> 末尾附加一个 noscript 回退:

"page:fragments": async (event, ctx) => {
	const containerId = await ctx.kv.get<string>("settings:gtmContainerId");
	if (!containerId) return null;

	return {
		kind: "html",
		placement: "body:end",
		html: `<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=${containerId}" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>`,
	};
},

多个片段

从单个钩子返回数组以贡献多个片段:

"page:fragments": async (event, ctx) => {
	const id = await ctx.kv.get<string>("settings:gtmContainerId");
	if (!id) return null;

	return [
		{
			kind: "external-script",
			placement: "head",
			src: `https://www.googletagmanager.com/gtm.js?id=${id}`,
			async: true,
		},
		{
			kind: "html",
			placement: "body:end",
			html: `<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=${id}" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>`,
		},
	];
},

页面事件

page:fragments 钩子接收与 page:metadata 相同的事件形状:

{
	page: {
		url: string;
		path: string;
		locale: string | null;
		kind: "content" | "custom";
		pageType: string;
		title: string | null;
		pageTitle?: string | null;
		description: string | null;
		canonical: string | null;
		image: string | null;
		content?: { collection: string; id: string; slug: string | null };
	}
}

使用 event.page.kindevent.page.pageType 来决定是否在给定页面上贡献 — 例如,在管理预览上跳过分析,或仅在博客文章上注入 JSON-LD。

何时改用 page:metadata

如果你实际需要的是:

  • meta 描述、robots 指令或 Twitter 卡片 → 使用 page:metadata 并设置 kind: "meta"
  • OpenGraph 属性 → 使用 page:metadata 并设置 kind: "property"
  • canonical 或 alternate <link> → 使用 page:metadata 并设置 kind: "link"
  • JSON-LD 图 → 使用 page:metadata 并设置 kind: "jsonld"

page:metadata 在沙箱插件中有效,免费获得验证和去重,并避免向访问者提供原始 HTML 的信任负担。仅在真正需要提供 JavaScript 或 HTML 时才使用 page:fragments