@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,1263 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Zenith Bundle Generator
|
|
5
|
+
*
|
|
6
|
+
* Generates the shared client runtime bundle that gets served as:
|
|
7
|
+
* - /assets/bundle.js in production
|
|
8
|
+
* - /runtime.js in development
|
|
9
|
+
*
|
|
10
|
+
* This is a cacheable, versioned file that contains:
|
|
11
|
+
* - Reactivity primitives (zenSignal, zenState, zenEffect, etc.)
|
|
12
|
+
* - Lifecycle hooks (zenOnMount, zenOnUnmount)
|
|
13
|
+
* - Hydration functions (zenithHydrate)
|
|
14
|
+
* - Event binding utilities
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Generate the complete client runtime bundle
|
|
18
|
+
* This is served as an external JS file, not inlined
|
|
19
|
+
*/
|
|
20
|
+
export function generateBundleJS(pluginData) {
|
|
21
|
+
// Serialize plugin data blindly - CLI never inspects what's inside.
|
|
22
|
+
// We escape </script> sequences just in case this bundle is ever inlined (unlikely but safe).
|
|
23
|
+
const serializedData = pluginData
|
|
24
|
+
? JSON.stringify(pluginData).replace(/<\/script/g, '<\\/script')
|
|
25
|
+
: '{}';
|
|
26
|
+
// Resolve core runtime paths - assumes sibling directory or relative in node_modules
|
|
27
|
+
const rootDir = process.cwd();
|
|
28
|
+
const coreDistPath = path.join(rootDir, '../zenith-core/dist/runtime');
|
|
29
|
+
let reactivityJS = '';
|
|
30
|
+
let lifecycleJS = '';
|
|
31
|
+
try {
|
|
32
|
+
const reactivityFile = path.join(coreDistPath, 'reactivity/index.js');
|
|
33
|
+
const lifecycleFile = path.join(coreDistPath, 'lifecycle/index.js');
|
|
34
|
+
if (existsSync(reactivityFile))
|
|
35
|
+
reactivityJS = readFileSync(reactivityFile, 'utf-8');
|
|
36
|
+
if (existsSync(lifecycleFile))
|
|
37
|
+
lifecycleJS = readFileSync(lifecycleFile, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
console.warn('[Zenith] Could not load runtime from core dist, falling back to basic support', e);
|
|
41
|
+
}
|
|
42
|
+
return `/*!
|
|
43
|
+
* Zenith Runtime v1.0.1
|
|
44
|
+
* Shared client-side runtime for hydration and reactivity
|
|
45
|
+
*/
|
|
46
|
+
(function(global) {
|
|
47
|
+
'use strict';
|
|
48
|
+
|
|
49
|
+
// Initialize plugin data envelope
|
|
50
|
+
global.__ZENITH_PLUGIN_DATA__ = ${serializedData};
|
|
51
|
+
|
|
52
|
+
${reactivityJS ? ` // ============================================
|
|
53
|
+
// Core Reactivity (Injected from @zenithbuild/core)
|
|
54
|
+
// ============================================
|
|
55
|
+
${reactivityJS}` : ` // Fallback: Reactivity not found`}
|
|
56
|
+
|
|
57
|
+
${lifecycleJS ? ` // ============================================
|
|
58
|
+
// Lifecycle Hooks (Injected from @zenithbuild/core)
|
|
59
|
+
// ============================================
|
|
60
|
+
${lifecycleJS}` : ` // Fallback: Lifecycle not found`}
|
|
61
|
+
|
|
62
|
+
// ============================================
|
|
63
|
+
// Component Instance System
|
|
64
|
+
// ============================================
|
|
65
|
+
// Each component instance gets isolated state, effects, and lifecycles
|
|
66
|
+
// Instances are tied to DOM elements via hydration markers
|
|
67
|
+
|
|
68
|
+
const componentRegistry = {};
|
|
69
|
+
|
|
70
|
+
function createComponentInstance(componentName, rootElement) {
|
|
71
|
+
const instanceMountCallbacks = [];
|
|
72
|
+
const instanceUnmountCallbacks = [];
|
|
73
|
+
const instanceEffects = [];
|
|
74
|
+
let instanceMounted = false;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
// DOM reference
|
|
78
|
+
root: rootElement,
|
|
79
|
+
|
|
80
|
+
// Lifecycle hooks (instance-scoped)
|
|
81
|
+
onMount: function(fn) {
|
|
82
|
+
if (instanceMounted) {
|
|
83
|
+
const cleanup = fn();
|
|
84
|
+
if (typeof cleanup === 'function') {
|
|
85
|
+
instanceUnmountCallbacks.push(cleanup);
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
instanceMountCallbacks.push(fn);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
onUnmount: function(fn) {
|
|
92
|
+
instanceUnmountCallbacks.push(fn);
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// Reactivity (uses global primitives but tracks for cleanup)
|
|
96
|
+
signal: function(initial) {
|
|
97
|
+
return zenSignal(initial);
|
|
98
|
+
},
|
|
99
|
+
state: function(initial) {
|
|
100
|
+
return zenState(initial);
|
|
101
|
+
},
|
|
102
|
+
ref: function(initial) {
|
|
103
|
+
return zenRef(initial);
|
|
104
|
+
},
|
|
105
|
+
effect: function(fn) {
|
|
106
|
+
const cleanup = zenEffect(fn);
|
|
107
|
+
instanceEffects.push(cleanup);
|
|
108
|
+
return cleanup;
|
|
109
|
+
},
|
|
110
|
+
memo: function(fn) {
|
|
111
|
+
return zenMemo(fn);
|
|
112
|
+
},
|
|
113
|
+
batch: function(fn) {
|
|
114
|
+
zenBatch(fn);
|
|
115
|
+
},
|
|
116
|
+
untrack: function(fn) {
|
|
117
|
+
return zenUntrack(fn);
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// Lifecycle execution
|
|
121
|
+
mount: function() {
|
|
122
|
+
instanceMounted = true;
|
|
123
|
+
for (let i = 0; i < instanceMountCallbacks.length; i++) {
|
|
124
|
+
try {
|
|
125
|
+
const cleanup = instanceMountCallbacks[i]();
|
|
126
|
+
if (typeof cleanup === 'function') {
|
|
127
|
+
instanceUnmountCallbacks.push(cleanup);
|
|
128
|
+
}
|
|
129
|
+
} catch(e) {
|
|
130
|
+
console.error('[Zenith] Component mount error:', componentName, e);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
instanceMountCallbacks.length = 0;
|
|
134
|
+
},
|
|
135
|
+
unmount: function() {
|
|
136
|
+
instanceMounted = false;
|
|
137
|
+
// Cleanup effects
|
|
138
|
+
for (let i = 0; i < instanceEffects.length; i++) {
|
|
139
|
+
try {
|
|
140
|
+
if (typeof instanceEffects[i] === 'function') instanceEffects[i]();
|
|
141
|
+
} catch(e) {
|
|
142
|
+
console.error('[Zenith] Effect cleanup error:', e);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
instanceEffects.length = 0;
|
|
146
|
+
// Run unmount callbacks
|
|
147
|
+
for (let i = 0; i < instanceUnmountCallbacks.length; i++) {
|
|
148
|
+
try { instanceUnmountCallbacks[i](); } catch(e) { console.error('[Zenith] Unmount error:', e); }
|
|
149
|
+
}
|
|
150
|
+
instanceUnmountCallbacks.length = 0;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function defineComponent(name, factory) {
|
|
156
|
+
componentRegistry[name] = factory;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function instantiateComponent(name, props, rootElement) {
|
|
160
|
+
const factory = componentRegistry[name];
|
|
161
|
+
if (!factory) {
|
|
162
|
+
if (name === 'ErrorPage') {
|
|
163
|
+
// Built-in fallback for ErrorPage if not registered by user
|
|
164
|
+
return fallbackErrorPage(props, rootElement);
|
|
165
|
+
}
|
|
166
|
+
console.warn('[Zenith] Component not found:', name);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return factory(props, rootElement);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function renderErrorPage(error, metadata) {
|
|
173
|
+
console.error('[Zenith Runtime Error]', error, metadata);
|
|
174
|
+
|
|
175
|
+
// In production, we might want a simpler page, but for now let's use the high-fidelity one
|
|
176
|
+
// if it's available.
|
|
177
|
+
const container = document.getElementById('app') || document.body;
|
|
178
|
+
|
|
179
|
+
// If we've already rendered an error page, don't do it again to avoid infinite loops
|
|
180
|
+
if (window.__ZENITH_ERROR_RENDERED__) return;
|
|
181
|
+
window.__ZENITH_ERROR_RENDERED__ = true;
|
|
182
|
+
|
|
183
|
+
const errorProps = {
|
|
184
|
+
message: error.message || 'Unknown Error',
|
|
185
|
+
stack: error.stack,
|
|
186
|
+
file: metadata.file || (error.file),
|
|
187
|
+
line: metadata.line || (error.line),
|
|
188
|
+
column: metadata.column || (error.column),
|
|
189
|
+
errorType: metadata.errorType || error.name || 'RuntimeError',
|
|
190
|
+
code: metadata.code || 'ERR500',
|
|
191
|
+
context: metadata.context || (metadata.expressionId ? 'Expression: ' + metadata.expressionId : ''),
|
|
192
|
+
hints: metadata.hints || [],
|
|
193
|
+
isProd: false // Check env here if possible
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Try to instantiate the user's ErrorPage
|
|
197
|
+
const instance = instantiateComponent('ErrorPage', errorProps, container);
|
|
198
|
+
if (instance) {
|
|
199
|
+
container.innerHTML = '';
|
|
200
|
+
instance.mount();
|
|
201
|
+
} else {
|
|
202
|
+
// Fallback to basic HTML if ErrorPage component fails or is missing
|
|
203
|
+
container.innerHTML = \`
|
|
204
|
+
<div style="padding: 4rem; font-family: system-ui, sans-serif; background: #000; color: #fff; min-h: 100vh;">
|
|
205
|
+
<h1 style="font-size: 3rem; margin-bottom: 1rem; color: #ef4444;">Zenith Runtime Error</h1>
|
|
206
|
+
<p style="font-size: 1.5rem; opacity: 0.8;">\${errorProps.message}</p>
|
|
207
|
+
<pre style="margin-top: 2rem; padding: 1rem; background: #111; border-radius: 8px; overflow: auto; font-size: 0.8rem; color: #888;">\${errorProps.stack}</pre>
|
|
208
|
+
</div>
|
|
209
|
+
\`;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function fallbackErrorPage(props, el) {
|
|
214
|
+
// This could be a more complex fallback, but for now we just return null
|
|
215
|
+
// to trigger the basic HTML fallback in renderErrorPage.
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Hydrate components by discovering data-zen-component markers
|
|
221
|
+
* This is the ONLY place component instantiation should happen
|
|
222
|
+
*/
|
|
223
|
+
function hydrateComponents(container) {
|
|
224
|
+
try {
|
|
225
|
+
const componentElements = container.querySelectorAll('[data-zen-component]');
|
|
226
|
+
|
|
227
|
+
for (let i = 0; i < componentElements.length; i++) {
|
|
228
|
+
const el = componentElements[i];
|
|
229
|
+
const componentName = el.getAttribute('data-zen-component');
|
|
230
|
+
|
|
231
|
+
// Skip if already hydrated OR if handled by instance script (data-zen-inst)
|
|
232
|
+
if (el.__zenith_instance || el.hasAttribute('data-zen-inst')) continue;
|
|
233
|
+
|
|
234
|
+
// Parse props from data attribute if present
|
|
235
|
+
const propsJson = el.getAttribute('data-zen-props') || '{}';
|
|
236
|
+
let props = {};
|
|
237
|
+
try {
|
|
238
|
+
props = JSON.parse(propsJson);
|
|
239
|
+
} catch(e) {
|
|
240
|
+
console.warn('[Zenith] Invalid props JSON for', componentName);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
// Instantiate component and bind to DOM element
|
|
245
|
+
const instance = instantiateComponent(componentName, props, el);
|
|
246
|
+
|
|
247
|
+
if (instance) {
|
|
248
|
+
el.__zenith_instance = instance;
|
|
249
|
+
}
|
|
250
|
+
} catch (e) {
|
|
251
|
+
renderErrorPage(e, { component: componentName, props: props });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch (e) {
|
|
255
|
+
renderErrorPage(e, { activity: 'hydrateComponents' });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ============================================
|
|
260
|
+
// Expression Registry & Hydration
|
|
261
|
+
// ============================================
|
|
262
|
+
|
|
263
|
+
const expressionRegistry = new Map();
|
|
264
|
+
|
|
265
|
+
function registerExpression(id, fn) {
|
|
266
|
+
expressionRegistry.set(id, fn);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function getExpression(id) {
|
|
270
|
+
return expressionRegistry.get(id);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function updateNode(node, exprId, pageState) {
|
|
274
|
+
const expr = getExpression(exprId);
|
|
275
|
+
if (!expr) return;
|
|
276
|
+
|
|
277
|
+
zenEffect(function() {
|
|
278
|
+
try {
|
|
279
|
+
const result = expr(pageState);
|
|
280
|
+
|
|
281
|
+
if (node.hasAttribute('data-zen-text')) {
|
|
282
|
+
// Handle complex text/children results
|
|
283
|
+
if (result === null || result === undefined || result === false) {
|
|
284
|
+
node.textContent = '';
|
|
285
|
+
} else if (typeof result === 'string') {
|
|
286
|
+
if (result.trim().startsWith('<') && result.trim().endsWith('>')) {
|
|
287
|
+
node.innerHTML = result;
|
|
288
|
+
} else {
|
|
289
|
+
node.textContent = result;
|
|
290
|
+
}
|
|
291
|
+
} else if (result instanceof Node) {
|
|
292
|
+
node.innerHTML = '';
|
|
293
|
+
node.appendChild(result);
|
|
294
|
+
} else if (Array.isArray(result)) {
|
|
295
|
+
node.innerHTML = '';
|
|
296
|
+
const fragment = document.createDocumentFragment();
|
|
297
|
+
result.flat(Infinity).forEach(item => {
|
|
298
|
+
if (item instanceof Node) fragment.appendChild(item);
|
|
299
|
+
else if (item != null && item !== false) fragment.appendChild(document.createTextNode(String(item)));
|
|
300
|
+
});
|
|
301
|
+
node.appendChild(fragment);
|
|
302
|
+
} else {
|
|
303
|
+
node.textContent = String(result);
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
// Attribute update
|
|
307
|
+
const attrNames = ['class', 'style', 'src', 'href', 'disabled', 'checked'];
|
|
308
|
+
for (const attr of attrNames) {
|
|
309
|
+
if (node.hasAttribute('data-zen-attr-' + attr)) {
|
|
310
|
+
if (attr === 'class' || attr === 'className') {
|
|
311
|
+
node.className = String(result || '');
|
|
312
|
+
} else if (attr === 'disabled' || attr === 'checked') {
|
|
313
|
+
if (result) node.setAttribute(attr, '');
|
|
314
|
+
else node.removeAttribute(attr);
|
|
315
|
+
} else {
|
|
316
|
+
if (result != null && result !== false) node.setAttribute(attr, String(result));
|
|
317
|
+
else node.removeAttribute(attr);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} catch (e) {
|
|
323
|
+
renderErrorPage(e, { expressionId: exprId, node: node });
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Hydrate a page with reactive bindings
|
|
330
|
+
* Called after page HTML is in DOM
|
|
331
|
+
*/
|
|
332
|
+
function updateLoopBinding(template, exprId, pageState) {
|
|
333
|
+
const expr = getExpression(exprId);
|
|
334
|
+
if (!expr) return;
|
|
335
|
+
|
|
336
|
+
const itemVar = template.getAttribute('data-zen-item');
|
|
337
|
+
const indexVar = template.getAttribute('data-zen-index');
|
|
338
|
+
|
|
339
|
+
// Use a marker or a container next to the template to hold instances
|
|
340
|
+
let container = template.__zen_container;
|
|
341
|
+
if (!container) {
|
|
342
|
+
container = document.createElement('div');
|
|
343
|
+
container.style.display = 'contents';
|
|
344
|
+
template.parentNode.insertBefore(container, template.nextSibling);
|
|
345
|
+
template.__zen_container = container;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
zenEffect(function() {
|
|
349
|
+
try {
|
|
350
|
+
const items = expr(pageState);
|
|
351
|
+
if (!Array.isArray(items)) return;
|
|
352
|
+
|
|
353
|
+
// Simple reconciliation: clear and redraw
|
|
354
|
+
container.innerHTML = '';
|
|
355
|
+
|
|
356
|
+
items.forEach(function(item, index) {
|
|
357
|
+
const fragment = template.content.cloneNode(true);
|
|
358
|
+
|
|
359
|
+
// Create child scope
|
|
360
|
+
const childState = Object.assign({}, pageState);
|
|
361
|
+
if (itemVar) childState[itemVar] = item;
|
|
362
|
+
if (indexVar) childState[indexVar] = index;
|
|
363
|
+
|
|
364
|
+
// Recursive hydration for the fragment
|
|
365
|
+
zenithHydrate(childState, fragment);
|
|
366
|
+
|
|
367
|
+
container.appendChild(fragment);
|
|
368
|
+
});
|
|
369
|
+
} catch (e) {
|
|
370
|
+
renderErrorPage(e, { expressionId: exprId, activity: 'loopReconciliation' });
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Hydrate static HTML with dynamic expressions
|
|
377
|
+
*/
|
|
378
|
+
function zenithHydrate(pageState, container) {
|
|
379
|
+
try {
|
|
380
|
+
container = container || document;
|
|
381
|
+
|
|
382
|
+
// Find all text expression placeholders
|
|
383
|
+
const textNodes = container.querySelectorAll('[data-zen-text]');
|
|
384
|
+
textNodes.forEach(el => updateNode(el, el.getAttribute('data-zen-text'), pageState));
|
|
385
|
+
|
|
386
|
+
// Find all attribute expression placeholders
|
|
387
|
+
const attrNodes = container.querySelectorAll('[data-zen-attr-class], [data-zen-attr-style], [data-zen-attr-src], [data-zen-attr-href]');
|
|
388
|
+
attrNodes.forEach(el => {
|
|
389
|
+
const attrMatch = Array.from(el.attributes).find(a => a.name.startsWith('data-zen-attr-'));
|
|
390
|
+
if (attrMatch) updateNode(el, attrMatch.value, pageState);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Find all loop placeholders
|
|
394
|
+
const loopNodes = container.querySelectorAll('template[data-zen-loop]');
|
|
395
|
+
loopNodes.forEach(el => updateLoopBinding(el, el.getAttribute('data-zen-loop'), pageState));
|
|
396
|
+
|
|
397
|
+
// Wire up event handlers
|
|
398
|
+
const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
|
|
399
|
+
eventTypes.forEach(eventType => {
|
|
400
|
+
const elements = container.querySelectorAll('[data-zen-' + eventType + ']');
|
|
401
|
+
elements.forEach(el => {
|
|
402
|
+
const handlerName = el.getAttribute('data-zen-' + eventType);
|
|
403
|
+
if (handlerName && (global[handlerName] || getExpression(handlerName))) {
|
|
404
|
+
el.addEventListener(eventType, function(e) {
|
|
405
|
+
const handler = global[handlerName] || getExpression(handlerName);
|
|
406
|
+
if (typeof handler === 'function') handler(e, el);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Trigger mount (only if container is root)
|
|
413
|
+
if (container === document || container.id === 'app' || container.tagName === 'BODY') {
|
|
414
|
+
triggerMount();
|
|
415
|
+
}
|
|
416
|
+
} catch (e) {
|
|
417
|
+
renderErrorPage(e, { activity: 'zenithHydrate' });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ============================================
|
|
422
|
+
// zenith:content - Content Engine
|
|
423
|
+
// ============================================
|
|
424
|
+
|
|
425
|
+
const schemaRegistry = new Map();
|
|
426
|
+
const builtInEnhancers = {
|
|
427
|
+
readTime: (item) => {
|
|
428
|
+
const wordsPerMinute = 200;
|
|
429
|
+
const text = item.content || '';
|
|
430
|
+
const wordCount = text.split(/\\s+/).length;
|
|
431
|
+
const minutes = Math.ceil(wordCount / wordsPerMinute);
|
|
432
|
+
return Object.assign({}, item, { readTime: minutes + ' min' });
|
|
433
|
+
},
|
|
434
|
+
wordCount: (item) => {
|
|
435
|
+
const text = item.content || '';
|
|
436
|
+
const wordCount = text.split(/\\s+/).length;
|
|
437
|
+
return Object.assign({}, item, { wordCount: wordCount });
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
async function applyEnhancers(item, enhancers) {
|
|
442
|
+
let enrichedItem = Object.assign({}, item);
|
|
443
|
+
for (const enhancer of enhancers) {
|
|
444
|
+
if (typeof enhancer === 'string') {
|
|
445
|
+
const fn = builtInEnhancers[enhancer];
|
|
446
|
+
if (fn) enrichedItem = await fn(enrichedItem);
|
|
447
|
+
} else if (typeof enhancer === 'function') {
|
|
448
|
+
enrichedItem = await enhancer(enrichedItem);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return enrichedItem;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
class ZenCollection {
|
|
455
|
+
constructor(items) {
|
|
456
|
+
this.items = [...items];
|
|
457
|
+
this.filters = [];
|
|
458
|
+
this.sortField = null;
|
|
459
|
+
this.sortOrder = 'desc';
|
|
460
|
+
this.limitCount = null;
|
|
461
|
+
this.selectedFields = null;
|
|
462
|
+
this.enhancers = [];
|
|
463
|
+
this._groupByFolder = false;
|
|
464
|
+
}
|
|
465
|
+
where(fn) { this.filters.push(fn); return this; }
|
|
466
|
+
sortBy(field, order = 'desc') { this.sortField = field; this.sortOrder = order; return this; }
|
|
467
|
+
limit(n) { this.limitCount = n; return this; }
|
|
468
|
+
fields(f) { this.selectedFields = f; return this; }
|
|
469
|
+
enhanceWith(e) { this.enhancers.push(e); return this; }
|
|
470
|
+
groupByFolder() { this._groupByFolder = true; return this; }
|
|
471
|
+
get() {
|
|
472
|
+
let results = [...this.items];
|
|
473
|
+
for (const filter of this.filters) results = results.filter(filter);
|
|
474
|
+
if (this.sortField) {
|
|
475
|
+
results.sort((a, b) => {
|
|
476
|
+
const valA = a[this.sortField];
|
|
477
|
+
const valB = b[this.sortField];
|
|
478
|
+
if (valA < valB) return this.sortOrder === 'asc' ? -1 : 1;
|
|
479
|
+
if (valA > valB) return this.sortOrder === 'asc' ? 1 : -1;
|
|
480
|
+
return 0;
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
if (this.limitCount !== null) results = results.slice(0, this.limitCount);
|
|
484
|
+
|
|
485
|
+
// Apply enhancers synchronously if possible
|
|
486
|
+
if (this.enhancers.length > 0) {
|
|
487
|
+
results = results.map(item => {
|
|
488
|
+
let enrichedItem = Object.assign({}, item);
|
|
489
|
+
for (const enhancer of this.enhancers) {
|
|
490
|
+
if (typeof enhancer === 'string') {
|
|
491
|
+
const fn = builtInEnhancers[enhancer];
|
|
492
|
+
if (fn) enrichedItem = fn(enrichedItem);
|
|
493
|
+
} else if (typeof enhancer === 'function') {
|
|
494
|
+
enrichedItem = enhancer(enrichedItem);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return enrichedItem;
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (this.selectedFields) {
|
|
502
|
+
results = results.map(item => {
|
|
503
|
+
const newItem = {};
|
|
504
|
+
this.selectedFields.forEach(f => { newItem[f] = item[f]; });
|
|
505
|
+
return newItem;
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Group by folder if requested
|
|
510
|
+
if (this._groupByFolder) {
|
|
511
|
+
const groups = {};
|
|
512
|
+
const groupOrder = [];
|
|
513
|
+
for (const item of results) {
|
|
514
|
+
// Extract folder from slug (e.g., "getting-started/installation" -> "getting-started")
|
|
515
|
+
const slug = item.slug || item.id || '';
|
|
516
|
+
const parts = slug.split('/');
|
|
517
|
+
const folder = parts.length > 1 ? parts[0] : 'root';
|
|
518
|
+
|
|
519
|
+
if (!groups[folder]) {
|
|
520
|
+
groups[folder] = {
|
|
521
|
+
id: folder,
|
|
522
|
+
title: folder.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
523
|
+
items: []
|
|
524
|
+
};
|
|
525
|
+
groupOrder.push(folder);
|
|
526
|
+
}
|
|
527
|
+
groups[folder].items.push(item);
|
|
528
|
+
}
|
|
529
|
+
return groupOrder.map(f => groups[f]);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return results;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function defineSchema(name, schema) { schemaRegistry.set(name, schema); }
|
|
537
|
+
|
|
538
|
+
function zenCollection(collectionName) {
|
|
539
|
+
// Access plugin data from the neutral envelope
|
|
540
|
+
// Content plugin stores all items under 'content' namespace
|
|
541
|
+
const pluginData = global.__ZENITH_PLUGIN_DATA__ || {};
|
|
542
|
+
const contentItems = pluginData.content || [];
|
|
543
|
+
|
|
544
|
+
// Filter by collection name (plugin owns data structure, runtime just filters)
|
|
545
|
+
const data = Array.isArray(contentItems)
|
|
546
|
+
? contentItems.filter(item => item && item.collection === collectionName)
|
|
547
|
+
: [];
|
|
548
|
+
|
|
549
|
+
return new ZenCollection(data);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ============================================
|
|
553
|
+
// useZenOrder - Documentation ordering & navigation
|
|
554
|
+
// ============================================
|
|
555
|
+
|
|
556
|
+
function slugify(text) {
|
|
557
|
+
return String(text || '')
|
|
558
|
+
.toLowerCase()
|
|
559
|
+
.replace(/[^\\w\\s-]/g, '')
|
|
560
|
+
.replace(/\\s+/g, '-')
|
|
561
|
+
.replace(/-+/g, '-')
|
|
562
|
+
.trim();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function getDocSlug(doc) {
|
|
566
|
+
const slugOrId = String(doc.slug || doc.id || '');
|
|
567
|
+
const parts = slugOrId.split('/');
|
|
568
|
+
const filename = parts[parts.length - 1];
|
|
569
|
+
return filename ? slugify(filename) : slugify(doc.title || 'untitled');
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function processRawSections(rawSections) {
|
|
573
|
+
const sections = (rawSections || []).map(function(rawSection) {
|
|
574
|
+
const sectionSlug = slugify(rawSection.title || rawSection.id || 'section');
|
|
575
|
+
const items = (rawSection.items || []).map(function(item) {
|
|
576
|
+
return Object.assign({}, item, {
|
|
577
|
+
slug: getDocSlug(item),
|
|
578
|
+
sectionSlug: sectionSlug,
|
|
579
|
+
isIntro: item.intro === true || (item.tags && item.tags.includes && item.tags.includes('intro'))
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// Sort items: intro first, then order, then alphabetical
|
|
584
|
+
items.sort(function(a, b) {
|
|
585
|
+
if (a.isIntro && !b.isIntro) return -1;
|
|
586
|
+
if (!a.isIntro && b.isIntro) return 1;
|
|
587
|
+
if (a.order !== undefined && b.order !== undefined) return a.order - b.order;
|
|
588
|
+
if (a.order !== undefined) return -1;
|
|
589
|
+
if (b.order !== undefined) return 1;
|
|
590
|
+
return (a.title || '').localeCompare(b.title || '');
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
return {
|
|
594
|
+
id: rawSection.id || sectionSlug,
|
|
595
|
+
title: rawSection.title || 'Untitled',
|
|
596
|
+
slug: sectionSlug,
|
|
597
|
+
order: rawSection.order !== undefined ? rawSection.order : (rawSection.meta && rawSection.meta.order),
|
|
598
|
+
hasIntro: items.some(function(i) { return i.isIntro; }),
|
|
599
|
+
items: items
|
|
600
|
+
};
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// Sort sections: order → hasIntro → alphabetical
|
|
604
|
+
sections.sort(function(a, b) {
|
|
605
|
+
if (a.order !== undefined && b.order !== undefined) return a.order - b.order;
|
|
606
|
+
if (a.order !== undefined) return -1;
|
|
607
|
+
if (b.order !== undefined) return 1;
|
|
608
|
+
if (a.hasIntro && !b.hasIntro) return -1;
|
|
609
|
+
if (!a.hasIntro && b.hasIntro) return 1;
|
|
610
|
+
return a.title.localeCompare(b.title);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
return sections;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function createZenOrder(rawSections) {
|
|
617
|
+
const sections = processRawSections(rawSections);
|
|
618
|
+
|
|
619
|
+
return {
|
|
620
|
+
sections: sections,
|
|
621
|
+
selectedSection: sections[0] || null,
|
|
622
|
+
selectedDoc: sections[0] && sections[0].items[0] || null,
|
|
623
|
+
|
|
624
|
+
getSectionBySlug: function(sectionSlug) {
|
|
625
|
+
return sections.find(function(s) { return s.slug === sectionSlug; }) || null;
|
|
626
|
+
},
|
|
627
|
+
|
|
628
|
+
getDocBySlug: function(sectionSlug, docSlug) {
|
|
629
|
+
var section = sections.find(function(s) { return s.slug === sectionSlug; });
|
|
630
|
+
if (!section) return null;
|
|
631
|
+
return section.items.find(function(d) { return d.slug === docSlug; }) || null;
|
|
632
|
+
},
|
|
633
|
+
|
|
634
|
+
getNextDoc: function(currentDoc) {
|
|
635
|
+
if (!currentDoc) return null;
|
|
636
|
+
var currentSection = sections.find(function(s) { return s.slug === currentDoc.sectionSlug; });
|
|
637
|
+
if (!currentSection) return null;
|
|
638
|
+
var idx = currentSection.items.findIndex(function(d) { return d.slug === currentDoc.slug; });
|
|
639
|
+
if (idx < currentSection.items.length - 1) return currentSection.items[idx + 1];
|
|
640
|
+
var secIdx = sections.findIndex(function(s) { return s.slug === currentSection.slug; });
|
|
641
|
+
if (secIdx < sections.length - 1) return sections[secIdx + 1].items[0] || null;
|
|
642
|
+
return null;
|
|
643
|
+
},
|
|
644
|
+
|
|
645
|
+
getPrevDoc: function(currentDoc) {
|
|
646
|
+
if (!currentDoc) return null;
|
|
647
|
+
var currentSection = sections.find(function(s) { return s.slug === currentDoc.sectionSlug; });
|
|
648
|
+
if (!currentSection) return null;
|
|
649
|
+
var idx = currentSection.items.findIndex(function(d) { return d.slug === currentDoc.slug; });
|
|
650
|
+
if (idx > 0) return currentSection.items[idx - 1];
|
|
651
|
+
var secIdx = sections.findIndex(function(s) { return s.slug === currentSection.slug; });
|
|
652
|
+
if (secIdx > 0) {
|
|
653
|
+
var prev = sections[secIdx - 1];
|
|
654
|
+
return prev.items[prev.items.length - 1] || null;
|
|
655
|
+
}
|
|
656
|
+
return null;
|
|
657
|
+
},
|
|
658
|
+
|
|
659
|
+
buildDocUrl: function(sectionSlug, docSlug) {
|
|
660
|
+
if (!docSlug || docSlug === 'index') return '/documentation/' + sectionSlug;
|
|
661
|
+
return '/documentation/' + sectionSlug + '/' + docSlug;
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// ============================================
|
|
667
|
+
// Virtual DOM Helper for JSX-style expressions
|
|
668
|
+
// ============================================
|
|
669
|
+
|
|
670
|
+
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
|
671
|
+
const SVG_TAGS = new Set([
|
|
672
|
+
'svg', 'path', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'rect',
|
|
673
|
+
'g', 'defs', 'clipPath', 'mask', 'use', 'symbol', 'text', 'tspan', 'textPath',
|
|
674
|
+
'image', 'foreignObject', 'switch', 'desc', 'title', 'metadata', 'linearGradient',
|
|
675
|
+
'radialGradient', 'stop', 'pattern', 'filter', 'feBlend', 'feColorMatrix',
|
|
676
|
+
'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting',
|
|
677
|
+
'feDisplacementMap', 'feFlood', 'feGaussianBlur', 'feImage', 'feMerge',
|
|
678
|
+
'feMergeNode', 'feMorphology', 'feOffset', 'feSpecularLighting', 'feTile',
|
|
679
|
+
'feTurbulence', 'animate', 'animateMotion', 'animateTransform', 'set', 'marker'
|
|
680
|
+
]);
|
|
681
|
+
|
|
682
|
+
// Track current namespace context for nested elements
|
|
683
|
+
let currentNamespace = null;
|
|
684
|
+
|
|
685
|
+
function h(tag, props, children) {
|
|
686
|
+
// Determine if this element should be in SVG namespace
|
|
687
|
+
const isSvgTag = SVG_TAGS.has(tag) || SVG_TAGS.has(tag.toLowerCase());
|
|
688
|
+
const useSvgNamespace = isSvgTag || currentNamespace === SVG_NAMESPACE;
|
|
689
|
+
|
|
690
|
+
// Create element with correct namespace
|
|
691
|
+
const el = useSvgNamespace
|
|
692
|
+
? document.createElementNS(SVG_NAMESPACE, tag)
|
|
693
|
+
: document.createElement(tag);
|
|
694
|
+
|
|
695
|
+
// Track namespace context for children (save previous, set new if svg root)
|
|
696
|
+
const previousNamespace = currentNamespace;
|
|
697
|
+
if (tag === 'svg' || tag === 'SVG') {
|
|
698
|
+
currentNamespace = SVG_NAMESPACE;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (props) {
|
|
702
|
+
// Helper to set class for both HTML and SVG elements
|
|
703
|
+
const setClass = (element, value) => {
|
|
704
|
+
if (useSvgNamespace && 'className' in element && typeof element.className === 'object') {
|
|
705
|
+
element.className.baseVal = String(value || '');
|
|
706
|
+
} else {
|
|
707
|
+
element.className = String(value || '');
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
for (const [key, value] of Object.entries(props)) {
|
|
712
|
+
if (key.startsWith('on') && typeof value === 'function') {
|
|
713
|
+
el.addEventListener(key.slice(2).toLowerCase(), value);
|
|
714
|
+
} else if (key === 'class' || key === 'className') {
|
|
715
|
+
if (typeof value === 'function') {
|
|
716
|
+
// Reactive class binding
|
|
717
|
+
zenEffect(function() {
|
|
718
|
+
setClass(el, value());
|
|
719
|
+
});
|
|
720
|
+
} else {
|
|
721
|
+
setClass(el, value);
|
|
722
|
+
}
|
|
723
|
+
} else if (key === 'style' && typeof value === 'object') {
|
|
724
|
+
Object.assign(el.style, value);
|
|
725
|
+
} else if (typeof value === 'function') {
|
|
726
|
+
// Reactive attribute binding
|
|
727
|
+
zenEffect(function() {
|
|
728
|
+
const v = value();
|
|
729
|
+
if (v != null && v !== false) {
|
|
730
|
+
el.setAttribute(key, String(v));
|
|
731
|
+
} else {
|
|
732
|
+
el.removeAttribute(key);
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
} else if (value != null && value !== false) {
|
|
736
|
+
el.setAttribute(key, String(value));
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (children != null && children !== false) {
|
|
742
|
+
// Flatten nested arrays (from .map() calls)
|
|
743
|
+
const childrenArray = Array.isArray(children) ? children.flat(Infinity) : [children];
|
|
744
|
+
for (const child of childrenArray) {
|
|
745
|
+
// Skip null, undefined, and false
|
|
746
|
+
if (child == null || child === false) continue;
|
|
747
|
+
|
|
748
|
+
if (typeof child === 'function') {
|
|
749
|
+
// Reactive child - use a placeholder and update reactively
|
|
750
|
+
const placeholder = document.createComment('expr');
|
|
751
|
+
el.appendChild(placeholder);
|
|
752
|
+
let currentNodes = [];
|
|
753
|
+
|
|
754
|
+
zenEffect(function() {
|
|
755
|
+
const result = child();
|
|
756
|
+
// Remove old nodes
|
|
757
|
+
for (let i = 0; i < currentNodes.length; i++) {
|
|
758
|
+
const n = currentNodes[i];
|
|
759
|
+
if (n.parentNode) n.parentNode.removeChild(n);
|
|
760
|
+
}
|
|
761
|
+
currentNodes = [];
|
|
762
|
+
|
|
763
|
+
if (result == null || result === false) {
|
|
764
|
+
// Render nothing
|
|
765
|
+
} else if (result instanceof Node) {
|
|
766
|
+
placeholder.parentNode.insertBefore(result, placeholder);
|
|
767
|
+
currentNodes = [result];
|
|
768
|
+
} else if (Array.isArray(result)) {
|
|
769
|
+
// Array of nodes/strings
|
|
770
|
+
const flat = result.flat(Infinity);
|
|
771
|
+
for (let i = 0; i < flat.length; i++) {
|
|
772
|
+
const item = flat[i];
|
|
773
|
+
if (item == null || item === false) continue;
|
|
774
|
+
const node = item instanceof Node ? item : document.createTextNode(String(item));
|
|
775
|
+
placeholder.parentNode.insertBefore(node, placeholder);
|
|
776
|
+
currentNodes.push(node);
|
|
777
|
+
}
|
|
778
|
+
} else {
|
|
779
|
+
// Primitive (string, number)
|
|
780
|
+
const textNode = document.createTextNode(String(result));
|
|
781
|
+
placeholder.parentNode.insertBefore(textNode, placeholder);
|
|
782
|
+
currentNodes = [textNode];
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
} else {
|
|
786
|
+
// Static child
|
|
787
|
+
el.appendChild(child instanceof Node ? child : document.createTextNode(String(child)));
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Restore previous namespace context
|
|
793
|
+
currentNamespace = previousNamespace;
|
|
794
|
+
|
|
795
|
+
return el;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
// ============================================
|
|
800
|
+
// Export to window.__zenith
|
|
801
|
+
// ============================================
|
|
802
|
+
|
|
803
|
+
global.__zenith = {
|
|
804
|
+
// Reactivity primitives
|
|
805
|
+
signal: zenSignal,
|
|
806
|
+
state: zenState,
|
|
807
|
+
effect: zenEffect,
|
|
808
|
+
memo: zenMemo,
|
|
809
|
+
ref: zenRef,
|
|
810
|
+
batch: zenBatch,
|
|
811
|
+
untrack: zenUntrack,
|
|
812
|
+
// zenith:content
|
|
813
|
+
defineSchema: defineSchema,
|
|
814
|
+
zenCollection: zenCollection,
|
|
815
|
+
// useZenOrder hook
|
|
816
|
+
createZenOrder: createZenOrder,
|
|
817
|
+
processRawSections: processRawSections,
|
|
818
|
+
slugify: slugify,
|
|
819
|
+
// Virtual DOM helper for JSX
|
|
820
|
+
h: h,
|
|
821
|
+
// Lifecycle
|
|
822
|
+
onMount: zenOnMount,
|
|
823
|
+
onUnmount: zenOnUnmount,
|
|
824
|
+
// Internal hooks
|
|
825
|
+
triggerMount: triggerMount,
|
|
826
|
+
triggerUnmount: triggerUnmount,
|
|
827
|
+
// Hydration
|
|
828
|
+
hydrate: zenithHydrate,
|
|
829
|
+
hydrateComponents: hydrateComponents, // Marker-driven component instantiation
|
|
830
|
+
registerExpression: registerExpression,
|
|
831
|
+
getExpression: getExpression,
|
|
832
|
+
// Component instance system
|
|
833
|
+
createInstance: createComponentInstance,
|
|
834
|
+
defineComponent: defineComponent,
|
|
835
|
+
instantiate: instantiateComponent
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
// Expose with zen* prefix for direct usage
|
|
839
|
+
global.zenSignal = zenSignal;
|
|
840
|
+
global.zenState = zenState;
|
|
841
|
+
global.zenEffect = zenEffect;
|
|
842
|
+
global.zenMemo = zenMemo;
|
|
843
|
+
global.zenRef = zenRef;
|
|
844
|
+
global.zenBatch = zenBatch;
|
|
845
|
+
global.zenUntrack = zenUntrack;
|
|
846
|
+
global.zenOnMount = zenOnMount;
|
|
847
|
+
global.zenOnUnmount = zenOnUnmount;
|
|
848
|
+
global.zenithHydrate = zenithHydrate;
|
|
849
|
+
|
|
850
|
+
// Clean aliases
|
|
851
|
+
global.signal = zenSignal;
|
|
852
|
+
global.state = zenState;
|
|
853
|
+
global.effect = zenEffect;
|
|
854
|
+
global.memo = zenMemo;
|
|
855
|
+
global.ref = zenRef;
|
|
856
|
+
global.batch = zenBatch;
|
|
857
|
+
global.untrack = zenUntrack;
|
|
858
|
+
global.onMount = zenOnMount;
|
|
859
|
+
global.onUnmount = zenOnUnmount;
|
|
860
|
+
|
|
861
|
+
// useZenOrder hook exports
|
|
862
|
+
global.createZenOrder = createZenOrder;
|
|
863
|
+
global.processRawSections = processRawSections;
|
|
864
|
+
global.slugify = slugify;
|
|
865
|
+
|
|
866
|
+
// ============================================
|
|
867
|
+
// SPA Router Runtime
|
|
868
|
+
// ============================================
|
|
869
|
+
|
|
870
|
+
// Router state
|
|
871
|
+
// Current route state
|
|
872
|
+
var currentRoute = {
|
|
873
|
+
path: '/',
|
|
874
|
+
params: {},
|
|
875
|
+
query: {}
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
// Route listeners
|
|
879
|
+
var routeListeners = new Set();
|
|
880
|
+
|
|
881
|
+
// Router outlet element
|
|
882
|
+
var routerOutlet = null;
|
|
883
|
+
|
|
884
|
+
// Page modules registry
|
|
885
|
+
var pageModules = {};
|
|
886
|
+
|
|
887
|
+
// Route manifest
|
|
888
|
+
var routeManifest = [];
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Parse query string
|
|
892
|
+
*/
|
|
893
|
+
function parseQueryString(search) {
|
|
894
|
+
var query = {};
|
|
895
|
+
if (!search || search === '?') return query;
|
|
896
|
+
var params = new URLSearchParams(search);
|
|
897
|
+
params.forEach(function(value, key) { query[key] = value; });
|
|
898
|
+
return query;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// ============================================
|
|
902
|
+
// Global Error Listeners (Dev Mode)
|
|
903
|
+
// ============================================
|
|
904
|
+
|
|
905
|
+
window.onerror = function(message, source, lineno, colno, error) {
|
|
906
|
+
renderErrorPage(error || new Error(message), {
|
|
907
|
+
file: source,
|
|
908
|
+
line: lineno,
|
|
909
|
+
column: colno,
|
|
910
|
+
errorType: 'UncaughtError'
|
|
911
|
+
});
|
|
912
|
+
return false;
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
window.onunhandledrejection = function(event) {
|
|
916
|
+
renderErrorPage(event.reason || new Error('Unhandled Promise Rejection'), {
|
|
917
|
+
errorType: 'UnhandledRejection'
|
|
918
|
+
});
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Resolve route from pathname
|
|
923
|
+
*/
|
|
924
|
+
function resolveRoute(pathname) {
|
|
925
|
+
var normalizedPath = pathname === '' ? '/' : pathname;
|
|
926
|
+
|
|
927
|
+
for (var i = 0; i < routeManifest.length; i++) {
|
|
928
|
+
var route = routeManifest[i];
|
|
929
|
+
var match = route.regex.exec(normalizedPath);
|
|
930
|
+
if (match) {
|
|
931
|
+
var params = {};
|
|
932
|
+
for (var j = 0; j < route.paramNames.length; j++) {
|
|
933
|
+
var paramValue = match[j + 1];
|
|
934
|
+
if (paramValue !== undefined) {
|
|
935
|
+
params[route.paramNames[j]] = decodeURIComponent(paramValue);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
return { record: route, params: params };
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* Clean up previous page
|
|
946
|
+
*/
|
|
947
|
+
function cleanupPreviousPage() {
|
|
948
|
+
// Trigger unmount lifecycle hooks
|
|
949
|
+
if (global.__zenith && global.__zenith.triggerUnmount) {
|
|
950
|
+
global.__zenith.triggerUnmount();
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Remove previous page styles
|
|
954
|
+
var prevStyles = document.querySelectorAll('style[data-zen-page-style]');
|
|
955
|
+
prevStyles.forEach(function(s) { s.remove(); });
|
|
956
|
+
|
|
957
|
+
// Clean up window properties
|
|
958
|
+
if (global.__zenith_cleanup) {
|
|
959
|
+
global.__zenith_cleanup.forEach(function(key) {
|
|
960
|
+
try { delete global[key]; } catch(e) {}
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
global.__zenith_cleanup = [];
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Inject styles
|
|
968
|
+
*/
|
|
969
|
+
function injectStyles(styles) {
|
|
970
|
+
styles.forEach(function(content, i) {
|
|
971
|
+
var style = document.createElement('style');
|
|
972
|
+
style.setAttribute('data-zen-page-style', String(i));
|
|
973
|
+
style.textContent = content;
|
|
974
|
+
document.head.appendChild(style);
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Execute scripts
|
|
980
|
+
*/
|
|
981
|
+
function executeScripts(scripts) {
|
|
982
|
+
scripts.forEach(function(content) {
|
|
983
|
+
try {
|
|
984
|
+
var fn = new Function(content);
|
|
985
|
+
fn();
|
|
986
|
+
} catch (e) {
|
|
987
|
+
console.error('[Zenith Router] Script error:', e);
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Render page
|
|
994
|
+
*/
|
|
995
|
+
function renderPage(pageModule) {
|
|
996
|
+
if (!routerOutlet) {
|
|
997
|
+
console.warn('[Zenith Router] No router outlet');
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
cleanupPreviousPage();
|
|
1002
|
+
routerOutlet.innerHTML = pageModule.html;
|
|
1003
|
+
injectStyles(pageModule.styles);
|
|
1004
|
+
executeScripts(pageModule.scripts);
|
|
1005
|
+
|
|
1006
|
+
// Trigger mount lifecycle hooks after scripts are executed
|
|
1007
|
+
if (global.__zenith && global.__zenith.triggerMount) {
|
|
1008
|
+
global.__zenith.triggerMount();
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Notify listeners
|
|
1014
|
+
*/
|
|
1015
|
+
function notifyListeners(route, prevRoute) {
|
|
1016
|
+
routeListeners.forEach(function(listener) {
|
|
1017
|
+
try { listener(route, prevRoute); } catch(e) {}
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Resolve and render
|
|
1023
|
+
*/
|
|
1024
|
+
function resolveAndRender(path, query, updateHistory, replace) {
|
|
1025
|
+
replace = replace || false;
|
|
1026
|
+
var prevRoute = Object.assign({}, currentRoute);
|
|
1027
|
+
var resolved = resolveRoute(path);
|
|
1028
|
+
|
|
1029
|
+
if (resolved) {
|
|
1030
|
+
currentRoute = {
|
|
1031
|
+
path: path,
|
|
1032
|
+
params: resolved.params,
|
|
1033
|
+
query: query,
|
|
1034
|
+
matched: resolved.record
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
var pageModule = pageModules[resolved.record.path];
|
|
1038
|
+
if (pageModule) {
|
|
1039
|
+
renderPage(pageModule);
|
|
1040
|
+
}
|
|
1041
|
+
} else {
|
|
1042
|
+
currentRoute = { path: path, params: {}, query: query, matched: undefined };
|
|
1043
|
+
console.warn('[Zenith Router] No route matched:', path);
|
|
1044
|
+
|
|
1045
|
+
// Render 404 if available
|
|
1046
|
+
if (routerOutlet) {
|
|
1047
|
+
routerOutlet.innerHTML = '<div style="padding: 2rem; text-align: center;"><h1>404</h1><p>Page not found</p></div>';
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (updateHistory) {
|
|
1052
|
+
var url = path + (Object.keys(query).length ? '?' + new URLSearchParams(query) : '');
|
|
1053
|
+
if (replace) {
|
|
1054
|
+
history.replaceState(null, '', url);
|
|
1055
|
+
} else {
|
|
1056
|
+
history.pushState(null, '', url);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
notifyListeners(currentRoute, prevRoute);
|
|
1061
|
+
global.__zenith_route = currentRoute;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* Handle popstate
|
|
1066
|
+
*/
|
|
1067
|
+
function handlePopState() {
|
|
1068
|
+
resolveAndRender(
|
|
1069
|
+
location.pathname,
|
|
1070
|
+
parseQueryString(location.search),
|
|
1071
|
+
false,
|
|
1072
|
+
false
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* Navigate (public API)
|
|
1078
|
+
*/
|
|
1079
|
+
function navigate(to, options) {
|
|
1080
|
+
options = options || {};
|
|
1081
|
+
var path, query = {};
|
|
1082
|
+
|
|
1083
|
+
if (to.includes('?')) {
|
|
1084
|
+
var parts = to.split('?');
|
|
1085
|
+
path = parts[0];
|
|
1086
|
+
query = parseQueryString('?' + parts[1]);
|
|
1087
|
+
} else {
|
|
1088
|
+
path = to;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
if (!path.startsWith('/')) {
|
|
1092
|
+
var currentDir = currentRoute.path.split('/').slice(0, -1).join('/');
|
|
1093
|
+
path = currentDir + '/' + path;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
var normalizedPath = path === '' ? '/' : path;
|
|
1097
|
+
var currentPath = currentRoute.path === '' ? '/' : currentRoute.path;
|
|
1098
|
+
var isSamePath = normalizedPath === currentPath;
|
|
1099
|
+
|
|
1100
|
+
if (isSamePath && JSON.stringify(query) === JSON.stringify(currentRoute.query)) {
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Dev mode: If no route manifest is loaded, use browser navigation
|
|
1105
|
+
// This allows ZenLink to work in dev server where pages are served fresh
|
|
1106
|
+
if (routeManifest.length === 0) {
|
|
1107
|
+
var url = normalizedPath + (Object.keys(query).length ? '?' + new URLSearchParams(query) : '');
|
|
1108
|
+
if (options.replace) {
|
|
1109
|
+
location.replace(url);
|
|
1110
|
+
} else {
|
|
1111
|
+
location.href = url;
|
|
1112
|
+
}
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
resolveAndRender(path, query, true, options.replace || false);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* Get current route
|
|
1121
|
+
*/
|
|
1122
|
+
function getRoute() {
|
|
1123
|
+
return Object.assign({}, currentRoute);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* Subscribe to route changes
|
|
1128
|
+
*/
|
|
1129
|
+
function onRouteChange(listener) {
|
|
1130
|
+
routeListeners.add(listener);
|
|
1131
|
+
return function() { routeListeners.delete(listener); };
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* Check if path is active
|
|
1136
|
+
*/
|
|
1137
|
+
function isActive(path, exact) {
|
|
1138
|
+
if (exact) return currentRoute.path === path;
|
|
1139
|
+
return currentRoute.path.startsWith(path);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
* Prefetch a route
|
|
1144
|
+
*/
|
|
1145
|
+
var prefetchedRoutes = new Set();
|
|
1146
|
+
function prefetch(path) {
|
|
1147
|
+
var normalizedPath = path === '' ? '/' : path;
|
|
1148
|
+
|
|
1149
|
+
if (prefetchedRoutes.has(normalizedPath)) {
|
|
1150
|
+
return Promise.resolve();
|
|
1151
|
+
}
|
|
1152
|
+
prefetchedRoutes.add(normalizedPath);
|
|
1153
|
+
|
|
1154
|
+
var resolved = resolveRoute(normalizedPath);
|
|
1155
|
+
if (!resolved) {
|
|
1156
|
+
return Promise.resolve();
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// In SPA build, all modules are already loaded
|
|
1160
|
+
return Promise.resolve();
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* Initialize router
|
|
1165
|
+
*/
|
|
1166
|
+
function initRouter(manifest, modules, outlet) {
|
|
1167
|
+
routeManifest = manifest;
|
|
1168
|
+
Object.assign(pageModules, modules);
|
|
1169
|
+
|
|
1170
|
+
if (outlet) {
|
|
1171
|
+
routerOutlet = typeof outlet === 'string'
|
|
1172
|
+
? document.querySelector(outlet)
|
|
1173
|
+
: outlet;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
window.addEventListener('popstate', handlePopState);
|
|
1177
|
+
|
|
1178
|
+
// Initial route resolution
|
|
1179
|
+
resolveAndRender(
|
|
1180
|
+
location.pathname,
|
|
1181
|
+
parseQueryString(location.search),
|
|
1182
|
+
false
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// Expose router API globally
|
|
1187
|
+
global.__zenith_router = {
|
|
1188
|
+
navigate: navigate,
|
|
1189
|
+
getRoute: getRoute,
|
|
1190
|
+
onRouteChange: onRouteChange,
|
|
1191
|
+
isActive: isActive,
|
|
1192
|
+
prefetch: prefetch,
|
|
1193
|
+
initRouter: initRouter
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
// Also expose navigate directly for convenience
|
|
1197
|
+
global.navigate = navigate;
|
|
1198
|
+
global.zenCollection = zenCollection;
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
// ============================================
|
|
1202
|
+
// HMR Client (Development Only)
|
|
1203
|
+
// ============================================
|
|
1204
|
+
|
|
1205
|
+
if (typeof window !== 'undefined' && (location.hostname === 'localhost' || location.hostname === '127.0.0.1')) {
|
|
1206
|
+
let socket;
|
|
1207
|
+
function connectHMR() {
|
|
1208
|
+
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1209
|
+
socket = new WebSocket(protocol + '//' + location.host + '/hmr');
|
|
1210
|
+
|
|
1211
|
+
socket.onmessage = function(event) {
|
|
1212
|
+
try {
|
|
1213
|
+
const data = JSON.parse(event.data);
|
|
1214
|
+
if (data.type === 'reload') {
|
|
1215
|
+
console.log('[Zenith] HMR: Reloading page...');
|
|
1216
|
+
location.reload();
|
|
1217
|
+
} else if (data.type === 'style-update') {
|
|
1218
|
+
console.log('[Zenith] HMR: Updating style ' + data.url);
|
|
1219
|
+
const links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
1220
|
+
for (let i = 0; i < links.length; i++) {
|
|
1221
|
+
const link = links[i];
|
|
1222
|
+
const url = new URL(link.href);
|
|
1223
|
+
if (url.pathname === data.url) {
|
|
1224
|
+
link.href = data.url + '?t=' + Date.now();
|
|
1225
|
+
break;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
} catch (e) {
|
|
1230
|
+
console.error('[Zenith] HMR Error:', e);
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
|
|
1234
|
+
socket.onclose = function() {
|
|
1235
|
+
console.log('[Zenith] HMR: Connection closed. Retrying in 2s...');
|
|
1236
|
+
setTimeout(connectHMR, 2000);
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// Connect unless explicitly disabled
|
|
1241
|
+
if (!window.__ZENITH_NO_HMR__) {
|
|
1242
|
+
connectHMR();
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
})(typeof window !== 'undefined' ? window : this);
|
|
1247
|
+
`;
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Generate a minified version of the bundle
|
|
1251
|
+
* For production builds
|
|
1252
|
+
*/
|
|
1253
|
+
export function generateMinifiedBundleJS() {
|
|
1254
|
+
// For now, return non-minified
|
|
1255
|
+
// TODO: Add minification via terser or similar
|
|
1256
|
+
return generateBundleJS();
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Get bundle version for cache busting
|
|
1260
|
+
*/
|
|
1261
|
+
export function getBundleVersion() {
|
|
1262
|
+
return '0.1.0';
|
|
1263
|
+
}
|