@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.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/build-analyzer.d.ts +44 -0
  4. package/dist/build-analyzer.js +87 -0
  5. package/dist/bundler.d.ts +31 -0
  6. package/dist/bundler.js +86 -0
  7. package/dist/core/components/index.d.ts +9 -0
  8. package/dist/core/components/index.js +13 -0
  9. package/dist/core/config/index.d.ts +11 -0
  10. package/dist/core/config/index.js +10 -0
  11. package/dist/core/config/loader.d.ts +17 -0
  12. package/dist/core/config/loader.js +60 -0
  13. package/dist/core/config/types.d.ts +98 -0
  14. package/dist/core/config/types.js +32 -0
  15. package/dist/core/index.d.ts +7 -0
  16. package/dist/core/index.js +6 -0
  17. package/dist/core/lifecycle/index.d.ts +16 -0
  18. package/dist/core/lifecycle/index.js +19 -0
  19. package/dist/core/lifecycle/zen-mount.d.ts +66 -0
  20. package/dist/core/lifecycle/zen-mount.js +151 -0
  21. package/dist/core/lifecycle/zen-unmount.d.ts +54 -0
  22. package/dist/core/lifecycle/zen-unmount.js +76 -0
  23. package/dist/core/plugins/bridge.d.ts +116 -0
  24. package/dist/core/plugins/bridge.js +121 -0
  25. package/dist/core/plugins/index.d.ts +6 -0
  26. package/dist/core/plugins/index.js +6 -0
  27. package/dist/core/plugins/registry.d.ts +67 -0
  28. package/dist/core/plugins/registry.js +113 -0
  29. package/dist/core/reactivity/index.d.ts +30 -0
  30. package/dist/core/reactivity/index.js +33 -0
  31. package/dist/core/reactivity/tracking.d.ts +74 -0
  32. package/dist/core/reactivity/tracking.js +136 -0
  33. package/dist/core/reactivity/zen-batch.d.ts +45 -0
  34. package/dist/core/reactivity/zen-batch.js +54 -0
  35. package/dist/core/reactivity/zen-effect.d.ts +48 -0
  36. package/dist/core/reactivity/zen-effect.js +98 -0
  37. package/dist/core/reactivity/zen-memo.d.ts +43 -0
  38. package/dist/core/reactivity/zen-memo.js +100 -0
  39. package/dist/core/reactivity/zen-ref.d.ts +44 -0
  40. package/dist/core/reactivity/zen-ref.js +34 -0
  41. package/dist/core/reactivity/zen-signal.d.ts +48 -0
  42. package/dist/core/reactivity/zen-signal.js +84 -0
  43. package/dist/core/reactivity/zen-state.d.ts +35 -0
  44. package/dist/core/reactivity/zen-state.js +147 -0
  45. package/dist/core/reactivity/zen-untrack.d.ts +38 -0
  46. package/dist/core/reactivity/zen-untrack.js +41 -0
  47. package/dist/css/index.d.ts +73 -0
  48. package/dist/css/index.js +246 -0
  49. package/dist/discovery/componentDiscovery.d.ts +42 -0
  50. package/dist/discovery/componentDiscovery.js +56 -0
  51. package/dist/discovery/layouts.d.ts +13 -0
  52. package/dist/discovery/layouts.js +41 -0
  53. package/dist/errors/compilerError.d.ts +31 -0
  54. package/dist/errors/compilerError.js +51 -0
  55. package/dist/finalize/finalizeOutput.d.ts +32 -0
  56. package/dist/finalize/finalizeOutput.js +62 -0
  57. package/dist/finalize/generateFinalBundle.d.ts +24 -0
  58. package/dist/finalize/generateFinalBundle.js +68 -0
  59. package/dist/index.d.ts +36 -0
  60. package/dist/index.js +51 -0
  61. package/dist/ir/types.d.ts +181 -0
  62. package/dist/ir/types.js +8 -0
  63. package/dist/output/types.d.ts +30 -0
  64. package/dist/output/types.js +6 -0
  65. package/dist/parse/detectMapExpressions.d.ts +45 -0
  66. package/dist/parse/detectMapExpressions.js +77 -0
  67. package/dist/parse/parseScript.d.ts +8 -0
  68. package/dist/parse/parseScript.js +36 -0
  69. package/dist/parse/parseTemplate.d.ts +11 -0
  70. package/dist/parse/parseTemplate.js +487 -0
  71. package/dist/parse/parseZenFile.d.ts +11 -0
  72. package/dist/parse/parseZenFile.js +50 -0
  73. package/dist/parse/scriptAnalysis.d.ts +25 -0
  74. package/dist/parse/scriptAnalysis.js +60 -0
  75. package/dist/parse/trackLoopContext.d.ts +20 -0
  76. package/dist/parse/trackLoopContext.js +62 -0
  77. package/dist/parseZenFile.d.ts +10 -0
  78. package/dist/parseZenFile.js +55 -0
  79. package/dist/runtime/analyzeAndEmit.d.ts +20 -0
  80. package/dist/runtime/analyzeAndEmit.js +70 -0
  81. package/dist/runtime/build.d.ts +6 -0
  82. package/dist/runtime/build.js +13 -0
  83. package/dist/runtime/bundle-generator.d.ts +27 -0
  84. package/dist/runtime/bundle-generator.js +1263 -0
  85. package/dist/runtime/client-runtime.d.ts +41 -0
  86. package/dist/runtime/client-runtime.js +397 -0
  87. package/dist/runtime/dataExposure.d.ts +52 -0
  88. package/dist/runtime/dataExposure.js +227 -0
  89. package/dist/runtime/generateDOM.d.ts +21 -0
  90. package/dist/runtime/generateDOM.js +194 -0
  91. package/dist/runtime/generateHydrationBundle.d.ts +15 -0
  92. package/dist/runtime/generateHydrationBundle.js +399 -0
  93. package/dist/runtime/hydration.d.ts +53 -0
  94. package/dist/runtime/hydration.js +271 -0
  95. package/dist/runtime/navigation.d.ts +58 -0
  96. package/dist/runtime/navigation.js +372 -0
  97. package/dist/runtime/serve.d.ts +13 -0
  98. package/dist/runtime/serve.js +76 -0
  99. package/dist/runtime/thinRuntime.d.ts +23 -0
  100. package/dist/runtime/thinRuntime.js +158 -0
  101. package/dist/runtime/transformIR.d.ts +19 -0
  102. package/dist/runtime/transformIR.js +285 -0
  103. package/dist/runtime/wrapExpression.d.ts +24 -0
  104. package/dist/runtime/wrapExpression.js +76 -0
  105. package/dist/runtime/wrapExpressionWithLoop.d.ts +17 -0
  106. package/dist/runtime/wrapExpressionWithLoop.js +75 -0
  107. package/dist/spa-build.d.ts +26 -0
  108. package/dist/spa-build.js +866 -0
  109. package/dist/ssg-build.d.ts +32 -0
  110. package/dist/ssg-build.js +408 -0
  111. package/dist/test/analyze-emit.test.d.ts +1 -0
  112. package/dist/test/analyze-emit.test.js +88 -0
  113. package/dist/test/bundler-contract.test.d.ts +1 -0
  114. package/dist/test/bundler-contract.test.js +137 -0
  115. package/dist/test/compiler-authority.test.d.ts +1 -0
  116. package/dist/test/compiler-authority.test.js +90 -0
  117. package/dist/test/component-instance-test.d.ts +1 -0
  118. package/dist/test/component-instance-test.js +115 -0
  119. package/dist/test/error-native-bridge.test.d.ts +1 -0
  120. package/dist/test/error-native-bridge.test.js +51 -0
  121. package/dist/test/error-serialization.test.d.ts +1 -0
  122. package/dist/test/error-serialization.test.js +38 -0
  123. package/dist/test/macro-inlining.test.d.ts +1 -0
  124. package/dist/test/macro-inlining.test.js +178 -0
  125. package/dist/test/validate-test.d.ts +6 -0
  126. package/dist/test/validate-test.js +95 -0
  127. package/dist/transform/classifyExpression.d.ts +46 -0
  128. package/dist/transform/classifyExpression.js +354 -0
  129. package/dist/transform/componentResolver.d.ts +15 -0
  130. package/dist/transform/componentResolver.js +30 -0
  131. package/dist/transform/expressionTransformer.d.ts +19 -0
  132. package/dist/transform/expressionTransformer.js +333 -0
  133. package/dist/transform/fragmentLowering.d.ts +25 -0
  134. package/dist/transform/fragmentLowering.js +468 -0
  135. package/dist/transform/layoutProcessor.d.ts +5 -0
  136. package/dist/transform/layoutProcessor.js +34 -0
  137. package/dist/transform/transformTemplate.d.ts +11 -0
  138. package/dist/transform/transformTemplate.js +33 -0
  139. package/dist/validate/invariants.d.ts +23 -0
  140. package/dist/validate/invariants.js +55 -0
  141. package/native/compiler-native/compiler-native.node +0 -0
  142. package/native/compiler-native/index.d.ts +113 -0
  143. package/native/compiler-native/index.js +19 -0
  144. package/native/compiler-native/package.json +19 -0
  145. package/package.json +49 -0
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Runtime Hydration Layer
3
+ *
4
+ * Phase 5: Browser-side runtime that hydrates static HTML with dynamic expressions
5
+ *
6
+ * This runtime:
7
+ * - Locates DOM placeholders (data-zen-text, data-zen-attr-*)
8
+ * - Evaluates precompiled expressions against state
9
+ * - Updates DOM textContent, attributes, and properties
10
+ * - Binds event handlers
11
+ * - Handles reactive state updates
12
+ */
13
+ const bindings = [];
14
+ /**
15
+ * Hydrate static HTML with dynamic expressions
16
+ *
17
+ * @param state - The state object to evaluate expressions against
18
+ * @param container - The container element to hydrate (defaults to document)
19
+ */
20
+ export function hydrate(state, container = document) {
21
+ if (!state) {
22
+ console.warn('[Zenith] hydrate called without state object');
23
+ return;
24
+ }
25
+ // Store state globally for event handlers
26
+ if (typeof window !== 'undefined') {
27
+ window.__ZENITH_STATE__ = state;
28
+ }
29
+ // Clear existing bindings
30
+ bindings.length = 0;
31
+ // Find all text expression placeholders
32
+ const textPlaceholders = container.querySelectorAll('[data-zen-text]');
33
+ for (let i = 0; i < textPlaceholders.length; i++) {
34
+ const node = textPlaceholders[i];
35
+ if (!node)
36
+ continue;
37
+ const expressionId = node.getAttribute('data-zen-text');
38
+ if (!expressionId)
39
+ continue;
40
+ bindings.push({
41
+ node,
42
+ type: 'text',
43
+ expressionId
44
+ });
45
+ updateTextBinding(node, expressionId, state);
46
+ }
47
+ // Find all attribute expression placeholders
48
+ const attrPlaceholders = container.querySelectorAll('[data-zen-attr-class], [data-zen-attr-style], [data-zen-attr-src], [data-zen-attr-href], [data-zen-attr-disabled], [data-zen-attr-checked]');
49
+ for (let i = 0; i < attrPlaceholders.length; i++) {
50
+ const node = attrPlaceholders[i];
51
+ if (!(node instanceof Element))
52
+ continue;
53
+ // Check each possible attribute
54
+ const attrNames = ['class', 'style', 'src', 'href', 'disabled', 'checked'];
55
+ for (const attrName of attrNames) {
56
+ const expressionId = node.getAttribute(`data-zen-attr-${attrName}`);
57
+ if (!expressionId)
58
+ continue;
59
+ bindings.push({
60
+ node,
61
+ type: 'attribute',
62
+ attributeName: attrName,
63
+ expressionId
64
+ });
65
+ updateAttributeBinding(node, attrName, expressionId, state);
66
+ }
67
+ }
68
+ // Bind event handlers
69
+ bindEvents(container);
70
+ }
71
+ /**
72
+ * Update a text binding
73
+ */
74
+ function updateTextBinding(node, expressionId, state) {
75
+ try {
76
+ const expression = window.__ZENITH_EXPRESSIONS__?.get(expressionId);
77
+ if (!expression) {
78
+ console.warn(`[Zenith] Expression ${expressionId} not found in registry`);
79
+ return;
80
+ }
81
+ const result = expression(state);
82
+ // Handle different result types
83
+ if (result === null || result === undefined || result === false) {
84
+ node.textContent = '';
85
+ }
86
+ else if (typeof result === 'string' || typeof result === 'number') {
87
+ node.textContent = String(result);
88
+ }
89
+ else if (result instanceof Node) {
90
+ // Replace node with result node
91
+ if (node.parentNode) {
92
+ node.parentNode.replaceChild(result, node);
93
+ }
94
+ }
95
+ else if (Array.isArray(result)) {
96
+ // Handle array results (for map expressions)
97
+ if (node.parentNode) {
98
+ const fragment = document.createDocumentFragment();
99
+ for (const item of result) {
100
+ if (item instanceof Node) {
101
+ fragment.appendChild(item);
102
+ }
103
+ else {
104
+ fragment.appendChild(document.createTextNode(String(item)));
105
+ }
106
+ }
107
+ node.parentNode.replaceChild(fragment, node);
108
+ }
109
+ }
110
+ else {
111
+ node.textContent = String(result);
112
+ }
113
+ }
114
+ catch (error) {
115
+ console.error(`[Zenith] Error evaluating expression ${expressionId}:`, error);
116
+ console.error('Expression ID:', expressionId, 'State:', state);
117
+ }
118
+ }
119
+ /**
120
+ * Update an attribute binding
121
+ */
122
+ function updateAttributeBinding(element, attributeName, expressionId, state) {
123
+ try {
124
+ const expression = window.__ZENITH_EXPRESSIONS__?.get(expressionId);
125
+ if (!expression) {
126
+ console.warn(`[Zenith] Expression ${expressionId} not found in registry`);
127
+ return;
128
+ }
129
+ const result = expression(state);
130
+ // Handle different attribute types
131
+ if (attributeName === 'class' || attributeName === 'className') {
132
+ element.className = String(result ?? '');
133
+ }
134
+ else if (attributeName === 'style') {
135
+ if (typeof result === 'string') {
136
+ element.setAttribute('style', result);
137
+ }
138
+ else if (result && typeof result === 'object') {
139
+ // Handle style object
140
+ const styleStr = Object.entries(result)
141
+ .map(([key, value]) => `${key}: ${value}`)
142
+ .join('; ');
143
+ element.setAttribute('style', styleStr);
144
+ }
145
+ }
146
+ else if (attributeName === 'disabled' || attributeName === 'checked' || attributeName === 'readonly') {
147
+ // Boolean attributes
148
+ if (result) {
149
+ element.setAttribute(attributeName, '');
150
+ }
151
+ else {
152
+ element.removeAttribute(attributeName);
153
+ }
154
+ }
155
+ else {
156
+ // Regular attributes
157
+ if (result === null || result === undefined || result === false) {
158
+ element.removeAttribute(attributeName);
159
+ }
160
+ else {
161
+ element.setAttribute(attributeName, String(result));
162
+ }
163
+ }
164
+ }
165
+ catch (error) {
166
+ console.error(`[Zenith] Error updating attribute ${attributeName} with expression ${expressionId}:`, error);
167
+ console.error('Expression ID:', expressionId, 'State:', state);
168
+ }
169
+ }
170
+ /**
171
+ * Bind event handlers to DOM elements
172
+ *
173
+ * @param container - The container element to bind events in (defaults to document)
174
+ */
175
+ export function bindEvents(container = document) {
176
+ const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
177
+ for (const eventType of eventTypes) {
178
+ const elements = container.querySelectorAll(`[data-zen-${eventType}]`);
179
+ for (let i = 0; i < elements.length; i++) {
180
+ const element = elements[i];
181
+ if (!(element instanceof Element))
182
+ continue;
183
+ const handlerName = element.getAttribute(`data-zen-${eventType}`);
184
+ if (!handlerName)
185
+ continue;
186
+ // Remove existing listener if any (to avoid duplicates)
187
+ const existingHandler = element[`__zen_${eventType}_handler`];
188
+ if (existingHandler) {
189
+ element.removeEventListener(eventType, existingHandler);
190
+ }
191
+ // Create new handler
192
+ const handler = (event) => {
193
+ try {
194
+ // Get handler function from window (functions are registered on window)
195
+ const handlerFunc = window[handlerName];
196
+ if (typeof handlerFunc === 'function') {
197
+ handlerFunc(event, element);
198
+ }
199
+ else {
200
+ console.warn(`[Zenith] Event handler "${handlerName}" not found for ${eventType} event`);
201
+ }
202
+ }
203
+ catch (error) {
204
+ console.error(`[Zenith] Error executing event handler "${handlerName}":`, error);
205
+ }
206
+ };
207
+ element[`__zen_${eventType}_handler`] = handler;
208
+ element.addEventListener(eventType, handler);
209
+ }
210
+ }
211
+ }
212
+ /**
213
+ * Update all bindings when state changes
214
+ *
215
+ * @param state - The new state object
216
+ */
217
+ export function update(state) {
218
+ if (!state) {
219
+ console.warn('[Zenith] update called without state object');
220
+ return;
221
+ }
222
+ // Update global state
223
+ if (typeof window !== 'undefined') {
224
+ window.__ZENITH_STATE__ = state;
225
+ }
226
+ // Update all tracked bindings
227
+ for (const binding of bindings) {
228
+ if (binding.type === 'text') {
229
+ updateTextBinding(binding.node, binding.expressionId, state);
230
+ }
231
+ else if (binding.type === 'attribute' && binding.attributeName) {
232
+ if (binding.node instanceof Element) {
233
+ updateAttributeBinding(binding.node, binding.attributeName, binding.expressionId, state);
234
+ }
235
+ }
236
+ }
237
+ }
238
+ /**
239
+ * Initialize the expression registry
240
+ * Called once when the runtime loads
241
+ *
242
+ * @param expressions - Map of expression IDs to evaluation functions
243
+ */
244
+ export function initExpressions(expressions) {
245
+ if (typeof window !== 'undefined') {
246
+ window.__ZENITH_EXPRESSIONS__ = expressions;
247
+ }
248
+ }
249
+ /**
250
+ * Clear all bindings and event listeners
251
+ * Useful for cleanup when navigating away
252
+ */
253
+ export function cleanup(container = document) {
254
+ // Remove event listeners
255
+ const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
256
+ for (const eventType of eventTypes) {
257
+ const elements = container.querySelectorAll(`[data-zen-${eventType}]`);
258
+ for (let i = 0; i < elements.length; i++) {
259
+ const element = elements[i];
260
+ if (!(element instanceof Element))
261
+ continue;
262
+ const handler = element[`__zen_${eventType}_handler`];
263
+ if (handler) {
264
+ element.removeEventListener(eventType, handler);
265
+ delete element[`__zen_${eventType}_handler`];
266
+ }
267
+ }
268
+ }
269
+ // Clear bindings
270
+ bindings.length = 0;
271
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Navigation & Prefetch Runtime
3
+ *
4
+ * Phase 7: Prefetch compiled output, safe SPA navigation, route caching
5
+ *
6
+ * This runtime handles:
7
+ * - Prefetching compiled HTML + JS for routes
8
+ * - Caching prefetched routes
9
+ * - Safe DOM mounting and hydration
10
+ * - Browser history management
11
+ * - Explicit data exposure for navigation
12
+ */
13
+ /**
14
+ * Route cache entry containing compiled output
15
+ */
16
+ export interface RouteCacheEntry {
17
+ html: string;
18
+ js: string;
19
+ styles: string[];
20
+ routePath: string;
21
+ compiledAt: number;
22
+ }
23
+ /**
24
+ * Navigation options with explicit data
25
+ */
26
+ export interface NavigateOptions {
27
+ loaderData?: any;
28
+ props?: any;
29
+ stores?: any;
30
+ replace?: boolean;
31
+ prefetch?: boolean;
32
+ }
33
+ /**
34
+ * Prefetch a route's compiled output
35
+ *
36
+ * @param routePath - The route path to prefetch (e.g., "/dashboard")
37
+ * @returns Promise that resolves when prefetch is complete
38
+ */
39
+ export declare function prefetchRoute(routePath: string): Promise<void>;
40
+ /**
41
+ * Get cached route entry
42
+ */
43
+ export declare function getCachedRoute(routePath: string): RouteCacheEntry | null;
44
+ /**
45
+ * Navigate to a route with explicit data
46
+ *
47
+ * @param routePath - The route path to navigate to
48
+ * @param options - Navigation options with loaderData, props, stores
49
+ */
50
+ export declare function navigate(routePath: string, options?: NavigateOptions): Promise<void>;
51
+ /**
52
+ * Handle browser back/forward navigation
53
+ */
54
+ export declare function setupHistoryHandling(): void;
55
+ /**
56
+ * Generate navigation runtime code (to be included in bundle)
57
+ */
58
+ export declare function generateNavigationRuntime(): string;
@@ -0,0 +1,372 @@
1
+ /**
2
+ * Navigation & Prefetch Runtime
3
+ *
4
+ * Phase 7: Prefetch compiled output, safe SPA navigation, route caching
5
+ *
6
+ * This runtime handles:
7
+ * - Prefetching compiled HTML + JS for routes
8
+ * - Caching prefetched routes
9
+ * - Safe DOM mounting and hydration
10
+ * - Browser history management
11
+ * - Explicit data exposure for navigation
12
+ */
13
+ /**
14
+ * Route cache - stores prefetched compiled output
15
+ */
16
+ const routeCache = new Map();
17
+ /**
18
+ * Current navigation state
19
+ */
20
+ let currentRoute = '';
21
+ let navigationInProgress = false;
22
+ /**
23
+ * Prefetch a route's compiled output
24
+ *
25
+ * @param routePath - The route path to prefetch (e.g., "/dashboard")
26
+ * @returns Promise that resolves when prefetch is complete
27
+ */
28
+ export async function prefetchRoute(routePath) {
29
+ // Normalize route path
30
+ const normalizedPath = routePath === '' ? '/' : routePath;
31
+ // Check if already cached
32
+ if (routeCache.has(normalizedPath)) {
33
+ return Promise.resolve();
34
+ }
35
+ // In a real implementation, this would fetch from the build output
36
+ // For Phase 7, we'll generate a placeholder that indicates the route needs to be built
37
+ try {
38
+ // Fetch compiled HTML + JS
39
+ // In production, this would be:
40
+ // const htmlResponse = await fetch(`${normalizedPath}.html`)
41
+ // const jsResponse = await fetch(`${normalizedPath}.js`)
42
+ // For now, return a placeholder that indicates prefetch structure
43
+ const cacheEntry = {
44
+ html: `<!-- Prefetched route: ${normalizedPath} -->`,
45
+ js: `// Prefetched route runtime: ${normalizedPath}`,
46
+ styles: [],
47
+ routePath: normalizedPath,
48
+ compiledAt: Date.now()
49
+ };
50
+ routeCache.set(normalizedPath, cacheEntry);
51
+ }
52
+ catch (error) {
53
+ console.warn(`[Zenith] Failed to prefetch route ${normalizedPath}:`, error);
54
+ throw error;
55
+ }
56
+ }
57
+ /**
58
+ * Get cached route entry
59
+ */
60
+ export function getCachedRoute(routePath) {
61
+ const normalizedPath = routePath === '' ? '/' : routePath;
62
+ return routeCache.get(normalizedPath) || null;
63
+ }
64
+ /**
65
+ * Navigate to a route with explicit data
66
+ *
67
+ * @param routePath - The route path to navigate to
68
+ * @param options - Navigation options with loaderData, props, stores
69
+ */
70
+ export async function navigate(routePath, options = {}) {
71
+ if (navigationInProgress) {
72
+ console.warn('[Zenith] Navigation already in progress, skipping');
73
+ return;
74
+ }
75
+ navigationInProgress = true;
76
+ try {
77
+ const normalizedPath = routePath === '' ? '/' : routePath;
78
+ // Check if route is cached, otherwise prefetch
79
+ let cacheEntry = getCachedRoute(normalizedPath);
80
+ if (!cacheEntry && options.prefetch !== false) {
81
+ await prefetchRoute(normalizedPath);
82
+ cacheEntry = getCachedRoute(normalizedPath);
83
+ }
84
+ if (!cacheEntry) {
85
+ throw new Error(`Route ${normalizedPath} not found. Ensure the route is compiled.`);
86
+ }
87
+ // Cleanup previous route
88
+ cleanupPreviousRoute();
89
+ // Get router outlet
90
+ const outlet = getRouterOutlet();
91
+ if (!outlet) {
92
+ throw new Error('Router outlet not found. Ensure <div id="zenith-outlet"></div> exists.');
93
+ }
94
+ // Mount compiled HTML
95
+ outlet.innerHTML = cacheEntry.html;
96
+ // Inject styles
97
+ injectStyles(cacheEntry.styles);
98
+ // Execute JS runtime (compiled expressions + hydration)
99
+ await executeRouteRuntime(cacheEntry.js, {
100
+ loaderData: options.loaderData || {},
101
+ props: options.props || {},
102
+ stores: options.stores || {}
103
+ });
104
+ // Update browser history
105
+ if (typeof window !== 'undefined') {
106
+ const url = normalizedPath + (window.location.search || '');
107
+ if (options.replace) {
108
+ window.history.replaceState({ route: normalizedPath }, '', url);
109
+ }
110
+ else {
111
+ window.history.pushState({ route: normalizedPath }, '', url);
112
+ }
113
+ }
114
+ currentRoute = normalizedPath;
115
+ // Dispatch navigation event
116
+ dispatchNavigationEvent(normalizedPath, options);
117
+ }
118
+ catch (error) {
119
+ console.error('[Zenith] Navigation error:', error);
120
+ throw error;
121
+ }
122
+ finally {
123
+ navigationInProgress = false;
124
+ }
125
+ }
126
+ /**
127
+ * Cleanup previous route
128
+ */
129
+ function cleanupPreviousRoute() {
130
+ if (typeof window === 'undefined')
131
+ return;
132
+ // Cleanup hydration runtime
133
+ if (window.zenithCleanup) {
134
+ ;
135
+ window.zenithCleanup();
136
+ }
137
+ // Remove previous page styles
138
+ document.querySelectorAll('style[data-zen-route-style]').forEach(style => {
139
+ style.remove();
140
+ });
141
+ // Clear window state (if needed)
142
+ // State is managed per-route, so we don't clear it here
143
+ }
144
+ /**
145
+ * Get router outlet element
146
+ */
147
+ function getRouterOutlet() {
148
+ if (typeof window === 'undefined')
149
+ return null;
150
+ return document.querySelector('#zenith-outlet') || document.querySelector('[data-zen-outlet]');
151
+ }
152
+ /**
153
+ * Inject route styles
154
+ */
155
+ function injectStyles(styles) {
156
+ if (typeof window === 'undefined')
157
+ return;
158
+ styles.forEach((styleContent, index) => {
159
+ const style = document.createElement('style');
160
+ style.setAttribute('data-zen-route-style', String(index));
161
+ style.textContent = styleContent;
162
+ document.head.appendChild(style);
163
+ });
164
+ }
165
+ /**
166
+ * Execute route runtime JS
167
+ *
168
+ * This executes the compiled JS bundle for the route, which includes:
169
+ * - Expression wrappers
170
+ * - Hydration runtime
171
+ * - Event bindings
172
+ */
173
+ async function executeRouteRuntime(jsCode, data) {
174
+ if (typeof window === 'undefined')
175
+ return;
176
+ try {
177
+ // Execute the compiled JS (which registers expressions and hydration functions)
178
+ // In a real implementation, this would use a script tag or eval (secure context)
179
+ const script = document.createElement('script');
180
+ script.textContent = jsCode;
181
+ document.head.appendChild(script);
182
+ document.head.removeChild(script);
183
+ // After JS executes, call hydrate with explicit data
184
+ if (window.zenithHydrate) {
185
+ const state = window.__ZENITH_STATE__ || {};
186
+ window.zenithHydrate(state, data.loaderData, data.props, data.stores, getRouterOutlet());
187
+ }
188
+ }
189
+ catch (error) {
190
+ console.error('[Zenith] Error executing route runtime:', error);
191
+ throw error;
192
+ }
193
+ }
194
+ /**
195
+ * Dispatch navigation event
196
+ */
197
+ function dispatchNavigationEvent(routePath, options) {
198
+ if (typeof window === 'undefined')
199
+ return;
200
+ const event = new CustomEvent('zenith:navigate', {
201
+ detail: {
202
+ route: routePath,
203
+ loaderData: options.loaderData,
204
+ props: options.props,
205
+ stores: options.stores
206
+ }
207
+ });
208
+ window.dispatchEvent(event);
209
+ }
210
+ /**
211
+ * Handle browser back/forward navigation
212
+ */
213
+ export function setupHistoryHandling() {
214
+ if (typeof window === 'undefined')
215
+ return;
216
+ window.addEventListener('popstate', (event) => {
217
+ const state = event.state;
218
+ const routePath = state?.route || window.location.pathname;
219
+ // Navigate without pushing to history (browser already changed it)
220
+ navigate(routePath, { replace: true, prefetch: false }).catch((error) => {
221
+ console.error('[Zenith] History navigation error:', error);
222
+ });
223
+ });
224
+ }
225
+ /**
226
+ * Generate navigation runtime code (to be included in bundle)
227
+ */
228
+ export function generateNavigationRuntime() {
229
+ return `
230
+ // Zenith Navigation Runtime (Phase 7)
231
+ (function() {
232
+ 'use strict';
233
+
234
+ // Route cache
235
+ const __zen_routeCache = new Map();
236
+
237
+ // Current route state
238
+ let __zen_currentRoute = '';
239
+ let __zen_navigationInProgress = false;
240
+
241
+ /**
242
+ * Prefetch a route
243
+ */
244
+ async function prefetchRoute(routePath) {
245
+ const normalizedPath = routePath === '' ? '/' : routePath;
246
+
247
+ if (__zen_routeCache.has(normalizedPath)) {
248
+ return Promise.resolve();
249
+ }
250
+
251
+ try {
252
+ // Fetch compiled HTML + JS
253
+ // This is a placeholder - in production, fetch from build output
254
+ const cacheEntry = {
255
+ html: '<!-- Prefetched: ' + normalizedPath + ' -->',
256
+ js: '// Prefetched runtime: ' + normalizedPath,
257
+ styles: [],
258
+ routePath: normalizedPath,
259
+ compiledAt: Date.now()
260
+ };
261
+
262
+ __zen_routeCache.set(normalizedPath, cacheEntry);
263
+ } catch (error) {
264
+ console.warn('[Zenith] Prefetch failed:', routePath, error);
265
+ throw error;
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Navigate to route with explicit data
271
+ */
272
+ async function navigate(routePath, options) {
273
+ options = options || {};
274
+
275
+ if (__zen_navigationInProgress) {
276
+ console.warn('[Zenith] Navigation in progress');
277
+ return;
278
+ }
279
+
280
+ __zen_navigationInProgress = true;
281
+
282
+ try {
283
+ const normalizedPath = routePath === '' ? '/' : routePath;
284
+
285
+ // Get cached route or prefetch
286
+ let cacheEntry = __zen_routeCache.get(normalizedPath);
287
+ if (!cacheEntry && options.prefetch !== false) {
288
+ await prefetchRoute(normalizedPath);
289
+ cacheEntry = __zen_routeCache.get(normalizedPath);
290
+ }
291
+
292
+ if (!cacheEntry) {
293
+ throw new Error('Route not found: ' + normalizedPath);
294
+ }
295
+
296
+ // Get outlet
297
+ const outlet = document.querySelector('#zenith-outlet') || document.querySelector('[data-zen-outlet]');
298
+ if (!outlet) {
299
+ throw new Error('Router outlet not found');
300
+ }
301
+
302
+ // Mount HTML
303
+ outlet.innerHTML = cacheEntry.html;
304
+
305
+ // Execute runtime JS
306
+ if (cacheEntry.js) {
307
+ const script = document.createElement('script');
308
+ script.textContent = cacheEntry.js;
309
+ document.head.appendChild(script);
310
+ document.head.removeChild(script);
311
+ }
312
+
313
+ // Hydrate with explicit data
314
+ if (window.zenithHydrate) {
315
+ const state = window.__ZENITH_STATE__ || {};
316
+ window.zenithHydrate(
317
+ state,
318
+ options.loaderData || {},
319
+ options.props || {},
320
+ options.stores || {},
321
+ outlet
322
+ );
323
+ }
324
+
325
+ // Update history
326
+ const url = normalizedPath + (window.location.search || '');
327
+ if (options.replace) {
328
+ window.history.replaceState({ route: normalizedPath }, '', url);
329
+ } else {
330
+ window.history.pushState({ route: normalizedPath }, '', url);
331
+ }
332
+
333
+ __zen_currentRoute = normalizedPath;
334
+
335
+ // Dispatch event
336
+ window.dispatchEvent(new CustomEvent('zenith:navigate', {
337
+ detail: { route: normalizedPath, options: options }
338
+ }));
339
+ } catch (error) {
340
+ console.error('[Zenith] Navigation error:', error);
341
+ throw error;
342
+ } finally {
343
+ __zen_navigationInProgress = false;
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Handle browser history
349
+ */
350
+ function setupHistoryHandling() {
351
+ window.addEventListener('popstate', function(event) {
352
+ const state = event.state;
353
+ const routePath = state && state.route ? state.route : window.location.pathname;
354
+
355
+ navigate(routePath, { replace: true, prefetch: false }).catch(function(error) {
356
+ console.error('[Zenith] History navigation error:', error);
357
+ });
358
+ });
359
+ }
360
+
361
+ // Initialize history handling
362
+ setupHistoryHandling();
363
+
364
+ // Expose API
365
+ if (typeof window !== 'undefined') {
366
+ window.__zenith_navigate = navigate;
367
+ window.__zenith_prefetch = prefetchRoute;
368
+ window.navigate = navigate; // Global convenience
369
+ }
370
+ })();
371
+ `;
372
+ }