diff --git a/build/server-worker.js b/build/server-worker.js index 2b73b67e5..99abb9674 100644 --- a/build/server-worker.js +++ b/build/server-worker.js @@ -28,10 +28,12 @@ if (!indexModulePath) { try { /** @type {import("../entry.ssr.js")} */ const indexModule = await import(pathToFileURL(indexModulePath).href); - const html = await indexModule?.render(reqPath, context, { - client: compilationStats.find((x) => x.name === "client") || {}, - legacy: compilationStats.find((x) => x.name === "legacy") || {}, - }); + const html = process.env.FRED_SIMPLE_HTML + ? `${await indexModule?.renderSimple(reqPath, context)}` + : await indexModule?.render(reqPath, context, { + client: compilationStats.find((x) => x.name === "client") || {}, + legacy: compilationStats.find((x) => x.name === "legacy") || {}, + }); parentPort?.postMessage({ html }); } catch (error) { parentPort?.postMessage({ error }); diff --git a/components/doc/server.js b/components/doc/server.js index e17b3955f..5773cbb7f 100644 --- a/components/doc/server.js +++ b/components/doc/server.js @@ -5,8 +5,16 @@ import { ServerComponent } from "../server/index.js"; export class Doc extends ServerComponent { /** * @param {import("@fred").Context} context - */ render(context) { + */ + render(context) { context.pageTitle = context.doc.pageTitle; return PageLayout.render(context, ReferenceLayout.render(context)); } + + /** + * @param {import("@fred").Context} context + */ + renderSimple(context) { + return ReferenceLayout.render(context); + } } diff --git a/components/outer-layout/server.js b/components/outer-layout/server.js index 5ee26e72b..3e8ad71f3 100644 --- a/components/outer-layout/server.js +++ b/components/outer-layout/server.js @@ -26,6 +26,9 @@ export class OuterLayout extends ServerComponent { if (!asyncStore) { throw new Error("asyncLocalStorage missing"); } + if ("renderSimple" in asyncStore) { + throw new Error("OuterLayout called from renderSimple function"); + } const { componentsUsed, componentsWithStylesInHead, compilationStats } = asyncStore; diff --git a/components/reference-layout/server.js b/components/reference-layout/server.js index b7abee5c2..948352258 100644 --- a/components/reference-layout/server.js +++ b/components/reference-layout/server.js @@ -44,4 +44,18 @@ export class ReferenceLayout extends ServerComponent { `; } + + /** + * @param {import("@fred").Context} context + */ + renderSimple(context) { + const { doc } = context; + const sections = + doc.body?.map((section) => ContentSection.render(context, section)) || []; + + return html` +

${doc.title}

+ ${BaselineIndicator.render(context)} ${sections} + `; + } } diff --git a/components/server/async-local-storage.js b/components/server/async-local-storage.js index d9ba4fcd4..db2f1b4ac 100644 --- a/components/server/async-local-storage.js +++ b/components/server/async-local-storage.js @@ -1,4 +1,13 @@ import { AsyncLocalStorage } from "node:async_hooks"; -/** @type {AsyncLocalStorage} */ +/** + * Store for internal context passed around components. + * + * Generally only used within the `ServerComponent` class itself, + * or very special server components (such as the `OuterLayout`). + * + * Populated in `entry.ssr.js`. + * + * @type {AsyncLocalStorage} + */ export const asyncLocalStorage = new AsyncLocalStorage(); diff --git a/components/server/index.js b/components/server/index.js index e6ff8a21c..0addb94cb 100644 --- a/components/server/index.js +++ b/components/server/index.js @@ -24,6 +24,13 @@ export class ServerComponent { if (!asyncStore) { throw new Error("asyncLocalStorage missing"); } + + const component = new this(); + + if ("renderSimple" in asyncStore) { + return component.renderSimple(...args); + } + const { componentsUsed, componentsWithStylesInHead, compilationStats } = asyncStore; const componentUsedBefore = componentsUsed.has(this.name); @@ -35,7 +42,7 @@ export class ServerComponent { componentsUsed.add("legacy"); } - let res = new this().render(...args); + let res = component.render(...args); if (!res || res === nothing) { if (!componentUsedBefore) { @@ -73,4 +80,12 @@ export class ServerComponent { render(..._args) { throw new Error("Must be implemented by subclass"); } + + /** + * @param {...any} args + * @returns {any} + */ + renderSimple(...args) { + return this.render(...args); + } } diff --git a/components/server/types.d.ts b/components/server/types.d.ts index 9eebcaddf..95e6c238b 100644 --- a/components/server/types.d.ts +++ b/components/server/types.d.ts @@ -1,5 +1,9 @@ -export interface AsyncLocalStorageContents { - componentsUsed: Set; - componentsWithStylesInHead: Set; - compilationStats: import("@fred").CompilationStats; -} +export type FredLocalContents = + | { + componentsUsed: Set; + componentsWithStylesInHead: Set; + compilationStats: import("@fred").CompilationStats; + } + | { + renderSimple: true; + }; diff --git a/entry.ssr.js b/entry.ssr.js index 2b9e63a0f..b16469ef3 100644 --- a/entry.ssr.js +++ b/entry.ssr.js @@ -2,6 +2,8 @@ import { render as r } from "@lit-labs/ssr"; import { collectResult } from "@lit-labs/ssr/lib/render-result.js"; +import { nothing } from "lit"; + import { Advertising } from "./components/advertising/server.js"; import { BlogIndex } from "./components/blog-index/server.js"; import { BlogPost } from "./components/blog-post/server.js"; @@ -54,7 +56,7 @@ export async function render(path, partialContext, compilationStats) { ...(await addFluent(locale)), ...partialContext, }; - /** @type {import("./components/server/types.js").AsyncLocalStorageContents} */ + /** @type {import("./components/server/types.js").FredLocalContents} */ const storageContents = { componentsUsed: new Set(), componentsWithStylesInHead: new Set(), @@ -130,3 +132,33 @@ export async function render(path, partialContext, compilationStats) { }), ); } + +/** + * @param {string} path + * @param {import("@fred").PartialContext} partialContext + */ +export async function renderSimple(path, partialContext) { + const locale = path.split("/")[1] || "en-US"; + const context = { + path, + ...(await addFluent(locale)), + ...partialContext, + }; + /** @type {import("./components/server/types.js").FredLocalContents} */ + const storageContents = { + renderSimple: true, + }; + return asyncLocalStorage.run(storageContents, () => + runWithContext({ locale }, async () => { + const component = await (async () => { + switch (context.renderer) { + case "Doc": + return Doc.render(context); + default: + return nothing; + } + })(); + return await collectResult(r(component)); + }), + ); +}