Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions build/server-worker.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { parentPort, workerData } from "node:worker_threads";
Expand Down Expand Up @@ -26,15 +27,26 @@ if (!indexModulePath) {
}

try {
/** @type {import("../entry.ssr.js")} */
const indexModule = await import(pathToFileURL(indexModulePath).href);
const html = process.env.FRED_SIMPLE_HTML
? `<!doctype html><meta charset="UTF-8">${await indexModule?.renderSimplified(reqPath, context)}`
: await indexModule?.render(reqPath, context, {
client: compilationStats.find((x) => x.name === "client") || {},
legacy: compilationStats.find((x) => x.name === "legacy") || {},
});
parentPort?.postMessage({ html });
if (process.env.FRED_BROWSER_SSR) {
const ssrBrowserStats = compilationStats.find(
(x) => x.name === "ssr-browser",
);
const html = await fs.readFile(
path.join(ssrBrowserStats.outputPath, "index.html"),
"utf8",
);
parentPort?.postMessage({ html });
} else {
/** @type {import("../entry.ssr.js")} */
const indexModule = await import(pathToFileURL(indexModulePath).href);
const html = process.env.FRED_SIMPLE_HTML
? `<!doctype html><meta charset="UTF-8">${await indexModule?.renderSimplified(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 });
}
26 changes: 26 additions & 0 deletions components/server/async-local-storage-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export class AsyncLocalStorage {
#store = undefined;

run(store, callback) {
this.#store = store;
return callback();
}

getStore() {
return this.#store;
}
}

/**
* Store for internal context passed around components.
*
* Generally only used within the `ServerComponent` class itself,
* or very special server components (such as the `OuterLayout`).
*
* e.g. We add the rspack compilation stats for use in `OuterLayout`.
*
* Populated in `entry.ssr.js`.
*
* @type {AsyncLocalStorage<import("./types.js").FredLocalContents>}
*/
export const asyncLocalStorage = new AsyncLocalStorage();
57 changes: 57 additions & 0 deletions entry.ssr-browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { render as litRender } from "lit";

import { Doc } from "./components/doc/server.js";
import { NotFound } from "./components/not-found/server.js";
import { asyncLocalStorage } from "./components/server/async-local-storage.js";
import { addFluent } from "./l10n/context.js";
import { runWithContext } from "./symmetric-context/server.js";

const cssContext = require.context(
"./components",
true,
/\/(server|global)\.css$/,
);
cssContext.keys().forEach(cssContext);

const params = new URLSearchParams(globalThis.location.search);
const path = params.get("path");

const response = await fetch(`${path}/index.json`);
const json = await response.json();

await render(path, json, null);

/**
* @param {string} path
* @param {import("@fred").PartialContext} partialContext
* @param {import("@fred").CompilationStats} compilationStats
*/
export async function render(path, partialContext, compilationStats) {
const locale = "en-US";

const context = {
path,
...(await addFluent(locale)),
...partialContext,
};
/** @type {import("./components/server/types.js").FredLocalContents} */
const storageContents = {
componentsUsed: new Set(),
componentsWithStylesInHead: new Set(),
compilationStats,
};
return asyncLocalStorage.run(storageContents, () =>
runWithContext({ locale }, async () => {
const component = await (async () => {
switch (context.renderer) {
case "Doc":
return Doc.render(context);
case "SpaNotFound":
default:
return NotFound.render(context);
}
})();
return litRender(component, document.body);
}),
);
}
50 changes: 50 additions & 0 deletions rspack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,55 @@ const clientConfig = merge(
},
);

const ssrinBrowserConfig = merge(clientConfig, {
name: "ssr-browser",
target: ["web", "browserslist"],
async entry() {
return {
index: [
// load custom elements
...(await crawl(path.join(__dirname, "components"), (filePath) =>
filePath.endsWith("/element.js"),
)),
"./entry.ssr-browser.js",
],
};
},
plugins: [
new rspack.NormalModuleReplacementPlugin(
/^node:async_hooks$/,
path.resolve(
__dirname,
"components/server/async-local-storage-client.js",
),
),
new rspack.HtmlRspackPlugin({
inject: true,
chunks: ["index"],
filename: "index.html",
scriptLoading: "module",
// template: "node_modules/@mdn/yari/client/public/index.html",
}),
],
resolve: {
alias: {
"@lit-labs/ssr": "lit",
},
},
output: {
path: path.resolve(FRED_BUILD_ROOT, "static", "ssr-browser"),
filename: "[name].js",
// use proper file names in sourcemaps:
devtoolModuleFilenameTemplate: (info) =>
path.resolve(info.absoluteResourcePath),
clean: {
keep: "index.d.ts",
},
publicPath: "/static/ssr-browser/",
library: { type: "module" },
},
});

const legacyConfig = merge(
common,
notServiceWorkerCommon,
Expand Down Expand Up @@ -639,6 +688,7 @@ const serviceWorkerConfig = merge(common, {
/** @type {import("@rspack/core").MultiRspackOptions} */
export default [
ssrConfig,
ssrinBrowserConfig,
clientConfig,
...(buildLegacy ? [legacyConfig, serviceWorkerConfig] : []),
];
Loading