release: WRNexusJS 0.4.0
This commit is contained in:
@@ -36,7 +36,9 @@ import {
|
||||
type TFunction,
|
||||
} from "@wrnexus/core";
|
||||
import type { Router } from "@wrnexus/router";
|
||||
import { renderDocument } from "@wrnexus/ssr";
|
||||
import { renderDocument, type RenderScript, type ScriptAsset } from "@wrnexus/ssr";
|
||||
import type { ClientRuntimeDefinition } from "@wrnexus/plugin";
|
||||
import { runtimeScriptsForMarkup } from "./plugin-assets.ts";
|
||||
import {
|
||||
THEME_COOKIE,
|
||||
THEME_CSS_HREF,
|
||||
@@ -58,7 +60,11 @@ import {
|
||||
} from "@wrnexus/i18n";
|
||||
import { runMiddleware } from "./pipeline.ts";
|
||||
import type { HmrHub } from "./hmr.ts";
|
||||
import type { DevToolbarConfig } from "@wrnexus/dev-toolbar/types";
|
||||
import type {
|
||||
DevToolbarConfig,
|
||||
DevToolbarPanel,
|
||||
DevToolbarPlatformSnapshot,
|
||||
} from "@wrnexus/dev-toolbar/types";
|
||||
|
||||
import {
|
||||
createServerIssue,
|
||||
@@ -125,6 +131,8 @@ export interface RuntimeDeps {
|
||||
inlineStyles?: string;
|
||||
/** Production cache-busting version appended to framework asset URLs. */
|
||||
assetVersion?: string;
|
||||
/** Package browser runtimes resolved by the plugin system. */
|
||||
clientRuntimes?: ClientRuntimeDefinition[];
|
||||
/** Raw HTML appended to every page head (e.g. CDN framework links). */
|
||||
head?: string;
|
||||
/** Global SEO defaults. */
|
||||
@@ -152,6 +160,8 @@ export interface RuntimeDeps {
|
||||
config: DevToolbarConfig;
|
||||
collector: DevToolbarCollector;
|
||||
root: string;
|
||||
platform?: DevToolbarPlatformSnapshot;
|
||||
panels?: DevToolbarPanel[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -835,6 +845,8 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
collector: deps.devToolbar.collector,
|
||||
editor: deps.devToolbar.config.editor,
|
||||
allowOpenEditor: deps.devToolbar.config.openEditor !== false,
|
||||
platform: deps.devToolbar.platform,
|
||||
panels: deps.devToolbar.panels,
|
||||
});
|
||||
if (toolbarResponse) return secure(toolbarResponse);
|
||||
}
|
||||
@@ -1111,7 +1123,10 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
selfClose === "/" ? { inner: "", end: openEnd } : readElementBody(body, tag!, openEnd);
|
||||
i = end;
|
||||
|
||||
const component = router.components.find((c) => c.name === name);
|
||||
const normalizedName = name.toLowerCase();
|
||||
const component = router.components.find(
|
||||
(candidate) => candidate.name.toLowerCase() === normalizedName,
|
||||
);
|
||||
if (!component) {
|
||||
console.warn(`[wrnexus] no component registered for '${name}'`);
|
||||
result += body.slice(tagStart, end);
|
||||
@@ -1280,7 +1295,9 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
if (deps.i18n) body = translateHtml(body, ctx.t);
|
||||
|
||||
// Point 3: only ship the JS this page actually uses.
|
||||
const scripts = collectScripts(body).map((src) => versionAssetUrl(src, deps.assetVersion));
|
||||
const scripts = collectScripts(body, deps.clientRuntimes).map((script) =>
|
||||
versionRenderScript(script, deps.assetVersion),
|
||||
);
|
||||
if (pwaServiceWorkerEnabled)
|
||||
scripts.push(versionAssetUrl("/__wrnexus/pwa.js", deps.assetVersion));
|
||||
if (deps.mobile?.enabled !== false && usesMobileRuntime(body))
|
||||
@@ -1319,7 +1336,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
// Conditional GET: hash the page CONTENT (`body`), not the assembled shell —
|
||||
// the shell carries a per-request CSP nonce in dev, which would otherwise make
|
||||
// the ETag change every request. Same content → same ETag → 304 on revalidate.
|
||||
const tag = etag(`${htmlAttrs ?? ""}\n${scripts.join(",")}\n${body}`);
|
||||
const tag = etag(`${htmlAttrs ?? ""}\n${JSON.stringify(scripts)}\n${body}`);
|
||||
const method = ctx.req.method.toUpperCase();
|
||||
if ((method === "GET" || method === "HEAD") && notModified(ctx.req, tag)) {
|
||||
return new Response(null, {
|
||||
@@ -1640,10 +1657,13 @@ export function resolveTProps(
|
||||
* server-rendered into the HTML; the only script is the reactive runtime, and
|
||||
* only when the page actually contains a scope or a browser-side API fetch.
|
||||
*/
|
||||
export function collectScripts(body: string): string[] {
|
||||
export function collectScripts(
|
||||
body: string,
|
||||
clientRuntimes: readonly ClientRuntimeDefinition[] = [],
|
||||
): RenderScript[] {
|
||||
// Client-side navigation is an app-wide progressive enhancement: it must load
|
||||
// on every page (you navigate *from* any page), and degrades to full loads.
|
||||
const scripts: string[] = ["/__wrnexus/nav.js"];
|
||||
const scripts: RenderScript[] = ["/__wrnexus/nav.js"];
|
||||
if (/\bdata-scope=/.test(body) || /\bdata-wrnexus-csr=/.test(body)) {
|
||||
scripts.push("/__wrnexus/reactive.js");
|
||||
}
|
||||
@@ -1667,6 +1687,10 @@ export function collectScripts(body: string): string[] {
|
||||
if (/\bdata-uploader\b/.test(body)) {
|
||||
scripts.push("/__wrnexus/uploader.js");
|
||||
}
|
||||
// Package runtimes are declarative. Components mark the rendered HTML with
|
||||
// `data-wrnexus-runtime="id"`; the corresponding package chunk is loaded
|
||||
// once, without requiring application-authored script tags or public copies.
|
||||
scripts.push(...runtimeScriptsForMarkup(body, clientRuntimes));
|
||||
return scripts;
|
||||
}
|
||||
|
||||
@@ -1686,6 +1710,11 @@ function versionAssetUrl(src: string, version?: string): string {
|
||||
return `${src}${src.includes("?") ? "&" : "?"}v=${encodeURIComponent(version)}`;
|
||||
}
|
||||
|
||||
function versionRenderScript(script: RenderScript, version?: string): RenderScript {
|
||||
if (typeof script === "string") return versionAssetUrl(script, version);
|
||||
return { ...script, src: versionAssetUrl(script.src, version) } satisfies ScriptAsset;
|
||||
}
|
||||
|
||||
function escapeStyleContent(css: string): string {
|
||||
return css.replace(/<\/style/gi, "<\\/style");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user