Files
WRNexusJS/packages/dev-server/src/assets.ts
T
ClintchizandClaude Opus 5 a184f1a3be feat(islands): serve the island runtime in dev, prod, and static builds
Adds /__wrnexus/islands.js (the bootstrap) and the /__wrnexus/island/
prefix (mount runtime, island bundles, shared chunks) to all three
serving paths.

Dev reuses the browserArtifactPaths registry pattern from pipeline.ts.
Prod mirrors the clientModulesDir handler, including its filename
allowlist, so island names cannot escape the output directory.

The bootstrap is inert without a data-wrn-island marker, so island-free
pages still download nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 15:28:10 +05:30

176 lines
6.0 KiB
TypeScript

/**
* Dev-mode asset server for `/__wrnexus/*`:
* /__wrnexus/reactive.js the reactive runtime
* /__wrnexus/theme.css design-token themes (per resolved theme config)
* /__wrnexus/theme.js client theme switcher
* /__wrnexus/styles.css bundled global stylesheet (cached)
*
* Components are `.wrn` files rendered on the server (see runtime.ts), so there
* are no per-component browser chunks to build or serve here. The CSS cache is
* invalidated in-process by the file watcher so edits show without a restart.
*/
import {
getActionRuntime,
getComponentControllerRuntime,
getReactiveRuntime,
getNavRuntime,
getRealtimeRuntime,
} from "@wrnexus/csr";
import {
renderStyles,
renderActiveThemeCss,
renderThemeCss,
renderThemeRuntime,
type ResolvedTheme,
type StylesConfig,
} from "@wrnexus/styles";
import { getIslandRuntime } from "@wrnexus/react/runtime";
import { VALIDATE_RUNTIME } from "@wrnexus/validation";
import { I18N_RUNTIME } from "@wrnexus/i18n";
import { UPLOAD_RUNTIME, UPLOAD_JS_HREF, UPLOADS_PREFIX, serveStoredFile } from "@wrnexus/uploader";
import type { Mode } from "@wrnexus/core";
import type { AssetServer } from "./runtime.ts";
import { servePublicAsset } from "./public.ts";
import { servePluginAsset, type ServedPluginAsset } from "./plugin-assets.ts";
import { serveIslandArtifact, serveWrnBrowserArtifact } from "./pipeline.ts";
/** Style inputs the dev asset server needs to build `/__wrnexus/styles.css`. */
export interface DevStyles {
entry: string | null;
config?: StylesConfig;
appRoot: string;
publicDir?: string;
sources?: string[];
entries?: string[];
}
/** A dev asset server also supports invalidating its caches in-process. */
export interface DevAssetServer extends AssetServer {
invalidateCss(): void;
updateSchemas(code: string): void;
}
function jsResponse(code: string): Response {
return new Response(code, {
headers: {
"content-type": "text/javascript; charset=utf-8",
"cache-control": "no-store, max-age=0",
pragma: "no-cache",
expires: "0",
},
});
}
function cssResponse(code: string): Response {
return new Response(code, {
headers: {
"content-type": "text/css; charset=utf-8",
"cache-control": "no-cache",
},
});
}
export function createDevAssetServer(
appDir: string,
mode: Mode,
styles?: DevStyles,
theme?: ResolvedTheme,
uiCss?: string | (() => string),
schemasJs?: string,
pluginAssets: readonly ServedPluginAsset[] = [],
): DevAssetServer {
let cssCache: string | null = null;
let schemasCode = schemasJs ?? "window.__wrnSchemas={};";
return {
invalidateCss() {
cssCache = null;
},
updateSchemas(code) {
schemasCode = code;
},
async serve(pathname: string): Promise<Response | null> {
if (pathname.startsWith("/__wrnexus/client/")) {
return serveWrnBrowserArtifact(pathname) ?? new Response("Not Found", { status: 404 });
}
if (pathname.startsWith("/__wrnexus/island/")) {
return serveIslandArtifact(pathname) ?? new Response("Not Found", { status: 404 });
}
if (pathname === "/__wrnexus/islands.js") return jsResponse(getIslandRuntime(true));
if (pathname === "/__wrnexus/reactive.js") return jsResponse(getReactiveRuntime(true));
if (pathname === "/__wrnexus/controllers.js")
return jsResponse(getComponentControllerRuntime(true));
if (pathname === "/__wrnexus/nav.js") return jsResponse(getNavRuntime());
if (pathname === "/__wrnexus/realtime.js") return jsResponse(getRealtimeRuntime());
if (pathname === "/__wrnexus/actions.js") return jsResponse(getActionRuntime());
if (pathname === "/__wrnexus/validate.js") return jsResponse(VALIDATE_RUNTIME);
if (pathname === "/__wrnexus/i18n.js") return jsResponse(I18N_RUNTIME);
if (pathname === UPLOAD_JS_HREF) return jsResponse(UPLOAD_RUNTIME);
// Public local uploads served at /__wrnexus/uploads/<store>/<key>.
if (pathname.startsWith(UPLOADS_PREFIX)) {
return (await serveStoredFile(pathname)) ?? new Response("Not Found", { status: 404 });
}
if (pathname === "/__wrnexus/schemas.js") return jsResponse(schemasCode);
if (pathname === "/__wrnexus/ui.css") {
const currentUiCss = typeof uiCss === "function" ? uiCss() : uiCss;
return currentUiCss
? cssResponse(currentUiCss)
: new Response("Not Found", { status: 404 });
}
if (pathname === "/__wrnexus/theme.css") {
return theme
? cssResponse(renderThemeCss(theme))
: new Response("Not Found", { status: 404 });
}
const activeThemeMatch = /^\/__wrnexus\/theme\/([^/]+)\/([^/]+)\.css$/.exec(pathname);
if (activeThemeMatch && theme) {
try {
const themeName = decodeURIComponent(activeThemeMatch[1]!);
const accentPart = decodeURIComponent(activeThemeMatch[2]!);
return cssResponse(
renderActiveThemeCss(theme, themeName, accentPart === "_" ? undefined : accentPart),
);
} catch {
return new Response("Not Found", { status: 404 });
}
}
if (pathname === "/__wrnexus/theme.js") {
return theme
? jsResponse(renderThemeRuntime(theme))
: new Response("Not Found", { status: 404 });
}
if (pathname === "/__wrnexus/styles.css") {
if (!styles?.entry && !styles?.entries?.length) {
return new Response("Not Found", { status: 404 });
}
if (cssCache === null) {
cssCache = await renderStyles(
{
entryPath: styles.entry,
appDir,
appRoot: styles.appRoot,
mode,
sources: styles.sources,
entries: styles.entries,
},
styles.config,
);
}
return cssResponse(cssCache);
}
const pluginAsset = await servePluginAsset(pluginAssets, pathname, mode);
if (pluginAsset) return pluginAsset;
return servePublicAsset(styles?.publicDir, pathname, mode);
},
};
}