@zenithbuild/runtime 0.1.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dom-hydration.d.ts +44 -0
- package/dist/dom-hydration.d.ts.map +1 -0
- package/dist/dom-hydration.js +272 -0
- package/dist/dom-hydration.js.map +1 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +505 -0
- package/dist/index.js.map +1 -0
- package/dist/thin-runtime.d.ts +24 -0
- package/dist/thin-runtime.d.ts.map +1 -0
- package/dist/thin-runtime.js +159 -0
- package/dist/thin-runtime.js.map +1 -0
- package/package.json +6 -2
- package/src/index.ts +81 -46
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin Runtime
|
|
3
|
+
*
|
|
4
|
+
* Phase 8/9/10: Declarative runtime for DOM updates and event binding
|
|
5
|
+
*
|
|
6
|
+
* This runtime is purely declarative - it:
|
|
7
|
+
* - Updates DOM nodes by ID
|
|
8
|
+
* - Binds event handlers
|
|
9
|
+
* - Reacts to state changes
|
|
10
|
+
* - Does NOT parse templates or expressions
|
|
11
|
+
* - Does NOT use eval, new Function, or with(window)
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Generate thin declarative runtime code
|
|
15
|
+
*
|
|
16
|
+
* This runtime is minimal and safe - it only:
|
|
17
|
+
* 1. Updates DOM nodes using pre-compiled expression functions
|
|
18
|
+
* 2. Binds event handlers by ID
|
|
19
|
+
* 3. Provides reactive state updates
|
|
20
|
+
*
|
|
21
|
+
* All expressions are pre-compiled at build time.
|
|
22
|
+
*/
|
|
23
|
+
export function generateThinRuntime() {
|
|
24
|
+
return `
|
|
25
|
+
// Zenith Thin Runtime (Phase 8/9/10)
|
|
26
|
+
// Purely declarative - no template parsing, no eval, no with(window)
|
|
27
|
+
|
|
28
|
+
(function() {
|
|
29
|
+
'use strict';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Update a single DOM node with expression result
|
|
33
|
+
* Node is identified by data-zen-text or data-zen-attr-* attribute
|
|
34
|
+
*/
|
|
35
|
+
function updateNode(node, expressionId, state, loaderData, props, stores) {
|
|
36
|
+
const expression = window.__ZENITH_EXPRESSIONS__.get(expressionId);
|
|
37
|
+
if (!expression) {
|
|
38
|
+
console.warn('[Zenith] Expression not found:', expressionId);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const result = expression(state, loaderData, props, stores);
|
|
44
|
+
|
|
45
|
+
// Update node based on attribute type
|
|
46
|
+
if (node.hasAttribute('data-zen-text')) {
|
|
47
|
+
// Text node update
|
|
48
|
+
if (result === null || result === undefined || result === false) {
|
|
49
|
+
node.textContent = '';
|
|
50
|
+
} else {
|
|
51
|
+
node.textContent = String(result);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
// Attribute update - determine attribute name from data-zen-attr-*
|
|
55
|
+
const attrMatch = Array.from(node.attributes)
|
|
56
|
+
.find(attr => attr.name.startsWith('data-zen-attr-'));
|
|
57
|
+
|
|
58
|
+
if (attrMatch) {
|
|
59
|
+
const attrName = attrMatch.name.replace('data-zen-attr-', '');
|
|
60
|
+
|
|
61
|
+
if (attrName === 'class' || attrName === 'className') {
|
|
62
|
+
node.className = String(result ?? '');
|
|
63
|
+
} else if (attrName === 'style') {
|
|
64
|
+
if (typeof result === 'string') {
|
|
65
|
+
node.setAttribute('style', result);
|
|
66
|
+
}
|
|
67
|
+
} else if (attrName === 'disabled' || attrName === 'checked') {
|
|
68
|
+
if (result) {
|
|
69
|
+
node.setAttribute(attrName, '');
|
|
70
|
+
} else {
|
|
71
|
+
node.removeAttribute(attrName);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
if (result != null && result !== false) {
|
|
75
|
+
node.setAttribute(attrName, String(result));
|
|
76
|
+
} else {
|
|
77
|
+
node.removeAttribute(attrName);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('[Zenith] Error updating node:', expressionId, error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Update all hydrated nodes
|
|
89
|
+
* Called when state changes
|
|
90
|
+
*/
|
|
91
|
+
function updateAll(state, loaderData, props, stores) {
|
|
92
|
+
// Find all nodes with hydration markers
|
|
93
|
+
const textNodes = document.querySelectorAll('[data-zen-text]');
|
|
94
|
+
const attrNodes = document.querySelectorAll('[data-zen-attr-class], [data-zen-attr-style], [data-zen-attr-src], [data-zen-attr-href]');
|
|
95
|
+
|
|
96
|
+
textNodes.forEach(node => {
|
|
97
|
+
const expressionId = node.getAttribute('data-zen-text');
|
|
98
|
+
if (expressionId) {
|
|
99
|
+
updateNode(node, expressionId, state, loaderData, props, stores);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
attrNodes.forEach(node => {
|
|
104
|
+
const attrMatch = Array.from(node.attributes)
|
|
105
|
+
.find(attr => attr.name.startsWith('data-zen-attr-'));
|
|
106
|
+
if (attrMatch) {
|
|
107
|
+
const expressionId = attrMatch.value;
|
|
108
|
+
if (expressionId) {
|
|
109
|
+
updateNode(node, expressionId, state, loaderData, props, stores);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Bind event handlers
|
|
117
|
+
* Handlers are pre-compiled and registered on window
|
|
118
|
+
*/
|
|
119
|
+
function bindEvents(container) {
|
|
120
|
+
container = container || document;
|
|
121
|
+
|
|
122
|
+
const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown', 'mouseenter'];
|
|
123
|
+
|
|
124
|
+
eventTypes.forEach(eventType => {
|
|
125
|
+
const elements = container.querySelectorAll('[data-zen-' + eventType + ']');
|
|
126
|
+
elements.forEach(element => {
|
|
127
|
+
const handlerName = element.getAttribute('data-zen-' + eventType);
|
|
128
|
+
if (!handlerName) return;
|
|
129
|
+
|
|
130
|
+
// Remove existing handler
|
|
131
|
+
const handlerKey = '__zen_' + eventType + '_handler';
|
|
132
|
+
const existingHandler = element[handlerKey];
|
|
133
|
+
if (existingHandler) {
|
|
134
|
+
element.removeEventListener(eventType, existingHandler);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Bind new handler (pre-compiled, registered on window)
|
|
138
|
+
const handler = function(event) {
|
|
139
|
+
const handlerFunc = window[handlerName];
|
|
140
|
+
if (typeof handlerFunc === 'function') {
|
|
141
|
+
handlerFunc(event, element);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
element[handlerKey] = handler;
|
|
146
|
+
element.addEventListener(eventType, handler);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Export to window
|
|
152
|
+
if (typeof window !== 'undefined') {
|
|
153
|
+
window.__zenith_updateAll = updateAll;
|
|
154
|
+
window.__zenith_bindEvents = bindEvents;
|
|
155
|
+
}
|
|
156
|
+
})();
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=thin-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thin-runtime.js","sourceRoot":"","sources":["../src/thin-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB;IAC/B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqIV,CAAA;AACD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenithbuild/runtime",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -10,10 +10,14 @@
|
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
13
17
|
"scripts": {
|
|
14
18
|
"build": "tsc"
|
|
15
19
|
},
|
|
16
20
|
"devDependencies": {
|
|
17
21
|
"typescript": "^5.3.3"
|
|
18
22
|
}
|
|
19
|
-
}
|
|
23
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
// Zenith Runtime
|
|
2
2
|
// Ported from hydration_runtime.js
|
|
3
3
|
|
|
4
|
+
// Exported Types
|
|
5
|
+
export type Signal<T> = ((v?: T) => T) & { _isSignal: true };
|
|
6
|
+
export type Memo<T> = Signal<T>;
|
|
7
|
+
export type Ref<T> = { current: T };
|
|
8
|
+
export type EffectFn = () => (void | (() => void));
|
|
9
|
+
export type DisposeFn = () => void;
|
|
10
|
+
export type Subscriber = { run: () => void; dependencies: Set<Set<any>>; isRunning: boolean };
|
|
11
|
+
export type TrackingContext = { subscriber: Subscriber };
|
|
12
|
+
|
|
13
|
+
export type MountCallback = () => void;
|
|
14
|
+
export type UnmountCallback = () => void;
|
|
15
|
+
|
|
4
16
|
// Global types for internal use
|
|
5
17
|
declare global {
|
|
6
18
|
interface Window {
|
|
@@ -28,15 +40,60 @@ declare global {
|
|
|
28
40
|
|
|
29
41
|
// Internal reactivity state
|
|
30
42
|
let cE: any = null;
|
|
31
|
-
|
|
43
|
+
let cS: any[] = [];
|
|
32
44
|
let bD = 0;
|
|
45
|
+
let isFlushing = false;
|
|
46
|
+
let flushScheduled = false;
|
|
33
47
|
const pE = new Set<any>();
|
|
48
|
+
|
|
49
|
+
// Lifecycle State
|
|
50
|
+
let isMounted = false;
|
|
51
|
+
const unmountCallbacks = new Set<() => void>();
|
|
52
|
+
|
|
53
|
+
// Public Reactivity Utilities
|
|
54
|
+
export const trackDependency = (s: Set<any>) => { if (cE) { s.add(cE); cE.dependencies.add(s); } };
|
|
55
|
+
export const notifySubscribers = (s: Set<any> | undefined) => {
|
|
56
|
+
if (!s) return;
|
|
57
|
+
const es = Array.from(s);
|
|
58
|
+
for (const e of es) {
|
|
59
|
+
if (e.isRunning) continue;
|
|
60
|
+
if (bD > 0 || isFlushing) pE.add(e);
|
|
61
|
+
else e.run();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
export const getCurrentContext = () => cE;
|
|
65
|
+
export const pushContext = (e: any) => { cS.push(cE); cE = e; };
|
|
66
|
+
export const popContext = () => { cE = cS.pop(); };
|
|
67
|
+
export const cleanupContext = (e: any) => { for (const d of e.dependencies) d.delete(e); e.dependencies.clear(); };
|
|
68
|
+
export const startBatch = () => { bD++; };
|
|
69
|
+
export const endBatch = () => { bD--; if (bD === 0) flushEffects(); };
|
|
70
|
+
export const isBatching = () => bD > 0;
|
|
71
|
+
export const runUntracked = <T>(fn: () => T): T => {
|
|
72
|
+
pushContext(null);
|
|
73
|
+
try { return fn(); } finally { popContext(); }
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Public Lifecycle Utilities
|
|
77
|
+
export const triggerMount = () => { isMounted = true; };
|
|
78
|
+
export const triggerUnmount = () => {
|
|
79
|
+
isMounted = false;
|
|
80
|
+
executeUnmountCallbacks();
|
|
81
|
+
};
|
|
82
|
+
export const executeUnmountCallbacks = () => {
|
|
83
|
+
for (const cb of unmountCallbacks) {
|
|
84
|
+
try { cb(); } catch (e) { console.error('Error in unmount callback:', e); }
|
|
85
|
+
}
|
|
86
|
+
unmountCallbacks.clear();
|
|
87
|
+
};
|
|
88
|
+
export const getIsMounted = () => isMounted;
|
|
89
|
+
export const getUnmountCallbackCount = () => unmountCallbacks.size;
|
|
90
|
+
export const resetMountState = () => { isMounted = false; };
|
|
91
|
+
export const resetUnmountState = () => { unmountCallbacks.clear(); };
|
|
92
|
+
|
|
34
93
|
if (typeof window !== 'undefined') {
|
|
35
94
|
window.__ZENITH_EXPRESSIONS__ = new Map();
|
|
36
95
|
window.__ZENITH_SCOPES__ = {};
|
|
37
96
|
}
|
|
38
|
-
let isFlushing = false;
|
|
39
|
-
let flushScheduled = false;
|
|
40
97
|
|
|
41
98
|
// Phase A3: Post-Mount Execution Hook
|
|
42
99
|
const mountedScopes = new Set<string>();
|
|
@@ -53,9 +110,12 @@ export function mountComponent(scopeId: string) {
|
|
|
53
110
|
}
|
|
54
111
|
}
|
|
55
112
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
113
|
+
// Internal shorthand helpers (matching original compiled code requirements if any)
|
|
114
|
+
const pC = pushContext;
|
|
115
|
+
const oC = popContext;
|
|
116
|
+
const tD = trackDependency;
|
|
117
|
+
const nS = notifySubscribers;
|
|
118
|
+
const cEf = cleanupContext;
|
|
59
119
|
|
|
60
120
|
export function zenRoute() {
|
|
61
121
|
if (typeof window === 'undefined') return { path: '/', slugs: [] };
|
|
@@ -66,16 +126,6 @@ export function zenRoute() {
|
|
|
66
126
|
};
|
|
67
127
|
}
|
|
68
128
|
|
|
69
|
-
function nS(s: Set<any> | undefined) {
|
|
70
|
-
if (!s) return;
|
|
71
|
-
const es = Array.from(s);
|
|
72
|
-
for (const e of es) {
|
|
73
|
-
if (e.isRunning) continue;
|
|
74
|
-
if (bD > 0 || isFlushing) pE.add(e);
|
|
75
|
-
else e.run();
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
129
|
function scheduleFlush() {
|
|
80
130
|
if (flushScheduled) return;
|
|
81
131
|
flushScheduled = true;
|
|
@@ -101,8 +151,6 @@ function flushEffects() {
|
|
|
101
151
|
}
|
|
102
152
|
}
|
|
103
153
|
|
|
104
|
-
function cEf(e: any) { for (const d of e.dependencies) d.delete(e); e.dependencies.clear(); }
|
|
105
|
-
|
|
106
154
|
export const signal = function (v: any) {
|
|
107
155
|
const s = new Set<any>();
|
|
108
156
|
function sig(nV?: any) {
|
|
@@ -190,21 +238,27 @@ export const memo = function (fn: () => any) {
|
|
|
190
238
|
};
|
|
191
239
|
|
|
192
240
|
export const batch = function (fn: () => void) {
|
|
193
|
-
|
|
194
|
-
try { fn(); } finally {
|
|
195
|
-
bD--;
|
|
196
|
-
if (bD === 0) flushEffects();
|
|
197
|
-
}
|
|
241
|
+
startBatch();
|
|
242
|
+
try { fn(); } finally { endBatch(); }
|
|
198
243
|
};
|
|
199
244
|
|
|
200
245
|
export const untrack = function (fn: () => any) {
|
|
201
|
-
|
|
202
|
-
try { return fn(); } finally { oC(); }
|
|
246
|
+
return runUntracked(fn);
|
|
203
247
|
};
|
|
204
248
|
|
|
205
249
|
export const ref = (i: any) => ({ current: i || null });
|
|
206
|
-
|
|
207
|
-
export const
|
|
250
|
+
|
|
251
|
+
export const onMount = (cb: () => void) => {
|
|
252
|
+
if (typeof window !== 'undefined' && window.__zenith && window.__zenith.activeInstance) {
|
|
253
|
+
window.__zenith.activeInstance.mountHooks.push(cb);
|
|
254
|
+
} else {
|
|
255
|
+
// Fallback for non-component context or SSR
|
|
256
|
+
if (isMounted) cb();
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
export const onUnmount = (cb: () => void) => {
|
|
260
|
+
unmountCallbacks.add(cb);
|
|
261
|
+
};
|
|
208
262
|
|
|
209
263
|
// DOM Helper (hC)
|
|
210
264
|
function hC(parent: Node, child: any) {
|
|
@@ -295,24 +349,6 @@ export function hydrate(state: any, container: Element | Document = document, lo
|
|
|
295
349
|
oldTitle = document.createElement('title');
|
|
296
350
|
headMount.appendChild(oldTitle);
|
|
297
351
|
}
|
|
298
|
-
const resolveContent = (children: any): string => {
|
|
299
|
-
let result = '';
|
|
300
|
-
(Array.isArray(children) ? children : [children]).forEach(child => {
|
|
301
|
-
if (child == null || child === false) return;
|
|
302
|
-
if (typeof child === 'function') {
|
|
303
|
-
const val = child();
|
|
304
|
-
if (val != null && val !== false) result += String(val);
|
|
305
|
-
} else if (typeof child === 'object' && child.fn) {
|
|
306
|
-
const val = child.fn();
|
|
307
|
-
if (val != null && val !== false) result += String(val);
|
|
308
|
-
} else if (child instanceof Node) {
|
|
309
|
-
result += child.textContent || '';
|
|
310
|
-
} else {
|
|
311
|
-
result += String(child);
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
return result;
|
|
315
|
-
};
|
|
316
352
|
const titleContent = newTitle.childNodes.length > 0
|
|
317
353
|
? Array.from(newTitle.childNodes).map((n: any) => n.textContent).join('')
|
|
318
354
|
: '';
|
|
@@ -407,7 +443,6 @@ export function h(tag: string, props: any, children: any) {
|
|
|
407
443
|
if (v && typeof v === 'object' && (v as any).fn) fn = (v as any).fn;
|
|
408
444
|
if (typeof fn === 'function') {
|
|
409
445
|
el.addEventListener(k.slice(2).toLowerCase(), (e) => {
|
|
410
|
-
// Fix: this binding via call(el, e, el)
|
|
411
446
|
const h = (fn as Function).call(el, e, el);
|
|
412
447
|
if (typeof h === 'function') h.call(el, e, el);
|
|
413
448
|
});
|
package/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"outDir": "./dist",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true
|
|
12
|
-
},
|
|
13
|
-
"include": [
|
|
14
|
-
"src/**/*"
|
|
15
|
-
]
|
|
16
|
-
}
|