独自ホスト lifecycle#
サイトが layout と公開ポリシーを持ちつつ、Vite lifecycle は Ox Content に任せたい
場合は oxContentCustomHost() を使います。
// vite.config.ts
import { defineConfig } from "vite";
import { oxContentCustomHost } from "@ox-content/vite-plugin";
export default defineConfig({
appType: "custom",
plugins: [
oxContentCustomHost({
host: "./src/site-host.ts",
oxContent: {
srcDir: "content",
redirects: { provider: "netlify" },
ssg: {
markdownSource: true,
siteUrl: "https://example.com",
siteName: "Example",
},
},
themeTokens: {
theme: colorScheme,
include: (name) => name.startsWith("syntax-"),
},
}),
],
});
この factory は oxContent({ ssg: { enabled: false } }) と独自ホスト plugin を
一緒に登録します。すでに oxContent() を自分で入れているホスト向けには、
低レベルの createOxContentCustomHostPlugin() もあります。
ホスト module#
ホスト module は route を export します。route は通常の Response、または
html、text、contentType、metadata、dependencies を持つ plain object を
返せます。
// src/site-host.ts
export default {
routes: [
{
path: "/",
inputPath: "content/index.md",
source: "# Home\n",
aliases: ["/old-home"],
dependencies: ["src/data.ts"],
async render(ctx) {
const data = await ctx.loadModule("/src/data.ts");
const assets = ctx.assets.document({
head: "<title>" + data.title + "</title>",
sharedStyles: [ctx.assets.themeTokens?.href].filter(Boolean),
clientEntries: ["src/main.ts"],
crossorigin: true,
});
return {
html: `<!doctype html><html><head>${assets.headHtml}</head><body>${data.html}</body></html>`,
title: data.title,
};
},
},
],
};
development では Ox Content が Vite 経由で host を SSR load し、route を dispatch
します。status と content type を保ち、HTML だけに transformIndexHtml() を適用し、
route も custom 404 もなければ fallthrough します。route response は promise として
cache され、宣言した dependency の変更で該当 response だけ invalidation されます。
reload は debounce され、失敗した render は次回 retry され、古い in-flight render が
新しい cache entry を消すこともありません。
production では Vite が client asset と .vite/manifest.json を出したあと、
closeBundle から一度だけ走ります。host と site module を SSR load するためだけに
一時的な middleware-mode Vite server を開き、raw server ではなく ctx.loadModule を
渡し、finally で必ず閉じます。
協調する出力#
ホストが描画した HTML route は、既定 SSG と同じ公開 writer に接続されます。
writeResourceFiles()による resource fingerprint と HTML URL 書き換え。writeSelfHostedAssets()による font と Iconify CSS。- route
sourceからのwriteMarkdownCompanions()。 - route
aliases/redirectからのwriteRedirectOutputs()。 - 選択された route metadata からの
writeFeedFiles()とwriteSiteMapFiles()。
route の出力 path が重複すると、どの route 同士が衝突したかを示して build を 失敗させます。公開対象の選択はホストが持ち、Ox Content はホストが返した route だけを書きます。
Solid HTML-string host は、同じ選択済み route / document set から browser island registry を
生成できます。@ox-content/vite-plugin-solid の
createSolidHtmlHostIslandRegistry() を使い、client entry では directory 全体の
import.meta.glob() の代わりに virtual:ox-content-solid/html-host/modules を
import します。生成 module には選択された island dynamic-import root だけが入り、
Vite はその transitive dependency を保持します。
Island stylesheet#
描画済み route が SSR-visible island の browser module id を知っている場合は、
blocking CSS を ctx.assets.stylesheets() から解決します。
const islandStyles = ctx.assets.stylesheets({
modules: rendered.clientModules.map((module) => module.moduleId),
});
const assets = ctx.assets.document({
islandStyles: islandStyles.stylesheets,
clientEntries: ["src/main.ts"],
});
return {
html: `<!doctype html><html><head>${assets.headHtml}</head><body>${rendered.html}</body></html>`,
dependencies: islandStyles.dependencies,
};
development では、host が ctx.loadModule() で island を描画したあと、描画済み browser module id
に対応する内部 Vite module graph を辿り、direct / transitive CSS を依存順に返します。
CSS query string は保持され、source file dependency も返るため cached route を invalidation
できます。build では Vite manifest を使い、同じ module identity から emitted hashed stylesheet
href を返します。entry が見つからない場合は style を黙って落とさず diagnostic に出ます。
返された style は ctx.assets.document() に渡すと、document-level dedupe、nonce、base、
shared CSS、page CSS と同じ場所で合成できます。
テーマトークン stylesheet#
themeTokens は小さな stylesheet を書き出して dev でも配信します。既定 href は
/__ox_theme_tokens__/theme-tokens.css です。ctx.assets.themeTokens.href を
ctx.assets.document() に渡せば、local Vite transform なしで syntax token CSS を
使えます。