release: WRNexusJS 0.6.0
This commit is contained in:
@@ -37,6 +37,12 @@ import {
|
||||
} from "@wrnexus/core";
|
||||
import type { Router } from "@wrnexus/router";
|
||||
import { renderDocument, type RenderScript, type ScriptAsset } from "@wrnexus/ssr";
|
||||
import {
|
||||
disposeRequestStores,
|
||||
renderStoreHydration,
|
||||
requestStoreContainer,
|
||||
} from "@wrnexus/ssr/store-context";
|
||||
import type { StoreDefinition } from "@wrnexus/store";
|
||||
import type { ClientRuntimeDefinition } from "@wrnexus/plugin";
|
||||
import { runtimeScriptsForMarkup } from "./plugin-assets.ts";
|
||||
import {
|
||||
@@ -549,6 +555,8 @@ export const HMR_CLIENT_JS = `
|
||||
} else if (msg.type === "css") {
|
||||
swapCss(msg.version);
|
||||
window.dispatchEvent(new CustomEvent("wrnexus:hmr", { detail: msg }));
|
||||
} else if (msg.type === "store-update") {
|
||||
applyStoreUpdates(msg);
|
||||
} else if (msg.type === "reload") requestSync();
|
||||
else if (msg.type === "html") applyHtml(msg.html);
|
||||
else if (msg.type === "error") console.error("[wrnexus] HMR update failed:", msg.message);
|
||||
@@ -563,6 +571,28 @@ export const HMR_CLIENT_JS = `
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
async function applyStoreUpdates(message) {
|
||||
var updates = Array.isArray(message.stores) ? message.stores : [];
|
||||
for (var i = 0; i < updates.length; i++) {
|
||||
var update = updates[i];
|
||||
try {
|
||||
var separator = String(update.url).indexOf("?") >= 0 ? "&" : "?";
|
||||
var module = await import(String(update.url) + separator + "hmr=" + encodeURIComponent(String(message.version || Date.now())));
|
||||
var definition = module[String(update.name) + "Definition"];
|
||||
if (!definition) throw new Error("Generated store module did not export its definition");
|
||||
if (typeof window.__wrnexusApplyStoreHotUpdate === "function") {
|
||||
var result = await window.__wrnexusApplyStoreHotUpdate(String(update.name), definition);
|
||||
window.dispatchEvent(new CustomEvent("wrnexus:hmr-store-updated", { detail: { update: update, result: result } }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[wrnexus] store HMR update failed", update, error);
|
||||
requestSync();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function requestSync() {
|
||||
if (pendingSync) return;
|
||||
pendingSync = true;
|
||||
@@ -1311,6 +1341,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
|
||||
// Issue the CSRF token cookie so forms on this page can echo it back.
|
||||
csrfToken(ctx);
|
||||
const storeContainer = requestStoreContainer(ctx.req, matched.route.raw);
|
||||
|
||||
const mod = await loadModule(matched.route.file);
|
||||
const component = mod.default;
|
||||
@@ -1322,8 +1353,10 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
const meta = (mod.meta ?? {}) as PageMeta;
|
||||
const pageCtx = ctx as Context & {
|
||||
__wrnexusCallApi?: (path: string, method?: string) => Promise<unknown>;
|
||||
__wrnexusUseStore?: (definition: StoreDefinition<any, any, any>) => Promise<unknown>;
|
||||
};
|
||||
pageCtx.__wrnexusCallApi = (path, method = "GET") => callApiFromContext(ctx, path, method);
|
||||
pageCtx.__wrnexusUseStore = (definition) => storeContainer.use(definition);
|
||||
let body = await renderComponents(String(await component(pageCtx)), ctx.t);
|
||||
const resolvedTheme = deps.theme
|
||||
? resolveThemeName(ctx.cookies.get(THEME_COOKIE), deps.theme)
|
||||
@@ -1334,15 +1367,24 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
// Page layout: a page selects one by exporting `layout = "<name>"`
|
||||
// (app/layouts/<name>.wrn), else falls back to a `default` layout if one
|
||||
// exists. `layout = "none"` opts out. The layout wraps the body via <slot>.
|
||||
const importedLayout =
|
||||
mod.layout && typeof mod.layout === "object"
|
||||
? (mod.layout as { name?: string; render?: (props: Record<string, unknown>) => string })
|
||||
: undefined;
|
||||
const layoutName =
|
||||
(isMobileRequest && deps.mobile?.layout
|
||||
? deps.mobile.layout
|
||||
: typeof mod.layout === "string"
|
||||
? mod.layout
|
||||
: undefined) ?? "default";
|
||||
const layout =
|
||||
layoutName === "none" ? undefined : router.layouts.find((l) => l.name === layoutName);
|
||||
if (layout) {
|
||||
: importedLayout?.name) ?? "default";
|
||||
const layout = importedLayout
|
||||
? undefined
|
||||
: layoutName === "none"
|
||||
? undefined
|
||||
: router.layouts.find((l) => l.name === layoutName);
|
||||
if (importedLayout?.render) {
|
||||
body = await renderComponents(fillSlots(String(importedLayout.render({})), body), ctx.t);
|
||||
} else if (layout) {
|
||||
try {
|
||||
const layoutMod = await loadModule(layout.file);
|
||||
const layoutRender = (layoutMod as { render?: (p: Record<string, string>) => string })
|
||||
@@ -1451,6 +1493,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
.join("\n "),
|
||||
extraBody:
|
||||
[
|
||||
renderStoreHydration(storeContainer, (ctx.locals.cspNonce as string) ?? undefined),
|
||||
hmr ? hmrClientTag((ctx.locals.cspNonce as string) ?? "") : "",
|
||||
shouldEnableDevToolbar(mode, deps) ? DEV_TOOLBAR_SCRIPT : "",
|
||||
]
|
||||
@@ -1464,6 +1507,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
// 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${JSON.stringify(scripts)}\n${body}`);
|
||||
await disposeRequestStores(ctx.req);
|
||||
const method = ctx.req.method.toUpperCase();
|
||||
if ((method === "GET" || method === "HEAD") && notModified(ctx.req, tag)) {
|
||||
return new Response(null, {
|
||||
|
||||
Reference in New Issue
Block a user