release: WRNexusJS 0.3.0

This commit is contained in:
2026-07-22 17:29:08 +05:30
parent 13dfa31d19
commit 07d8fb59d6
145 changed files with 9664 additions and 3881 deletions
+387 -108
View File
@@ -25,6 +25,21 @@ export const REACTIVE_RUNTIME = String.raw`
var updateHooksScheduled = false;
var behaviorObserver;
function reportDiagnostic(code, message, element, detail) {
var payload = {
code: code,
message: message,
hydrationId: element && element.getAttribute ? element.getAttribute("data-wrn-hydration") : null,
detail: detail || null,
};
console.error("[wrnexus:" + code + "] " + message, detail || "");
try {
window.dispatchEvent(new CustomEvent("wrnexus:diagnostic", { detail: payload }));
} catch (_) {
// CustomEvent can be unavailable in minimal DOM test environments.
}
}
function scheduleUpdateHook(element, callback) {
pendingUpdateHooks.set(element, callback);
if (updateHooksScheduled) return;
@@ -424,8 +439,10 @@ export const REACTIVE_RUNTIME = String.raw`
encodedScope,
);
} catch (error) {
console.error(
"[wrnexus] failed to decode scope payload",
reportDiagnostic(
"WRN-HYDRATE-SCOPE",
"Failed to decode the server-rendered scope payload.",
el,
error,
);
@@ -445,6 +462,16 @@ export const REACTIVE_RUNTIME = String.raw`
var signals = {};
Object.keys(initial).forEach(function (k) { signals[k] = signal(initial[k]); });
var behavior = parseBehavior(el);
var computedDefinitions = {};
var computing = new Set();
if (behavior && Array.isArray(behavior.computed)) {
behavior.computed.forEach(function (entry) {
if (entry && typeof entry.name === "string" && typeof entry.expr === "string") {
computedDefinitions[entry.name] = entry.expr;
}
});
}
var renderers = [];
// Dependency-tracked rendering: while a renderer runs, every signal it reads
@@ -452,7 +479,41 @@ export const REACTIVE_RUNTIME = String.raw`
// change then re-runs only the renderers that actually read it. The Set in
// signal.subscribe dedupes, so re-subscribing each run is cheap and bounded.
var currentRenderer = null;
var currentRenderer = null;
var pendingRenderers = new Set();
var batchDepth = 0;
var flushingRenderers = false;
function flushRenderers() {
if (flushingRenderers || batchDepth > 0) return;
flushingRenderers = true;
try {
while (pendingRenderers.size > 0) {
var queue = Array.from(pendingRenderers);
pendingRenderers.clear();
queue.forEach(function (run) { run(); });
}
} finally {
flushingRenderers = false;
}
}
function scheduleRenderer(renderer) {
pendingRenderers.add(renderer);
flushRenderers();
}
function batchUpdates(callback) {
batchDepth++;
try {
return callback();
} finally {
batchDepth--;
flushRenderers();
}
}
function reactive(fn) {
var running = false;
@@ -467,7 +528,7 @@ export const REACTIVE_RUNTIME = String.raw`
var previousRenderer =
currentRenderer;
currentRenderer = run;
currentRenderer = schedule;
try {
fn();
@@ -479,6 +540,10 @@ export const REACTIVE_RUNTIME = String.raw`
}
}
function schedule() {
scheduleRenderer(run);
}
renderers.push(run);
return run;
@@ -577,6 +642,18 @@ export const REACTIVE_RUNTIME = String.raw`
}
function readScope(name) {
if (Object.prototype.hasOwnProperty.call(computedDefinitions, name)) {
if (computing.has(name)) {
reportDiagnostic("WRN-COMPUTED-CYCLE", "Computed value '" + name + "' has a dependency cycle.", el);
return undefined;
}
computing.add(name);
try {
return evalExpr(computedDefinitions[name]);
} finally {
computing.delete(name);
}
}
var sig = signals[name];
if (sig) {
if (currentRenderer) sig.subscribe(currentRenderer);
@@ -589,6 +666,9 @@ export const REACTIVE_RUNTIME = String.raw`
}
function peekScope(name) {
if (Object.prototype.hasOwnProperty.call(computedDefinitions, name)) {
return readScope(name);
}
if (signals[name]) return signals[name].get();
if (Object.prototype.hasOwnProperty.call(behaviorFunctions, name)) {
return behaviorFunctions[name];
@@ -664,68 +744,70 @@ export const REACTIVE_RUNTIME = String.raw`
source,
locals,
) {
var statements =
splitStatements(source);
return batchUpdates(function () {
var statements =
splitStatements(source);
for (
var statementIndex = 0;
statementIndex <
statements.length;
statementIndex++
) {
var result = runStatement(
statements[statementIndex],
function (expression) {
return evalExpr(
expression,
locals,
);
},
function (name) {
if (
locals &&
Object.prototype
.hasOwnProperty.call(
locals,
name,
)
) {
return locals[name];
}
for (
var statementIndex = 0;
statementIndex <
statements.length;
statementIndex++
) {
var result = runStatement(
statements[statementIndex],
function (expression) {
return evalExpr(
expression,
locals,
);
},
function (name) {
if (
locals &&
Object.prototype
.hasOwnProperty.call(
locals,
name,
)
) {
return locals[name];
}
return peekScope(name);
},
function (name, value) {
if (
locals &&
Object.prototype
.hasOwnProperty.call(
locals,
name,
)
) {
locals[name] = value;
} else {
writeScope(name, value);
}
},
function (body) {
return runStmt(
body,
locals,
);
},
);
return peekScope(name);
},
function (name, value) {
if (
locals &&
Object.prototype
.hasOwnProperty.call(
locals,
name,
)
) {
locals[name] = value;
} else {
writeScope(name, value);
}
},
function (body) {
return runStmt(
body,
locals,
);
},
);
if (result.returned) {
return result;
if (result.returned) {
return result;
}
}
}
return {
returned: false,
value: undefined,
};
return {
returned: false,
value: undefined,
};
});
}
function installBehaviorFunctions(source) {
@@ -771,13 +853,31 @@ export const REACTIVE_RUNTIME = String.raw`
}
// --- data-for list rendering -------------------------------------------
// Each [data-for="item in list"] element is a per-item template. On any
// change to the list (or a dependency an item reads), the list re-renders.
// Each [data-for="item in list"] element is a per-item template. Add
// data-key="item.id" or a key item.id suffix preserves DOM nodes when
// a list is reordered. Unkeyed loops retain the legacy full-rerender path.
function parseFor(value) {
var m = /^\s*([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*)\s*)?\s+in\s+([\s\S]+?)\s*$/.exec(
var m = /^\s*([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*)\s*)?\s+in\s+([\s\S]+?)(?:\s+key\s+([\s\S]+?))?\s*$/.exec(
value || "",
);
return m ? { item: m[1], index: m[2], list: m[3] } : null;
return m ? { item: m[1], index: m[2], list: m[3].trim(), key: m[4] && m[4].trim() } : null;
}
function unwrapForKey(value) {
var expression = String(value || "").trim();
if (expression.charAt(0) === "{" && expression.charAt(expression.length - 1) === "}") {
expression = expression.slice(1, -1).trim();
}
return expression;
}
function stableForKey(value) {
if (value === null) return "null:";
var type = typeof value;
if (type === "object") {
try { return "object:" + JSON.stringify(value); } catch (_) { return "object:" + String(value); }
}
return type + ":" + String(value);
}
function fillMustache(str, itemEval) {
return str.replace(/\{\{\s*([^}]+?)\s*\}\}|\{([^{}]+)\}/g, function (_, d, s) {
@@ -1152,6 +1252,18 @@ export const REACTIVE_RUNTIME = String.raw`
"data-for",
);
var keyExpression =
spec.key ||
unwrapForKey(
tpl.getAttribute(
"data-key",
),
);
template.removeAttribute(
"data-key",
);
var parent =
tpl.parentNode;
@@ -1168,6 +1280,7 @@ export const REACTIVE_RUNTIME = String.raw`
parent.removeChild(tpl);
var clones = [];
var keyedRecords = new Map();
reactive(function () {
var list =
@@ -1184,64 +1297,155 @@ export const REACTIVE_RUNTIME = String.raw`
list = [];
}
for (
var cloneIndex = 0;
cloneIndex <
clones.length;
cloneIndex++
) {
var existingClone =
clones[cloneIndex];
if (
existingClone.parentNode
if (!keyExpression) {
for (
var cloneIndex = 0;
cloneIndex <
clones.length;
cloneIndex++
) {
existingClone.parentNode
.removeChild(
existingClone,
);
var existingClone =
clones[cloneIndex];
if (
existingClone.parentNode
) {
existingClone.parentNode
.removeChild(
existingClone,
);
}
}
clones = [];
var fragment =
document.createDocumentFragment();
for (
var itemIndex = 0;
itemIndex < list.length;
itemIndex++
) {
var clone =
template.cloneNode(true);
var locals = {};
locals[spec.item] =
list[itemIndex];
if (spec.index) {
locals[spec.index] =
itemIndex;
}
hydrateItem(
clone,
locals,
);
fragment.appendChild(
clone,
);
clones.push(clone);
}
parent.insertBefore(
fragment,
marker.nextSibling,
);
return;
}
clones = [];
var fragment =
document.createDocumentFragment();
var nextRecords = new Map();
var orderedNodes = [];
for (
var itemIndex = 0;
itemIndex < list.length;
itemIndex++
var keyedIndex = 0;
keyedIndex < list.length;
keyedIndex++
) {
var clone =
template.cloneNode(true);
var keyedItem = list[keyedIndex];
var keyedLocals = {};
var locals = {};
keyedLocals[spec.item] = keyedItem;
if (spec.index) keyedLocals[spec.index] = keyedIndex;
locals[spec.item] =
list[itemIndex];
if (spec.index) {
locals[spec.index] =
itemIndex;
var rawKey;
try {
rawKey = evaluateExpression(
keyExpression,
function (name) {
return Object.prototype.hasOwnProperty.call(keyedLocals, name)
? keyedLocals[name]
: readScope(name);
},
);
} catch (error) {
reportDiagnostic(
"WRN-HYDRATE-KEY-001",
"Unable to evaluate data-for key '" + keyExpression + "'.",
el,
error,
);
rawKey = keyedIndex;
}
hydrateItem(
clone,
locals,
);
var normalizedKey = stableForKey(rawKey);
if (nextRecords.has(normalizedKey)) {
reportDiagnostic(
"WRN-HYDRATE-KEY-002",
"Duplicate data-for key '" + String(rawKey) + "'; falling back to its index.",
el,
{ key: rawKey, index: keyedIndex },
);
normalizedKey += ":index:" + keyedIndex;
}
fragment.appendChild(
clone,
);
var record = keyedRecords.get(normalizedKey);
if (
!record ||
record.item !== keyedItem ||
(spec.index && record.index !== keyedIndex)
) {
if (record && record.node.parentNode) {
record.node.parentNode.removeChild(record.node);
}
clones.push(clone);
var keyedClone = template.cloneNode(true);
hydrateItem(keyedClone, keyedLocals);
record = {
node: keyedClone,
item: keyedItem,
index: keyedIndex,
};
}
nextRecords.set(normalizedKey, record);
orderedNodes.push(record.node);
}
keyedRecords.forEach(function (record, key) {
if (!nextRecords.has(key) && record.node.parentNode) {
record.node.parentNode.removeChild(record.node);
}
});
var keyedFragment = document.createDocumentFragment();
orderedNodes.forEach(function (node) {
keyedFragment.appendChild(node);
});
parent.insertBefore(
fragment,
keyedFragment,
marker.nextSibling,
);
keyedRecords = nextRecords;
clones = orderedNodes;
});
});
@@ -1512,10 +1716,18 @@ export const REACTIVE_RUNTIME = String.raw`
});
});
var behavior = parseBehavior(el);
if (behavior) {
installBehaviorFunctions(behavior.functions);
(behavior.effects || []).forEach(function (source) {
if (typeof source !== "string" || !source.trim()) return;
reactive(function () {
try { runStmt(source); } catch (error) {
reportDiagnostic("WRN-EFFECT-ERROR", "Reactive effect failed.", el, error);
}
});
});
(behavior.watches || []).forEach(function (watch) {
if (!watch || typeof watch.state !== "string" || typeof watch.body !== "string") return;
if (!stateWatchers[watch.state]) stateWatchers[watch.state] = [];
@@ -1609,6 +1821,73 @@ export const REACTIVE_RUNTIME = String.raw`
behaviorObserver.observe(document.documentElement, { childList: true, subtree: true });
}
function queueScopeHydration(element) {
if (!element || element.__wrnexusScope || element.__wrnexusHydrationQueued) return;
var runtime = element.getAttribute("data-wrn-runtime") || "universal";
var strategy = element.getAttribute("data-wrn-hydrate") || "load";
if (runtime === "server" || strategy === "none") return;
element.__wrnexusHydrationQueued = true;
function hydrate() {
if (element.__wrnexusScope || !element.isConnected) return;
setupScope(element);
}
if (strategy === "load") {
hydrate();
return;
}
if (strategy === "idle") {
var idle = window.requestIdleCallback || function (callback) { return window.setTimeout(callback, 1); };
idle(hydrate);
return;
}
if (strategy === "visible" && typeof IntersectionObserver !== "undefined") {
var observer = new IntersectionObserver(function (entries) {
if (!entries.some(function (entry) { return entry.isIntersecting; })) return;
observer.disconnect();
hydrate();
});
observer.observe(element);
return;
}
if (strategy === "interaction") {
var activate = function () {
element.removeEventListener("pointerdown", activate, true);
element.removeEventListener("keydown", activate, true);
element.removeEventListener("focusin", activate, true);
hydrate();
};
element.addEventListener("pointerdown", activate, true);
element.addEventListener("keydown", activate, true);
element.addEventListener("focusin", activate, true);
return;
}
if (strategy.indexOf("media:") === 0 && typeof window.matchMedia === "function") {
var query = strategy.slice(6);
var media = window.matchMedia(query);
if (media.matches) hydrate();
else {
var onChange = function (event) {
if (!event.matches) return;
if (media.removeEventListener) media.removeEventListener("change", onChange);
else media.removeListener(onChange);
hydrate();
};
if (media.addEventListener) media.addEventListener("change", onChange);
else media.addListener(onChange);
}
return;
}
reportDiagnostic(
"WRN-HYDRATE-STRATEGY",
"Unknown hydration strategy '" + strategy + "'. Falling back to load.",
element,
);
hydrate();
}
function hydrateScopes(root) {
var host = root || document;
@@ -1620,13 +1899,13 @@ export const REACTIVE_RUNTIME = String.raw`
host.matches &&
host.matches(scopeSelector)
) {
setupScope(host);
queueScopeHydration(host);
}
if (host.querySelectorAll) {
host
.querySelectorAll(scopeSelector)
.forEach(setupScope);
.forEach(queueScopeHydration);
}
ensureBehaviorObserver();