@zenithbuild/compiler 1.0.2
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/LICENSE +21 -0
- package/README.md +30 -0
- package/dist/build-analyzer.d.ts +44 -0
- package/dist/build-analyzer.js +87 -0
- package/dist/bundler.d.ts +31 -0
- package/dist/bundler.js +86 -0
- package/dist/core/components/index.d.ts +9 -0
- package/dist/core/components/index.js +13 -0
- package/dist/core/config/index.d.ts +11 -0
- package/dist/core/config/index.js +10 -0
- package/dist/core/config/loader.d.ts +17 -0
- package/dist/core/config/loader.js +60 -0
- package/dist/core/config/types.d.ts +98 -0
- package/dist/core/config/types.js +32 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +6 -0
- package/dist/core/lifecycle/index.d.ts +16 -0
- package/dist/core/lifecycle/index.js +19 -0
- package/dist/core/lifecycle/zen-mount.d.ts +66 -0
- package/dist/core/lifecycle/zen-mount.js +151 -0
- package/dist/core/lifecycle/zen-unmount.d.ts +54 -0
- package/dist/core/lifecycle/zen-unmount.js +76 -0
- package/dist/core/plugins/bridge.d.ts +116 -0
- package/dist/core/plugins/bridge.js +121 -0
- package/dist/core/plugins/index.d.ts +6 -0
- package/dist/core/plugins/index.js +6 -0
- package/dist/core/plugins/registry.d.ts +67 -0
- package/dist/core/plugins/registry.js +113 -0
- package/dist/core/reactivity/index.d.ts +30 -0
- package/dist/core/reactivity/index.js +33 -0
- package/dist/core/reactivity/tracking.d.ts +74 -0
- package/dist/core/reactivity/tracking.js +136 -0
- package/dist/core/reactivity/zen-batch.d.ts +45 -0
- package/dist/core/reactivity/zen-batch.js +54 -0
- package/dist/core/reactivity/zen-effect.d.ts +48 -0
- package/dist/core/reactivity/zen-effect.js +98 -0
- package/dist/core/reactivity/zen-memo.d.ts +43 -0
- package/dist/core/reactivity/zen-memo.js +100 -0
- package/dist/core/reactivity/zen-ref.d.ts +44 -0
- package/dist/core/reactivity/zen-ref.js +34 -0
- package/dist/core/reactivity/zen-signal.d.ts +48 -0
- package/dist/core/reactivity/zen-signal.js +84 -0
- package/dist/core/reactivity/zen-state.d.ts +35 -0
- package/dist/core/reactivity/zen-state.js +147 -0
- package/dist/core/reactivity/zen-untrack.d.ts +38 -0
- package/dist/core/reactivity/zen-untrack.js +41 -0
- package/dist/css/index.d.ts +73 -0
- package/dist/css/index.js +246 -0
- package/dist/discovery/componentDiscovery.d.ts +42 -0
- package/dist/discovery/componentDiscovery.js +56 -0
- package/dist/discovery/layouts.d.ts +13 -0
- package/dist/discovery/layouts.js +41 -0
- package/dist/errors/compilerError.d.ts +31 -0
- package/dist/errors/compilerError.js +51 -0
- package/dist/finalize/finalizeOutput.d.ts +32 -0
- package/dist/finalize/finalizeOutput.js +62 -0
- package/dist/finalize/generateFinalBundle.d.ts +24 -0
- package/dist/finalize/generateFinalBundle.js +68 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +51 -0
- package/dist/ir/types.d.ts +181 -0
- package/dist/ir/types.js +8 -0
- package/dist/output/types.d.ts +30 -0
- package/dist/output/types.js +6 -0
- package/dist/parse/detectMapExpressions.d.ts +45 -0
- package/dist/parse/detectMapExpressions.js +77 -0
- package/dist/parse/parseScript.d.ts +8 -0
- package/dist/parse/parseScript.js +36 -0
- package/dist/parse/parseTemplate.d.ts +11 -0
- package/dist/parse/parseTemplate.js +487 -0
- package/dist/parse/parseZenFile.d.ts +11 -0
- package/dist/parse/parseZenFile.js +50 -0
- package/dist/parse/scriptAnalysis.d.ts +25 -0
- package/dist/parse/scriptAnalysis.js +60 -0
- package/dist/parse/trackLoopContext.d.ts +20 -0
- package/dist/parse/trackLoopContext.js +62 -0
- package/dist/parseZenFile.d.ts +10 -0
- package/dist/parseZenFile.js +55 -0
- package/dist/runtime/analyzeAndEmit.d.ts +20 -0
- package/dist/runtime/analyzeAndEmit.js +70 -0
- package/dist/runtime/build.d.ts +6 -0
- package/dist/runtime/build.js +13 -0
- package/dist/runtime/bundle-generator.d.ts +27 -0
- package/dist/runtime/bundle-generator.js +1263 -0
- package/dist/runtime/client-runtime.d.ts +41 -0
- package/dist/runtime/client-runtime.js +397 -0
- package/dist/runtime/dataExposure.d.ts +52 -0
- package/dist/runtime/dataExposure.js +227 -0
- package/dist/runtime/generateDOM.d.ts +21 -0
- package/dist/runtime/generateDOM.js +194 -0
- package/dist/runtime/generateHydrationBundle.d.ts +15 -0
- package/dist/runtime/generateHydrationBundle.js +399 -0
- package/dist/runtime/hydration.d.ts +53 -0
- package/dist/runtime/hydration.js +271 -0
- package/dist/runtime/navigation.d.ts +58 -0
- package/dist/runtime/navigation.js +372 -0
- package/dist/runtime/serve.d.ts +13 -0
- package/dist/runtime/serve.js +76 -0
- package/dist/runtime/thinRuntime.d.ts +23 -0
- package/dist/runtime/thinRuntime.js +158 -0
- package/dist/runtime/transformIR.d.ts +19 -0
- package/dist/runtime/transformIR.js +285 -0
- package/dist/runtime/wrapExpression.d.ts +24 -0
- package/dist/runtime/wrapExpression.js +76 -0
- package/dist/runtime/wrapExpressionWithLoop.d.ts +17 -0
- package/dist/runtime/wrapExpressionWithLoop.js +75 -0
- package/dist/spa-build.d.ts +26 -0
- package/dist/spa-build.js +866 -0
- package/dist/ssg-build.d.ts +32 -0
- package/dist/ssg-build.js +408 -0
- package/dist/test/analyze-emit.test.d.ts +1 -0
- package/dist/test/analyze-emit.test.js +88 -0
- package/dist/test/bundler-contract.test.d.ts +1 -0
- package/dist/test/bundler-contract.test.js +137 -0
- package/dist/test/compiler-authority.test.d.ts +1 -0
- package/dist/test/compiler-authority.test.js +90 -0
- package/dist/test/component-instance-test.d.ts +1 -0
- package/dist/test/component-instance-test.js +115 -0
- package/dist/test/error-native-bridge.test.d.ts +1 -0
- package/dist/test/error-native-bridge.test.js +51 -0
- package/dist/test/error-serialization.test.d.ts +1 -0
- package/dist/test/error-serialization.test.js +38 -0
- package/dist/test/macro-inlining.test.d.ts +1 -0
- package/dist/test/macro-inlining.test.js +178 -0
- package/dist/test/validate-test.d.ts +6 -0
- package/dist/test/validate-test.js +95 -0
- package/dist/transform/classifyExpression.d.ts +46 -0
- package/dist/transform/classifyExpression.js +354 -0
- package/dist/transform/componentResolver.d.ts +15 -0
- package/dist/transform/componentResolver.js +30 -0
- package/dist/transform/expressionTransformer.d.ts +19 -0
- package/dist/transform/expressionTransformer.js +333 -0
- package/dist/transform/fragmentLowering.d.ts +25 -0
- package/dist/transform/fragmentLowering.js +468 -0
- package/dist/transform/layoutProcessor.d.ts +5 -0
- package/dist/transform/layoutProcessor.js +34 -0
- package/dist/transform/transformTemplate.d.ts +11 -0
- package/dist/transform/transformTemplate.js +33 -0
- package/dist/validate/invariants.d.ts +23 -0
- package/dist/validate/invariants.js +55 -0
- package/native/compiler-native/compiler-native.node +0 -0
- package/native/compiler-native/index.d.ts +113 -0
- package/native/compiler-native/index.js +19 -0
- package/native/compiler-native/package.json +19 -0
- package/package.json +49 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zenith Development Server
|
|
3
|
+
*
|
|
4
|
+
* SPA-compatible server that:
|
|
5
|
+
* - Serves static assets directly (js, css, ico, images)
|
|
6
|
+
* - Serves index.html for all other routes (SPA fallback)
|
|
7
|
+
*
|
|
8
|
+
* This enables client-side routing to work on:
|
|
9
|
+
* - Direct URL entry
|
|
10
|
+
* - Hard refresh
|
|
11
|
+
* - Back/forward navigation
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zenith Development Server
|
|
3
|
+
*
|
|
4
|
+
* SPA-compatible server that:
|
|
5
|
+
* - Serves static assets directly (js, css, ico, images)
|
|
6
|
+
* - Serves index.html for all other routes (SPA fallback)
|
|
7
|
+
*
|
|
8
|
+
* This enables client-side routing to work on:
|
|
9
|
+
* - Direct URL entry
|
|
10
|
+
* - Hard refresh
|
|
11
|
+
* - Back/forward navigation
|
|
12
|
+
*/
|
|
13
|
+
import { serve } from "bun";
|
|
14
|
+
import path from "path";
|
|
15
|
+
const distDir = path.resolve(import.meta.dir, "..", "app", "dist");
|
|
16
|
+
// File extensions that should be served as static assets
|
|
17
|
+
const STATIC_EXTENSIONS = new Set([
|
|
18
|
+
".js",
|
|
19
|
+
".css",
|
|
20
|
+
".ico",
|
|
21
|
+
".png",
|
|
22
|
+
".jpg",
|
|
23
|
+
".jpeg",
|
|
24
|
+
".gif",
|
|
25
|
+
".svg",
|
|
26
|
+
".webp",
|
|
27
|
+
".woff",
|
|
28
|
+
".woff2",
|
|
29
|
+
".ttf",
|
|
30
|
+
".eot",
|
|
31
|
+
".json",
|
|
32
|
+
".map"
|
|
33
|
+
]);
|
|
34
|
+
serve({
|
|
35
|
+
port: 3000,
|
|
36
|
+
async fetch(req) {
|
|
37
|
+
const url = new URL(req.url);
|
|
38
|
+
const pathname = url.pathname;
|
|
39
|
+
// Get file extension
|
|
40
|
+
const ext = path.extname(pathname).toLowerCase();
|
|
41
|
+
// Check if this is a static asset request
|
|
42
|
+
if (STATIC_EXTENSIONS.has(ext)) {
|
|
43
|
+
const filePath = path.join(distDir, pathname);
|
|
44
|
+
const file = Bun.file(filePath);
|
|
45
|
+
// Check if file exists
|
|
46
|
+
if (await file.exists()) {
|
|
47
|
+
return new Response(file);
|
|
48
|
+
}
|
|
49
|
+
// Static file not found
|
|
50
|
+
return new Response("Not found", { status: 404 });
|
|
51
|
+
}
|
|
52
|
+
// For all other routes, serve index.html (SPA fallback)
|
|
53
|
+
const indexPath = path.join(distDir, "index.html");
|
|
54
|
+
const indexFile = Bun.file(indexPath);
|
|
55
|
+
if (await indexFile.exists()) {
|
|
56
|
+
return new Response(indexFile, {
|
|
57
|
+
headers: {
|
|
58
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// No index.html found - likely need to run build first
|
|
63
|
+
return new Response(`<html>
|
|
64
|
+
<head><title>Zenith - Build Required</title></head>
|
|
65
|
+
<body style="font-family: system-ui; padding: 2rem; text-align: center;">
|
|
66
|
+
<h1>Build Required</h1>
|
|
67
|
+
<p>Run <code>bun runtime/build.ts</code> first to compile the pages.</p>
|
|
68
|
+
</body>
|
|
69
|
+
</html>`, {
|
|
70
|
+
status: 500,
|
|
71
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
console.log("🚀 Zenith dev server running at http://localhost:3000");
|
|
76
|
+
console.log(" SPA mode: All routes serve index.html");
|
|
@@ -0,0 +1,23 @@
|
|
|
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 declare function generateThinRuntime(): string;
|
|
@@ -0,0 +1,158 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform IR to Runtime Code
|
|
3
|
+
*
|
|
4
|
+
* Phase 4: Transform ZenIR into runtime-ready JavaScript code with full reactivity
|
|
5
|
+
*/
|
|
6
|
+
import type { ZenIR } from '../ir/types';
|
|
7
|
+
export interface RuntimeCode {
|
|
8
|
+
expressions: string;
|
|
9
|
+
render: string;
|
|
10
|
+
hydration: string;
|
|
11
|
+
styles: string;
|
|
12
|
+
script: string;
|
|
13
|
+
stateInit: string;
|
|
14
|
+
bundle: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Transform ZenIR into runtime JavaScript code
|
|
18
|
+
*/
|
|
19
|
+
export declare function transformIR(ir: ZenIR): RuntimeCode;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform IR to Runtime Code
|
|
3
|
+
*
|
|
4
|
+
* Phase 4: Transform ZenIR into runtime-ready JavaScript code with full reactivity
|
|
5
|
+
*/
|
|
6
|
+
import { generateExpressionWrappers } from './wrapExpression';
|
|
7
|
+
import { generateDOMFunction } from './generateDOM';
|
|
8
|
+
import { generateHydrationRuntime, generateExpressionRegistry } from './generateHydrationBundle';
|
|
9
|
+
import { analyzeAllExpressions } from './dataExposure';
|
|
10
|
+
import { generateNavigationRuntime } from './navigation';
|
|
11
|
+
import { extractStateDeclarations, extractProps, transformStateDeclarations } from '../parse/scriptAnalysis';
|
|
12
|
+
/**
|
|
13
|
+
* Transform ZenIR into runtime JavaScript code
|
|
14
|
+
*/
|
|
15
|
+
export function transformIR(ir) {
|
|
16
|
+
// Phase 6: Analyze expression dependencies for explicit data exposure
|
|
17
|
+
const expressionDependencies = analyzeAllExpressions(ir.template.expressions, ir.filePath, [], // declaredLoaderProps
|
|
18
|
+
ir.script?.attributes['props'] ? ir.script.attributes['props'].split(',') : [], // declaredProps
|
|
19
|
+
[] // declaredStores
|
|
20
|
+
);
|
|
21
|
+
// Generate expression wrappers with dependencies
|
|
22
|
+
const expressions = generateExpressionWrappers(ir.template.expressions, expressionDependencies);
|
|
23
|
+
// Generate DOM creation code
|
|
24
|
+
const renderFunction = generateDOMFunction(ir.template.nodes, ir.template.expressions, 'renderDynamicPage');
|
|
25
|
+
// Generate hydrate function (legacy, for reference)
|
|
26
|
+
const hydrateFunction = generateHydrateFunction();
|
|
27
|
+
// Generate Phase 5 hydration runtime
|
|
28
|
+
const hydrationRuntime = generateHydrationRuntime();
|
|
29
|
+
// Generate Phase 7 navigation runtime
|
|
30
|
+
const navigationRuntime = generateNavigationRuntime();
|
|
31
|
+
// Generate expression registry initialization
|
|
32
|
+
const expressionRegistry = generateExpressionRegistry(ir.template.expressions);
|
|
33
|
+
// Generate style injection code
|
|
34
|
+
const stylesCode = generateStyleInjection(ir.styles);
|
|
35
|
+
// Extract state and prop declarations
|
|
36
|
+
const scriptContent = ir.script?.raw || '';
|
|
37
|
+
const stateDeclarations = extractStateDeclarations(scriptContent);
|
|
38
|
+
const propKeys = Object.keys(ir.script?.attributes || {}).filter(k => k !== 'setup' && k !== 'lang');
|
|
39
|
+
const propDeclarations = extractProps(scriptContent);
|
|
40
|
+
const stateInitCode = generateStateInitialization(stateDeclarations, [...propDeclarations, ...propKeys]);
|
|
41
|
+
// Transform script (remove state and prop declarations, they're handled by runtime)
|
|
42
|
+
const scriptCode = transformStateDeclarations(scriptContent);
|
|
43
|
+
// Generate complete runtime bundle
|
|
44
|
+
const bundle = generateRuntimeBundle({
|
|
45
|
+
expressions,
|
|
46
|
+
expressionRegistry,
|
|
47
|
+
hydrationRuntime,
|
|
48
|
+
navigationRuntime,
|
|
49
|
+
stylesCode,
|
|
50
|
+
scriptCode,
|
|
51
|
+
stateInitCode
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
expressions,
|
|
55
|
+
render: renderFunction,
|
|
56
|
+
hydration: hydrationRuntime,
|
|
57
|
+
styles: stylesCode,
|
|
58
|
+
script: scriptCode,
|
|
59
|
+
stateInit: stateInitCode,
|
|
60
|
+
bundle
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Generate complete runtime bundle
|
|
65
|
+
*/
|
|
66
|
+
function generateRuntimeBundle(parts) {
|
|
67
|
+
// Extract function declarations from script code to register on window
|
|
68
|
+
const functionRegistrations = extractFunctionRegistrations(parts.scriptCode);
|
|
69
|
+
return `// Zenith Runtime Bundle (Phase 5)
|
|
70
|
+
// Generated at compile time - no .zen parsing in browser
|
|
71
|
+
|
|
72
|
+
${parts.expressions}
|
|
73
|
+
|
|
74
|
+
${parts.expressionRegistry}
|
|
75
|
+
|
|
76
|
+
${parts.hydrationRuntime}
|
|
77
|
+
|
|
78
|
+
${parts.navigationRuntime}
|
|
79
|
+
|
|
80
|
+
${parts.stylesCode ? `// Style injection
|
|
81
|
+
${parts.stylesCode}` : ''}
|
|
82
|
+
|
|
83
|
+
// User script code - executed first to define variables needed by state initialization
|
|
84
|
+
${parts.scriptCode ? parts.scriptCode : ''}
|
|
85
|
+
|
|
86
|
+
${functionRegistrations}
|
|
87
|
+
|
|
88
|
+
${parts.stateInitCode ? `// State initialization
|
|
89
|
+
${parts.stateInitCode}` : ''}
|
|
90
|
+
|
|
91
|
+
// Export hydration functions
|
|
92
|
+
if (typeof window !== 'undefined') {
|
|
93
|
+
window.zenithHydrate = window.__zenith_hydrate || function(state, container) {
|
|
94
|
+
console.warn('[Zenith] Hydration runtime not loaded');
|
|
95
|
+
};
|
|
96
|
+
window.zenithUpdate = window.__zenith_update || function(state) {
|
|
97
|
+
console.warn('[Zenith] Update runtime not loaded');
|
|
98
|
+
};
|
|
99
|
+
window.zenithBindEvents = window.__zenith_bindEvents || function(container) {
|
|
100
|
+
console.warn('[Zenith] Event binding runtime not loaded');
|
|
101
|
+
};
|
|
102
|
+
window.zenithCleanup = window.__zenith_cleanup || function(container) {
|
|
103
|
+
console.warn('[Zenith] Cleanup runtime not loaded');
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Auto-hydrate on page mount
|
|
108
|
+
(function() {
|
|
109
|
+
'use strict';
|
|
110
|
+
|
|
111
|
+
function autoHydrate() {
|
|
112
|
+
// Initialize state object
|
|
113
|
+
const state = window.__ZENITH_STATE__ || {};
|
|
114
|
+
|
|
115
|
+
// Run state initialization if defined
|
|
116
|
+
if (typeof initializeState === 'function') {
|
|
117
|
+
initializeState(state);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Store state globally
|
|
121
|
+
window.__ZENITH_STATE__ = state;
|
|
122
|
+
|
|
123
|
+
// Expose state variables on window with reactive getters/setters
|
|
124
|
+
// This allows user functions (like increment) to access state variables directly
|
|
125
|
+
for (const key in state) {
|
|
126
|
+
if (state.hasOwnProperty(key) && !window.hasOwnProperty(key)) {
|
|
127
|
+
Object.defineProperty(window, key, {
|
|
128
|
+
get: function() { return window.__ZENITH_STATE__[key]; },
|
|
129
|
+
set: function(value) {
|
|
130
|
+
window.__ZENITH_STATE__[key] = value;
|
|
131
|
+
// Trigger reactive update
|
|
132
|
+
if (window.__zenith_update) {
|
|
133
|
+
window.__zenith_update(window.__ZENITH_STATE__);
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
configurable: true
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Inject styles if defined
|
|
142
|
+
if (typeof injectStyles === 'function') {
|
|
143
|
+
injectStyles();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Get the router outlet or body
|
|
147
|
+
const container = document.querySelector('#app') || document.body;
|
|
148
|
+
|
|
149
|
+
// Hydrate with state
|
|
150
|
+
if (window.__zenith_hydrate) {
|
|
151
|
+
window.__zenith_hydrate(state, {}, {}, {}, container);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Run on DOM ready
|
|
156
|
+
if (document.readyState === 'loading') {
|
|
157
|
+
document.addEventListener('DOMContentLoaded', autoHydrate);
|
|
158
|
+
} else {
|
|
159
|
+
// DOM already loaded, hydrate immediately
|
|
160
|
+
autoHydrate();
|
|
161
|
+
}
|
|
162
|
+
})();
|
|
163
|
+
`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Extract function declarations and generate window registration code
|
|
167
|
+
*/
|
|
168
|
+
function extractFunctionRegistrations(scriptCode) {
|
|
169
|
+
if (!scriptCode)
|
|
170
|
+
return '';
|
|
171
|
+
// Match function declarations: function name(...) { ... }
|
|
172
|
+
const functionPattern = /function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
|
|
173
|
+
const functionNames = [];
|
|
174
|
+
let match;
|
|
175
|
+
while ((match = functionPattern.exec(scriptCode)) !== null) {
|
|
176
|
+
if (match[1]) {
|
|
177
|
+
functionNames.push(match[1]);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (functionNames.length === 0) {
|
|
181
|
+
return '';
|
|
182
|
+
}
|
|
183
|
+
// Generate window registration for each function
|
|
184
|
+
const registrations = functionNames.map(name => ` if (typeof ${name} === 'function') window.${name} = ${name};`).join('\n');
|
|
185
|
+
return `// Register functions on window for event handlers\n${registrations}`;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Generate hydrate function that mounts the DOM with reactivity
|
|
189
|
+
*/
|
|
190
|
+
function generateHydrateFunction() {
|
|
191
|
+
return `function hydrate(root, state) {
|
|
192
|
+
if (!root) {
|
|
193
|
+
// SSR fallback - return initial HTML string
|
|
194
|
+
console.warn('[Zenith] hydrate called without root element - SSR mode');
|
|
195
|
+
return '';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Clear root
|
|
199
|
+
root.innerHTML = '';
|
|
200
|
+
|
|
201
|
+
// Render template
|
|
202
|
+
const dom = renderDynamicPage(state);
|
|
203
|
+
|
|
204
|
+
// Append to root
|
|
205
|
+
if (dom instanceof DocumentFragment) {
|
|
206
|
+
root.appendChild(dom);
|
|
207
|
+
} else if (dom instanceof Node) {
|
|
208
|
+
root.appendChild(dom);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Bind event handlers
|
|
212
|
+
bindEventHandlers(root, state);
|
|
213
|
+
|
|
214
|
+
// Set up reactive updates (if state is reactive)
|
|
215
|
+
setupReactiveUpdates(root, state);
|
|
216
|
+
|
|
217
|
+
return root;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function bindEventHandlers(root, state) {
|
|
221
|
+
// Find all elements with data-zen-* event attributes
|
|
222
|
+
const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur'];
|
|
223
|
+
|
|
224
|
+
for (const eventType of eventTypes) {
|
|
225
|
+
const elements = root.querySelectorAll(\`[data-zen-\${eventType}]\`);
|
|
226
|
+
for (const el of elements) {
|
|
227
|
+
const handlerName = el.getAttribute(\`data-zen-\${eventType}\`);
|
|
228
|
+
if (handlerName && typeof window[handlerName] === 'function') {
|
|
229
|
+
el.addEventListener(eventType, (e) => {
|
|
230
|
+
window[handlerName](e, el);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function setupReactiveUpdates(root, state) {
|
|
238
|
+
// For now, reactive updates are handled by the existing binding system
|
|
239
|
+
// This is a placeholder for future reactive DOM updates
|
|
240
|
+
// The existing runtime handles reactivity via state property setters
|
|
241
|
+
}`;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Generate style injection code
|
|
245
|
+
*/
|
|
246
|
+
function generateStyleInjection(styles) {
|
|
247
|
+
if (styles.length === 0) {
|
|
248
|
+
return '';
|
|
249
|
+
}
|
|
250
|
+
const styleBlocks = styles.map((style, index) => {
|
|
251
|
+
const escapedStyle = style.raw.replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
252
|
+
return `
|
|
253
|
+
const style${index} = document.createElement('style');
|
|
254
|
+
style${index}.textContent = \`${escapedStyle}\`;
|
|
255
|
+
document.head.appendChild(style${index});`;
|
|
256
|
+
}).join('');
|
|
257
|
+
return `function injectStyles() {${styleBlocks}
|
|
258
|
+
}`;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Generate state initialization code
|
|
262
|
+
* In Phase 9: Also handles props passing
|
|
263
|
+
*/
|
|
264
|
+
function generateStateInitialization(stateDeclarations, propDeclarations) {
|
|
265
|
+
const stateInit = Array.from(stateDeclarations.entries()).map(([name, value]) => {
|
|
266
|
+
return `
|
|
267
|
+
// Initialize state: ${name}
|
|
268
|
+
if (typeof state.${name} === 'undefined') {
|
|
269
|
+
state.${name} = ${value};
|
|
270
|
+
}`;
|
|
271
|
+
}).join('');
|
|
272
|
+
const legacyPropInit = propDeclarations.includes('props') ? `
|
|
273
|
+
// Initialize props object (legacy)
|
|
274
|
+
if (typeof window.__ZEN_PROPS__ !== 'undefined') {
|
|
275
|
+
state.props = window.__ZEN_PROPS__;
|
|
276
|
+
}` : '';
|
|
277
|
+
const individualPropInit = propDeclarations.filter(p => p !== 'props').map(prop => `
|
|
278
|
+
// Initialize prop: ${prop}
|
|
279
|
+
if (typeof state.${prop} === 'undefined' && typeof window.__ZEN_PROPS__ !== 'undefined' && typeof window.__ZEN_PROPS__.${prop} !== 'undefined') {
|
|
280
|
+
state.${prop} = window.__ZEN_PROPS__.${prop};
|
|
281
|
+
}`).join('');
|
|
282
|
+
return `function initializeState(state) {${stateInit}${legacyPropInit}${individualPropInit}
|
|
283
|
+
}`;
|
|
284
|
+
}
|
|
285
|
+
// Note: transformScript is now handled by transformStateDeclarations in legacy/parse.ts
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expression Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Wraps extracted expressions into runtime functions with explicit data arguments
|
|
5
|
+
*
|
|
6
|
+
* Phase 6: Expressions now accept explicit loaderData, props, stores arguments
|
|
7
|
+
* instead of relying on implicit globals
|
|
8
|
+
*/
|
|
9
|
+
import type { ExpressionIR, LoopContext } from '../ir/types';
|
|
10
|
+
import type { ExpressionDataDependencies } from './dataExposure';
|
|
11
|
+
/**
|
|
12
|
+
* Wrap an expression into a runtime function with explicit data arguments
|
|
13
|
+
*
|
|
14
|
+
* Phase 6: Supports explicit loaderData, props, stores arguments
|
|
15
|
+
* Phase 7: Supports loop context for expressions inside map iterations
|
|
16
|
+
*/
|
|
17
|
+
export declare function wrapExpression(expr: ExpressionIR, dependencies?: ExpressionDataDependencies, loopContext?: LoopContext): string;
|
|
18
|
+
/**
|
|
19
|
+
* Generate all expression wrappers for a set of expressions
|
|
20
|
+
*
|
|
21
|
+
* Phase 6: Accepts dependencies array for explicit data exposure
|
|
22
|
+
* Phase 7: Accepts loop contexts for expressions inside map iterations
|
|
23
|
+
*/
|
|
24
|
+
export declare function generateExpressionWrappers(expressions: ExpressionIR[], dependencies?: ExpressionDataDependencies[], loopContexts?: (LoopContext | undefined)[]): string;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expression Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Wraps extracted expressions into runtime functions with explicit data arguments
|
|
5
|
+
*
|
|
6
|
+
* Phase 6: Expressions now accept explicit loaderData, props, stores arguments
|
|
7
|
+
* instead of relying on implicit globals
|
|
8
|
+
*/
|
|
9
|
+
import { generateExplicitExpressionWrapper } from './dataExposure';
|
|
10
|
+
import { wrapExpressionWithLoopContext } from './wrapExpressionWithLoop';
|
|
11
|
+
import { transformExpressionJSX } from '../transform/expressionTransformer';
|
|
12
|
+
/**
|
|
13
|
+
* Wrap an expression into a runtime function with explicit data arguments
|
|
14
|
+
*
|
|
15
|
+
* Phase 6: Supports explicit loaderData, props, stores arguments
|
|
16
|
+
* Phase 7: Supports loop context for expressions inside map iterations
|
|
17
|
+
*/
|
|
18
|
+
export function wrapExpression(expr, dependencies, loopContext // Phase 7: Loop context for map expressions
|
|
19
|
+
) {
|
|
20
|
+
const { id, code } = expr;
|
|
21
|
+
// Phase 7: If loop context is provided, use loop-aware wrapper
|
|
22
|
+
if (loopContext && loopContext.variables.length > 0) {
|
|
23
|
+
return wrapExpressionWithLoopContext(expr, loopContext, dependencies);
|
|
24
|
+
}
|
|
25
|
+
// If dependencies are provided, use explicit wrapper (Phase 6)
|
|
26
|
+
if (dependencies) {
|
|
27
|
+
return generateExplicitExpressionWrapper(expr, dependencies);
|
|
28
|
+
}
|
|
29
|
+
// Fallback to legacy wrapper (backwards compatibility)
|
|
30
|
+
// Transform JSX-like tags inside expression code
|
|
31
|
+
const transformedCode = transformExpressionJSX(code);
|
|
32
|
+
// Escape the code for use in a single-line comment (replace newlines with spaces)
|
|
33
|
+
const commentCode = code.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ').substring(0, 100);
|
|
34
|
+
const jsonEscapedCode = JSON.stringify(code);
|
|
35
|
+
return `
|
|
36
|
+
// Expression: ${commentCode}${code.length > 100 ? '...' : ''}
|
|
37
|
+
const ${id} = (state) => {
|
|
38
|
+
try {
|
|
39
|
+
// Expose zenith helpers for JSX and content
|
|
40
|
+
const __zenith = window.__zenith || {};
|
|
41
|
+
const zenCollection = __zenith.zenCollection || ((name) => ({ get: () => [] }));
|
|
42
|
+
|
|
43
|
+
with (state) {
|
|
44
|
+
return ${transformedCode};
|
|
45
|
+
}
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.warn('[Zenith] Expression evaluation error:', ${jsonEscapedCode}, e);
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
};`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate all expression wrappers for a set of expressions
|
|
54
|
+
*
|
|
55
|
+
* Phase 6: Accepts dependencies array for explicit data exposure
|
|
56
|
+
* Phase 7: Accepts loop contexts for expressions inside map iterations
|
|
57
|
+
*/
|
|
58
|
+
export function generateExpressionWrappers(expressions, dependencies, loopContexts // Phase 7: Loop contexts for each expression
|
|
59
|
+
) {
|
|
60
|
+
if (expressions.length === 0) {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
if (dependencies && dependencies.length === expressions.length) {
|
|
64
|
+
// Use explicit wrappers with dependencies and optional loop contexts
|
|
65
|
+
return expressions
|
|
66
|
+
.map((expr, index) => {
|
|
67
|
+
const loopCtx = loopContexts && loopContexts[index] !== undefined
|
|
68
|
+
? loopContexts[index]
|
|
69
|
+
: undefined;
|
|
70
|
+
return wrapExpression(expr, dependencies[index], loopCtx);
|
|
71
|
+
})
|
|
72
|
+
.join('\n');
|
|
73
|
+
}
|
|
74
|
+
// Fallback to legacy wrappers (no dependencies, no loop contexts)
|
|
75
|
+
return expressions.map(expr => wrapExpression(expr)).join('\n');
|
|
76
|
+
}
|