feat(csr): add stripped dev output diagnostics
This commit is contained in:
@@ -18,8 +18,16 @@ export { REALTIME_RUNTIME } from "./realtime-runtime.ts";
|
|||||||
export { ACTION_RUNTIME } from "./action-runtime.ts";
|
export { ACTION_RUNTIME } from "./action-runtime.ts";
|
||||||
|
|
||||||
/** The reactive runtime served at `/__wrnexus/reactive.js` (plain browser JS). */
|
/** The reactive runtime served at `/__wrnexus/reactive.js` (plain browser JS). */
|
||||||
export function getReactiveRuntime(): string {
|
export function getReactiveRuntime(development = false): string {
|
||||||
return REACTIVE_RUNTIME;
|
if (development) {
|
||||||
|
return REACTIVE_RUNTIME
|
||||||
|
.replace(/\/\*__WRNEXUS_DEV_START__\*\//g, "")
|
||||||
|
.replace(/\/\*__WRNEXUS_DEV_END__\*\//g, "");
|
||||||
|
}
|
||||||
|
return REACTIVE_RUNTIME.replace(
|
||||||
|
/\/\*__WRNEXUS_DEV_START__\*\/[\s\S]*?\/\*__WRNEXUS_DEV_END__\*\//g,
|
||||||
|
"",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The client-side navigation runtime served at `/__wrnexus/nav.js`. */
|
/** The client-side navigation runtime served at `/__wrnexus/nav.js`. */
|
||||||
|
|||||||
@@ -191,6 +191,27 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*__WRNEXUS_DEV_START__*/
|
||||||
|
var developmentWarnings = Object.create(null);
|
||||||
|
function warnOnce(code, message, element, detail) {
|
||||||
|
var key = code + "\\n" + message;
|
||||||
|
if (developmentWarnings[key]) return;
|
||||||
|
developmentWarnings[key] = true;
|
||||||
|
var payload = {
|
||||||
|
code: code,
|
||||||
|
message: message,
|
||||||
|
hydrationId: element && element.getAttribute ? element.getAttribute("data-wrn-hydration") : null,
|
||||||
|
detail: detail || null,
|
||||||
|
};
|
||||||
|
console.warn("[" + code + "] " + message, detail || "");
|
||||||
|
try {
|
||||||
|
window.dispatchEvent(new CustomEvent("wrnexus:diagnostic", { detail: payload }));
|
||||||
|
} catch (_) {
|
||||||
|
// CustomEvent can be unavailable in minimal DOM test environments.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*__WRNEXUS_DEV_END__*/
|
||||||
|
|
||||||
function scheduleUpdateHook(element, callback) {
|
function scheduleUpdateHook(element, callback) {
|
||||||
pendingUpdateHooks.set(element, callback);
|
pendingUpdateHooks.set(element, callback);
|
||||||
if (updateHooksScheduled) return;
|
if (updateHooksScheduled) return;
|
||||||
@@ -3953,6 +3974,15 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
? Promise.all(values)
|
? Promise.all(values)
|
||||||
: values[values.length - 1];
|
: values[values.length - 1];
|
||||||
}
|
}
|
||||||
|
/*__WRNEXUS_DEV_START__*/
|
||||||
|
warnOnce(
|
||||||
|
"WRN-DEV-OUTPUT-UNHANDLED",
|
||||||
|
"Output '" + name + "' has no parent binding. Registered bindings: " +
|
||||||
|
(registry ? Object.keys(registry).join(", ") || "none" : "none") + ".",
|
||||||
|
root,
|
||||||
|
{ output: name, registeredBindings: registry ? Object.keys(registry) : [] },
|
||||||
|
);
|
||||||
|
/*__WRNEXUS_DEV_END__*/
|
||||||
return dispatchComponentEvent(root, name, payload);
|
return dispatchComponentEvent(root, name, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { test, expect, beforeEach } from "bun:test";
|
import { test, expect, beforeEach } from "bun:test";
|
||||||
import { Window } from "happy-dom";
|
import { Window } from "happy-dom";
|
||||||
import { REACTIVE_RUNTIME } from "../src/reactive-runtime.ts";
|
import { REACTIVE_RUNTIME } from "../src/reactive-runtime.ts";
|
||||||
|
import { getReactiveRuntime } from "../src/index.ts";
|
||||||
import { mountHtml } from "@wrnexus/test";
|
import { mountHtml } from "@wrnexus/test";
|
||||||
|
|
||||||
// Fresh DOM per test, with the runtime's globals bound.
|
// Fresh DOM per test, with the runtime's globals bound.
|
||||||
@@ -663,6 +664,38 @@ test("a camelCase output reaches a parent binding despite attribute lowercasing"
|
|||||||
expect(win.document.querySelector("#out")?.textContent).toBe("yes");
|
expect(win.document.querySelector("#out")?.textContent).toBe("yes");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("development runtime warns once for an output with no parent binding", () => {
|
||||||
|
const win = new Window() as unknown as Window & Record<string, unknown>;
|
||||||
|
win.document.body.innerHTML =
|
||||||
|
`<div data-scope="" data-wrn-events="complete"><button data-on-click="output.complete({})">go</button></div>`;
|
||||||
|
(globalThis as Record<string, unknown>).window = win;
|
||||||
|
(globalThis as Record<string, unknown>).document = win.document;
|
||||||
|
(globalThis as Record<string, unknown>).location = win.location;
|
||||||
|
(globalThis as Record<string, unknown>).NodeFilter = (win as unknown as { NodeFilter: unknown }).NodeFilter;
|
||||||
|
(globalThis as Record<string, unknown>).MutationObserver = (win as unknown as { MutationObserver: unknown }).MutationObserver;
|
||||||
|
(globalThis as Record<string, unknown>).CustomEvent = (win as unknown as { CustomEvent: unknown }).CustomEvent;
|
||||||
|
const warnings: unknown[][] = [];
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
console.warn = (...args: unknown[]) => warnings.push(args);
|
||||||
|
try {
|
||||||
|
(0, eval)(getReactiveRuntime(true));
|
||||||
|
(win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }).__wrnexusHydrateScopes?.(win.document);
|
||||||
|
(win.document.querySelector("button") as unknown as HTMLElement).click();
|
||||||
|
(win.document.querySelector("button") as unknown as HTMLElement).click();
|
||||||
|
} finally {
|
||||||
|
console.warn = originalWarn;
|
||||||
|
}
|
||||||
|
expect(warnings).toHaveLength(1);
|
||||||
|
expect(String(warnings[0]?.[0])).toContain("WRN-DEV-OUTPUT-UNHANDLED");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("production runtime strips development diagnostics", () => {
|
||||||
|
const production = getReactiveRuntime();
|
||||||
|
expect(production).not.toContain("WRN-DEV-");
|
||||||
|
expect(production).not.toContain("warnOnce");
|
||||||
|
expect(REACTIVE_RUNTIME).toContain("__WRNEXUS_DEV_START__");
|
||||||
|
});
|
||||||
|
|
||||||
// --- browser globals + regex literals in client expressions ----------------
|
// --- browser globals + regex literals in client expressions ----------------
|
||||||
// Client functions and inline handlers are interpreted by the runtime's own
|
// Client functions and inline handlers are interpreted by the runtime's own
|
||||||
// eval-free expression engine (so a strict CSP needs no unsafe-eval). Anything
|
// eval-free expression engine (so a strict CSP needs no unsafe-eval). Anything
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export function createDevAssetServer(
|
|||||||
if (pathname.startsWith("/__wrnexus/client/")) {
|
if (pathname.startsWith("/__wrnexus/client/")) {
|
||||||
return serveWrnBrowserArtifact(pathname) ?? new Response("Not Found", { status: 404 });
|
return serveWrnBrowserArtifact(pathname) ?? new Response("Not Found", { status: 404 });
|
||||||
}
|
}
|
||||||
if (pathname === "/__wrnexus/reactive.js") return jsResponse(getReactiveRuntime());
|
if (pathname === "/__wrnexus/reactive.js") return jsResponse(getReactiveRuntime(true));
|
||||||
if (pathname === "/__wrnexus/nav.js") return jsResponse(getNavRuntime());
|
if (pathname === "/__wrnexus/nav.js") return jsResponse(getNavRuntime());
|
||||||
if (pathname === "/__wrnexus/realtime.js") return jsResponse(getRealtimeRuntime());
|
if (pathname === "/__wrnexus/realtime.js") return jsResponse(getRealtimeRuntime());
|
||||||
if (pathname === "/__wrnexus/actions.js") return jsResponse(getActionRuntime());
|
if (pathname === "/__wrnexus/actions.js") return jsResponse(getActionRuntime());
|
||||||
|
|||||||
Reference in New Issue
Block a user