release: WRNexusJS 0.4.0

This commit is contained in:
2026-07-27 12:42:18 +05:30
parent 8b728a3e5d
commit 30e5721e84
250 changed files with 10065 additions and 3923 deletions
+36 -7
View File
@@ -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");
}