ページフラグメント

このページ

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 の寄与は verbatim で挿入されます。可能なときはフラグメントを静的にし、そうでなければ設定、コンテンツ、リクエストデータ、外部サービスから来るすべての値をエスケープしてください。

ネイティブプラグインでは、ケイパビリティを definePlugin() で宣言します。createPlugin() がランタイムのケイパビリティ一覧を供給するため、ディスクリプタに二度目のコピーは不要です。

ケイパビリティがない場合、EmDash は警告を記録し、フックを登録しません。フックのエラーは記録され、ページのレンダリングは妨げられません。

レイアウトの挿入ポイントを追加する

ホストテーマがサポートする placement を選びます。対応する EmDash コンポーネントに同じ PublicPageContext を渡す必要があります。

  1. レイアウトでページコンテキストを一度構築します。

    ---
    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,
    });
    ---
  2. サポートする各 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 に記載してください。

寄与のリファレンス

フックは 1 つの寄与、配列、または null を返せます。サポートされる寄与の形は次のとおりです。

kind必須フィールド任意フィールド出力の動作
external-scriptplacement, srcasync, defer, attributes, key<script src="…"> 要素をレンダリングします。
inline-scriptplacement, codeattributes, key<script> 要素内に code をレンダリングします。
htmlplacement, htmlkeyhtml をサニタイズせずに挿入します。

placement は head、body:start、body:end を受け付けます。

EmDash は属性名と値を HTML エスケープし、名前が on で始まる属性を削除します。インラインスクリプトでは </ をエスケープし、値がスクリプト要素を閉じられないようにします。これらのレンダラー検査は、信頼できない 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",
	};
},

1 つの placement 内では、同じ key の寄与は最初の値を保持します。キーのない外部スクリプトは src でも重複排除されます。2 つのフックが同じ論理フラグメントを記述し得るときは、安定したキーを使ってください。

ページコンテキストのリファレンス

フックは { page } を受け取ります。page オブジェクトはホストレイアウトから来ており、リクエスト中に読み込まれたコンテンツエントリーには SEO パネルの値を任意で重ねます。

フィールド型と意味
url絶対ページ URL。
pathURL のパス名。
localeアクティブなロケール、または null。
kindEmDash エントリーなら content、他のページなら custom。
pageTypeテーマ定義の型。通常は article または website。
title, pageTitle完全なドキュメントタイトルと、任意のページ専用タイトル。
description, canonical, imageページメタデータ値。いずれも nullable。
contentレンダリングされたエントリーの任意の { collection, id, slug } 参照。
seoOpen Graph のタイトル、説明、画像、robots の任意の上書き。
articleMeta任意の公開時刻、更新時刻、著者。
siteName, siteUrl任意の公開サイト識別情報とオリジン。
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 を使ってください。