page:fragments 钩子会向已渲染的公开页面的 head 或 body 提供脚本或原始 HTML。用它处理结构化元数据无法表达的浏览器代码,例如带 <noscript> 回退的分析加载器。
片段输出会作为页面的第一方代码执行。EmDash 仅对受信任的进程内插件调用此钩子;沙箱插件从不贡献片段。若页面需要 meta 标签、canonical 或 alternate 链接、或 JSON-LD,请改用兼容沙箱的 page:metadata 钩子。
声明能力与钩子
page:fragments 需要原生运行时定义中的 hooks.page-fragments:register。下面的钩子仅在存在已配置的分析 ID 时添加外部脚本和 HTML 回退:
return definePlugin({
id: "plugin-analytics",
version: "0.1.0",
capabilities: ["hooks.page-fragments:register"],
hooks: {
"page:fragments": async (event, ctx) => {
const analyticsId = await ctx.settings.get<string>("analyticsId");
if (!analyticsId || event.page.path.startsWith("/_emdash/")) return null;
const encodedId = encodeURIComponent(analyticsId);
return [
{
kind: "external-script",
placement: "head",
src: `https://analytics.example.com/client.js?id=${encodedId}`,
async: true,
key: "analytics-client",
},
{
kind: "html",
placement: "body:end",
html: "<noscript>Analytics requires JavaScript.</noscript>",
key: "analytics-fallback",
},
];
},
},
});
原始 HTML 贡献会按原文插入。尽可能保持片段为静态;否则请转义任何可能来自设置、内容、请求数据或外部服务的值。
对于原生插件,在 definePlugin() 中声明能力。描述符不需要第二份副本,因为 createPlugin() 会提供运行时能力列表。
若缺少能力,EmDash 会记录警告且不注册钩子。钩子错误会被记录,不会阻止页面渲染。
添加布局插入点
宿主主题选择支持哪些 placement。它必须将同一 PublicPageContext 传给对应的 EmDash 组件:
-
在布局中构建一次页面上下文。
--- import { createPublicPageContext } from "emdash/page"; import { EmDashBodyEnd, EmDashBodyStart, EmDashHead, } from "emdash/ui"; interface Props { title: string; description?: string; content?: { collection: string; id: string; slug?: string | null }; } const { title, description, content } = Astro.props; const page = createPublicPageContext({ Astro, kind: content ? "content" : "custom", pageType: content ? "article" : "website", title, description, content, }); --- -
在文档的相应位置渲染每个受支持的 placement。
<html lang="en"> <head> <title>{title}</title> <EmDashHead page={page} /> </head> <body> <EmDashBodyStart page={page} /> <slot /> <EmDashBodyEnd page={page} /> </body> </html>
EmDashHead 渲染 head 片段以及 EmDash 与插件元数据。EmDashBodyStart 在开始的 <body> 之后立即渲染 body:start 片段,EmDashBodyEnd 在页面内容之后渲染 body:end 片段。省略某个组件的主题不会渲染该 placement 的片段。请在插件 README 中记录任何必需的插入点。
贡献参考
钩子可以返回一个贡献、一个数组或 null。支持的贡献形式如下:
kind | 必填字段 | 可选字段 | 输出行为 |
|---|---|---|---|
external-script | placement, src | async, defer, attributes, key | 渲染 <script src="…"> 元素。 |
inline-script | placement, code | attributes, key | 在 <script> 元素内渲染 code。 |
html | placement, html | key | 插入 html 且不进行消毒。 |
placement 接受 head、body:start 或 body:end。
EmDash 会对属性名和值做 HTML 转义,并移除名称以 on 开头的属性。对于内联脚本,会转义 </,使值无法关闭 script 元素。这些渲染器检查不会让不受信任的 HTML 或 JavaScript 变得安全;插件仍须按插入的语言与上下文对插值数据进行编码。
下面的钩子将 JSON 值安全地放入内联脚本:
"page:fragments": async (event) => {
if (event.page.kind !== "content" || !event.page.content) return null;
return {
kind: "inline-script",
placement: "body:start",
code: `window.currentContent = ${JSON.stringify({
collection: event.page.content.collection,
id: event.page.content.id,
})};`,
key: "current-content",
};
},
在同一 placement 内,具有相同 key 的贡献保留第一个值。无 key 的外部脚本也会按 src 去重。当两个钩子可能描述同一逻辑片段时,请使用稳定的 key。
页面上下文参考
钩子接收 { page }。page 对象来自宿主布局,对于请求期间加载的内容条目,可叠加 SEO 面板的可选值。
| 字段 | 类型与含义 |
|---|---|
url | 绝对页面 URL。 |
path | URL 的 pathname。 |
locale | 活动语言区域,或 null。 |
kind | EmDash 条目为 content,其他页面为 custom。 |
pageType | 主题定义的类型,通常为 article 或 website。 |
title, pageTitle | 完整文档标题与可选的仅页面标题。 |
description, canonical, image | 页面元数据值,均可为 null。 |
content | 已渲染条目的可选 { collection, id, slug } 引用。 |
seo | Open Graph 标题、描述、图片与 robots 的可选覆盖。 |
articleMeta | 可选的发布时间、修改时间与作者。 |
siteName, siteUrl | 可选的公开站点身份与 origin。 |
breadcrumbs | 可选的 root-first { name, url } 项。空数组表示明确没有面包屑。 |
使用 kind、pageType、path、locale 或 content 限制片段出现的位置。当页面上下文已携带决策所需的身份时,不要再次获取条目。
尽可能优先使用结构化元数据
对以下输出使用 page:metadata:
<meta name="…">与<meta property="…">标签- canonical、alternate、author、license、
nlweb与site.standard.document链接 - JSON-LD 图
page:metadata 会验证其结构化贡献,与页面基础元数据去重,并同时适用于原生与沙箱插件。当浏览器需要接收结构化钩子无法表示的可执行代码或标记时,使用 page:fragments。