@zenithbuild/core 1.2.2 → 1.2.3

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 (83) hide show
  1. package/README.md +20 -19
  2. package/cli/commands/add.ts +2 -2
  3. package/cli/commands/build.ts +2 -3
  4. package/cli/commands/dev.ts +93 -73
  5. package/cli/commands/index.ts +1 -1
  6. package/cli/commands/preview.ts +1 -1
  7. package/cli/commands/remove.ts +2 -2
  8. package/cli/index.ts +1 -1
  9. package/cli/main.ts +1 -1
  10. package/cli/utils/logger.ts +1 -1
  11. package/cli/utils/plugin-manager.ts +1 -1
  12. package/cli/utils/project.ts +4 -4
  13. package/core/components/ErrorPage.zen +218 -0
  14. package/core/components/index.ts +15 -0
  15. package/core/config.ts +1 -0
  16. package/core/index.ts +29 -0
  17. package/dist/compiler-native-frej59m4.node +0 -0
  18. package/dist/core/compiler-native-frej59m4.node +0 -0
  19. package/dist/core/index.js +6293 -0
  20. package/dist/runtime/lifecycle/index.js +1 -0
  21. package/dist/runtime/reactivity/index.js +1 -0
  22. package/dist/zen-build.js +1 -20118
  23. package/dist/zen-dev.js +1 -20118
  24. package/dist/zen-preview.js +1 -20118
  25. package/dist/zenith.js +1 -20118
  26. package/package.json +11 -20
  27. package/compiler/README.md +0 -380
  28. package/compiler/build-analyzer.ts +0 -122
  29. package/compiler/css/index.ts +0 -317
  30. package/compiler/discovery/componentDiscovery.ts +0 -242
  31. package/compiler/discovery/layouts.ts +0 -70
  32. package/compiler/errors/compilerError.ts +0 -56
  33. package/compiler/finalize/finalizeOutput.ts +0 -192
  34. package/compiler/finalize/generateFinalBundle.ts +0 -82
  35. package/compiler/index.ts +0 -83
  36. package/compiler/ir/types.ts +0 -174
  37. package/compiler/output/types.ts +0 -48
  38. package/compiler/parse/detectMapExpressions.ts +0 -102
  39. package/compiler/parse/importTypes.ts +0 -78
  40. package/compiler/parse/parseImports.ts +0 -309
  41. package/compiler/parse/parseScript.ts +0 -46
  42. package/compiler/parse/parseTemplate.ts +0 -628
  43. package/compiler/parse/parseZenFile.ts +0 -66
  44. package/compiler/parse/scriptAnalysis.ts +0 -91
  45. package/compiler/parse/trackLoopContext.ts +0 -82
  46. package/compiler/runtime/dataExposure.ts +0 -332
  47. package/compiler/runtime/generateDOM.ts +0 -255
  48. package/compiler/runtime/generateHydrationBundle.ts +0 -407
  49. package/compiler/runtime/hydration.ts +0 -309
  50. package/compiler/runtime/navigation.ts +0 -432
  51. package/compiler/runtime/thinRuntime.ts +0 -160
  52. package/compiler/runtime/transformIR.ts +0 -406
  53. package/compiler/runtime/wrapExpression.ts +0 -114
  54. package/compiler/runtime/wrapExpressionWithLoop.ts +0 -97
  55. package/compiler/spa-build.ts +0 -917
  56. package/compiler/ssg-build.ts +0 -486
  57. package/compiler/test/component-stacking.test.ts +0 -365
  58. package/compiler/test/map-lowering.test.ts +0 -130
  59. package/compiler/test/validate-test.ts +0 -104
  60. package/compiler/transform/classifyExpression.ts +0 -444
  61. package/compiler/transform/componentResolver.ts +0 -350
  62. package/compiler/transform/componentScriptTransformer.ts +0 -303
  63. package/compiler/transform/expressionTransformer.ts +0 -385
  64. package/compiler/transform/fragmentLowering.ts +0 -819
  65. package/compiler/transform/generateBindings.ts +0 -68
  66. package/compiler/transform/generateHTML.ts +0 -28
  67. package/compiler/transform/layoutProcessor.ts +0 -132
  68. package/compiler/transform/slotResolver.ts +0 -292
  69. package/compiler/transform/transformNode.ts +0 -314
  70. package/compiler/transform/transformTemplate.ts +0 -38
  71. package/compiler/validate/invariants.ts +0 -292
  72. package/compiler/validate/validateExpressions.ts +0 -168
  73. package/core/config/index.ts +0 -18
  74. package/core/config/loader.ts +0 -69
  75. package/core/config/types.ts +0 -119
  76. package/core/plugins/bridge.ts +0 -193
  77. package/core/plugins/index.ts +0 -7
  78. package/core/plugins/registry.ts +0 -126
  79. package/dist/cli.js +0 -11675
  80. package/runtime/build.ts +0 -17
  81. package/runtime/bundle-generator.ts +0 -1266
  82. package/runtime/client-runtime.ts +0 -891
  83. package/runtime/serve.ts +0 -93
@@ -1,406 +0,0 @@
1
- /**
2
- * Transform IR to Runtime Code
3
- *
4
- * Phase 4: Transform ZenIR into runtime-ready JavaScript code with full reactivity
5
- */
6
-
7
- import type { ZenIR, ScriptImport } from '../ir/types'
8
- import { generateExpressionWrappers } from './wrapExpression'
9
- import { generateDOMFunction } from './generateDOM'
10
- import { generateHydrationRuntime, generateExpressionRegistry } from './generateHydrationBundle'
11
- import { analyzeAllExpressions } from './dataExposure'
12
- // Legacy navigation runtime removed - router now provided by @zenithbuild/router via bundle-generator.ts
13
- import { extractStateDeclarations, extractProps, transformStateDeclarations } from '../parse/scriptAnalysis'
14
- import { transformAllComponentScripts, emitImports } from '../transform/componentScriptTransformer'
15
-
16
- export interface RuntimeCode {
17
- expressions: string // Expression wrapper functions
18
- render: string // renderDynamicPage function (legacy, for reference)
19
- hydration: string // Phase 5 hydration runtime code
20
- styles: string // Style injection code
21
- script: string // Transformed script code
22
- stateInit: string // State initialization code
23
- bundle: string // Complete runtime bundle (expressions + hydration + helpers)
24
- }
25
-
26
- /**
27
- * Transform ZenIR into runtime JavaScript code
28
- */
29
- export async function transformIR(ir: ZenIR): Promise<RuntimeCode> {
30
- // Phase 6: Analyze expression dependencies for explicit data exposure
31
- const expressionDependencies = analyzeAllExpressions(
32
- ir.template.expressions,
33
- ir.filePath,
34
- [], // declaredLoaderProps
35
- ir.script?.attributes['props'] ? ir.script.attributes['props'].split(',') : [], // declaredProps
36
- [] // declaredStores
37
- )
38
-
39
- // Generate expression wrappers with dependencies
40
- const expressions = generateExpressionWrappers(ir.template.expressions, expressionDependencies)
41
-
42
- // Generate DOM creation code
43
- const renderFunction = generateDOMFunction(
44
- ir.template.nodes,
45
- ir.template.expressions,
46
- 'renderDynamicPage'
47
- )
48
-
49
- // Generate hydrate function (legacy, for reference)
50
- const hydrateFunction = generateHydrateFunction()
51
-
52
- // Generate Phase 5 hydration runtime
53
- const hydrationRuntime = generateHydrationRuntime()
54
-
55
- // Phase 7 navigation runtime removed - router is provided by shared runtime bundle
56
- const navigationRuntime = '' // No longer inlined per-page
57
-
58
- // Generate expression registry initialization
59
- const expressionRegistry = generateExpressionRegistry(ir.template.expressions)
60
-
61
- // Generate style injection code
62
- const stylesCode = generateStyleInjection(ir.styles)
63
-
64
- // Extract state and prop declarations
65
- const scriptContent = ir.script?.raw || ''
66
- const stateDeclarations = extractStateDeclarations(scriptContent)
67
- const propKeys = Object.keys(ir.script?.attributes || {}).filter(k => k !== 'setup' && k !== 'lang')
68
- const propDeclarations = extractProps(scriptContent)
69
- const stateInitCode = generateStateInitialization(stateDeclarations, [...propDeclarations, ...propKeys])
70
-
71
- // Transform script (remove state and prop declarations, they're handled by runtime)
72
- const scriptCode = transformStateDeclarations(scriptContent)
73
-
74
- // Transform component scripts for instance-scoped execution (synchronous - Acorn)
75
- const componentScriptResult = transformAllComponentScripts(ir.componentScripts || [])
76
-
77
- // Generate complete runtime bundle
78
- const bundle = generateRuntimeBundle({
79
- expressions,
80
- expressionRegistry,
81
- hydrationRuntime,
82
- navigationRuntime,
83
- stylesCode,
84
- scriptCode,
85
- stateInitCode,
86
- componentScriptCode: componentScriptResult.code,
87
- npmImports: componentScriptResult.imports
88
- })
89
-
90
- return {
91
- expressions,
92
- render: renderFunction,
93
- hydration: hydrationRuntime,
94
- styles: stylesCode,
95
- script: scriptCode,
96
- stateInit: stateInitCode,
97
- bundle
98
- }
99
- }
100
-
101
- /**
102
- * Generate complete runtime bundle
103
- */
104
- function generateRuntimeBundle(parts: {
105
- expressions: string
106
- expressionRegistry: string
107
- hydrationRuntime: string
108
- navigationRuntime: string
109
- stylesCode: string
110
- scriptCode: string
111
- stateInitCode: string
112
- componentScriptCode: string // Component factories
113
- npmImports: ScriptImport[] // Structured npm imports from component scripts
114
- }): string {
115
- // Extract function declarations from script code to register on window
116
- const functionRegistrations = extractFunctionRegistrations(parts.scriptCode)
117
-
118
- // Extract const/let declarations from script code to register on window
119
- const variableRegistrations = extractVariableRegistrations(parts.scriptCode)
120
-
121
- // Generate npm imports header (hoisted, deduplicated, deterministic)
122
- const npmImportsHeader = parts.npmImports.length > 0
123
- ? `// NPM Imports (hoisted from component scripts)\n${emitImports(parts.npmImports)}\n\n`
124
- : ''
125
-
126
- return `// Zenith Runtime Bundle (Phase 5)
127
- // Generated at compile time - no .zen parsing in browser
128
-
129
- ${npmImportsHeader}${parts.expressions}
130
-
131
- ${parts.expressionRegistry}
132
-
133
- ${parts.hydrationRuntime}
134
-
135
- ${parts.navigationRuntime}
136
-
137
- ${parts.stylesCode ? `// Style injection
138
- ${parts.stylesCode}` : ''}
139
-
140
- // User script code - executed first to define variables needed by state initialization
141
- ${parts.scriptCode ? parts.scriptCode : ''}
142
-
143
- ${functionRegistrations}
144
-
145
- ${variableRegistrations}
146
-
147
- ${parts.stateInitCode ? `// State initialization
148
- ${parts.stateInitCode}` : ''}
149
-
150
- ${parts.componentScriptCode ? `// Component factories (instance-scoped)
151
- ${parts.componentScriptCode}` : ''}
152
-
153
- // Export hydration functions
154
- if (typeof window !== 'undefined') {
155
- window.zenithHydrate = window.__zenith_hydrate || function(state, container) {
156
- console.warn('[Zenith] Hydration runtime not loaded');
157
- };
158
- window.zenithUpdate = window.__zenith_update || function(state) {
159
- console.warn('[Zenith] Update runtime not loaded');
160
- };
161
- window.zenithBindEvents = window.__zenith_bindEvents || function(container) {
162
- console.warn('[Zenith] Event binding runtime not loaded');
163
- };
164
- window.zenithCleanup = window.__zenith_cleanup || function(container) {
165
- console.warn('[Zenith] Cleanup runtime not loaded');
166
- };
167
- }
168
-
169
- // Auto-hydrate on page mount
170
- (function() {
171
- 'use strict';
172
-
173
- function autoHydrate() {
174
- // Initialize state object
175
- const state = window.__ZENITH_STATE__ || {};
176
-
177
- // Run state initialization if defined
178
- if (typeof initializeState === 'function') {
179
- initializeState(state);
180
- }
181
-
182
- // Store state globally
183
- window.__ZENITH_STATE__ = state;
184
-
185
- // Expose state variables on window with reactive getters/setters
186
- // This allows user functions (like increment) to access state variables directly
187
- for (const key in state) {
188
- if (state.hasOwnProperty(key) && !window.hasOwnProperty(key)) {
189
- Object.defineProperty(window, key, {
190
- get: function() { return window.__ZENITH_STATE__[key]; },
191
- set: function(value) {
192
- window.__ZENITH_STATE__[key] = value;
193
- // Trigger reactive update
194
- if (window.__zenith_update) {
195
- window.__zenith_update(window.__ZENITH_STATE__);
196
- }
197
- },
198
- configurable: true
199
- });
200
- }
201
- }
202
-
203
- // Inject styles if defined
204
- if (typeof injectStyles === 'function') {
205
- injectStyles();
206
- }
207
-
208
- // Get the router outlet or body
209
- const container = document.querySelector('#app') || document.body;
210
-
211
- // Hydrate with state (expressions, bindings)
212
- if (window.__zenith_hydrate) {
213
- window.__zenith_hydrate(state, {}, {}, {}, container);
214
- }
215
-
216
- // Hydrate components by discovering data-zen-component markers
217
- // This is the ONLY place component instantiation happens - driven by DOM markers
218
- if (window.__zenith && window.__zenith.hydrateComponents) {
219
- window.__zenith.hydrateComponents(container);
220
- }
221
-
222
- // Trigger page-level mount lifecycle
223
- if (window.__zenith && window.__zenith.triggerMount) {
224
- window.__zenith.triggerMount();
225
- }
226
- }
227
-
228
- // Run on DOM ready
229
- if (document.readyState === 'loading') {
230
- document.addEventListener('DOMContentLoaded', autoHydrate);
231
- } else {
232
- // DOM already loaded, hydrate immediately
233
- autoHydrate();
234
- }
235
- })();
236
- `
237
- }
238
-
239
- /**
240
- * Extract function declarations and generate window registration code
241
- */
242
- function extractFunctionRegistrations(scriptCode: string): string {
243
- if (!scriptCode) return ''
244
-
245
- // Match function declarations: function name(...) { ... }
246
- const functionPattern = /function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g
247
- const functionNames: string[] = []
248
- let match
249
-
250
- while ((match = functionPattern.exec(scriptCode)) !== null) {
251
- if (match[1]) {
252
- functionNames.push(match[1])
253
- }
254
- }
255
-
256
- if (functionNames.length === 0) {
257
- return ''
258
- }
259
-
260
- // Generate window registration for each function
261
- const registrations = functionNames.map(name =>
262
- ` if (typeof ${name} === 'function') window.${name} = ${name};`
263
- ).join('\n')
264
-
265
- return `// Register functions on window for event handlers\n${registrations}`
266
- }
267
-
268
- /**
269
- * Extract const/let declarations and generate window registration code
270
- * This allows expressions evaluated via new Function() to access script-level variables
271
- */
272
- function extractVariableRegistrations(scriptCode: string): string {
273
- if (!scriptCode) return ''
274
-
275
- // Match const/let declarations: const name = ... or let name = ...
276
- // Also match destructured: const { a, b } = ... but we only care about simple assignments
277
- const varPattern = /(?:const|let)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/g
278
- const varNames: string[] = []
279
- let match
280
-
281
- while ((match = varPattern.exec(scriptCode)) !== null) {
282
- if (match[1]) {
283
- varNames.push(match[1])
284
- }
285
- }
286
-
287
- if (varNames.length === 0) {
288
- return ''
289
- }
290
-
291
- // Generate window registration for each variable
292
- const registrations = varNames.map(name =>
293
- ` if (typeof ${name} !== 'undefined') window.${name} = ${name};`
294
- ).join('\n')
295
-
296
- return `// Register script variables on window for expression access\n${registrations}`
297
- }
298
-
299
- /**
300
- * Generate hydrate function that mounts the DOM with reactivity
301
- */
302
- function generateHydrateFunction(): string {
303
- return `function hydrate(root, state) {
304
- if (!root) {
305
- // SSR fallback - return initial HTML string
306
- console.warn('[Zenith] hydrate called without root element - SSR mode');
307
- return '';
308
- }
309
-
310
- // Clear root
311
- root.innerHTML = '';
312
-
313
- // Render template
314
- const dom = renderDynamicPage(state);
315
-
316
- // Append to root
317
- if (dom instanceof DocumentFragment) {
318
- root.appendChild(dom);
319
- } else if (dom instanceof Node) {
320
- root.appendChild(dom);
321
- }
322
-
323
- // Bind event handlers
324
- bindEventHandlers(root, state);
325
-
326
- // Set up reactive updates (if state is reactive)
327
- setupReactiveUpdates(root, state);
328
-
329
- return root;
330
- }
331
-
332
- function bindEventHandlers(root, state) {
333
- // Find all elements with data-zen-* event attributes
334
- const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur'];
335
-
336
- for (const eventType of eventTypes) {
337
- const elements = root.querySelectorAll(\`[data-zen-\${eventType}]\`);
338
- for (const el of elements) {
339
- const handlerName = el.getAttribute(\`data-zen-\${eventType}\`);
340
- if (handlerName && typeof window[handlerName] === 'function') {
341
- el.addEventListener(eventType, (e) => {
342
- window[handlerName](e, el);
343
- });
344
- }
345
- }
346
- }
347
- }
348
-
349
- function setupReactiveUpdates(root, state) {
350
- // For now, reactive updates are handled by the existing binding system
351
- // This is a placeholder for future reactive DOM updates
352
- // The existing runtime handles reactivity via state property setters
353
- }`
354
- }
355
-
356
- /**
357
- * Generate style injection code
358
- */
359
- function generateStyleInjection(styles: Array<{ raw: string }>): string {
360
- if (styles.length === 0) {
361
- return ''
362
- }
363
-
364
- const styleBlocks = styles.map((style, index) => {
365
- const escapedStyle = style.raw.replace(/`/g, '\\`').replace(/\$/g, '\\$')
366
- return `
367
- const style${index} = document.createElement('style');
368
- style${index}.textContent = \`${escapedStyle}\`;
369
- document.head.appendChild(style${index});`
370
- }).join('')
371
-
372
- return `function injectStyles() {${styleBlocks}
373
- }`
374
- }
375
-
376
- /**
377
- * Generate state initialization code
378
- * In Phase 9: Also handles props passing
379
- */
380
- function generateStateInitialization(stateDeclarations: Map<string, string>, propDeclarations: string[]): string {
381
- const stateInit = Array.from(stateDeclarations.entries()).map(([name, value]) => {
382
- return `
383
- // Initialize state: ${name}
384
- if (typeof state.${name} === 'undefined') {
385
- state.${name} = ${value};
386
- }`
387
- }).join('')
388
-
389
- const legacyPropInit = propDeclarations.includes('props') ? `
390
- // Initialize props object (legacy)
391
- if (typeof window.__ZEN_PROPS__ !== 'undefined') {
392
- state.props = window.__ZEN_PROPS__;
393
- }` : ''
394
-
395
- const individualPropInit = propDeclarations.filter(p => p !== 'props').map(prop => `
396
- // Initialize prop: ${prop}
397
- if (typeof state.${prop} === 'undefined' && typeof window.__ZEN_PROPS__ !== 'undefined' && typeof window.__ZEN_PROPS__.${prop} !== 'undefined') {
398
- state.${prop} = window.__ZEN_PROPS__.${prop};
399
- }`).join('')
400
-
401
- return `function initializeState(state) {${stateInit}${legacyPropInit}${individualPropInit}
402
- }`
403
- }
404
-
405
- // Note: transformScript is now handled by transformStateDeclarations in legacy/parse.ts
406
-
@@ -1,114 +0,0 @@
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
-
10
- import type { ExpressionIR, LoopContext } from '../ir/types'
11
- import type { ExpressionDataDependencies } from './dataExposure'
12
- import { generateExplicitExpressionWrapper } from './dataExposure'
13
- import { wrapExpressionWithLoopContext } from './wrapExpressionWithLoop'
14
-
15
- import { transformExpressionJSX } from '../transform/expressionTransformer'
16
-
17
- /**
18
- * Wrap an expression into a runtime function with explicit data arguments
19
- *
20
- * Phase 6: Supports explicit loaderData, props, stores arguments
21
- * Phase 7: Supports loop context for expressions inside map iterations
22
- */
23
- export function wrapExpression(
24
- expr: ExpressionIR,
25
- dependencies?: ExpressionDataDependencies,
26
- loopContext?: LoopContext // Phase 7: Loop context for map expressions
27
- ): string {
28
- const { id, code } = expr
29
-
30
- // Phase 7: If loop context is provided, use loop-aware wrapper
31
- if (loopContext && loopContext.variables.length > 0) {
32
- return wrapExpressionWithLoopContext(expr, loopContext, dependencies)
33
- }
34
-
35
- // If dependencies are provided, use explicit wrapper (Phase 6)
36
- if (dependencies) {
37
- return generateExplicitExpressionWrapper(expr, dependencies)
38
- }
39
-
40
- // Fallback to legacy wrapper (backwards compatibility)
41
- // Transform JSX-like tags inside expression code
42
- const transformedCode = transformExpressionJSX(code)
43
- // Escape the code for use in a single-line comment (replace newlines with spaces)
44
- const commentCode = code.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ').substring(0, 100)
45
- const jsonEscapedCode = JSON.stringify(code)
46
-
47
- // Properly escape the transformed code for use inside a string
48
- const escapedTransformedCode = transformedCode
49
- .replace(/\\/g, '\\\\')
50
- .replace(/'/g, "\\'")
51
- .replace(/\n/g, '\\n')
52
- .replace(/\r/g, '\\r')
53
-
54
- // Note: We cannot use `with (state)` in ES modules (strict mode)
55
- // Instead, we use new Function() which runs in non-strict sloppy mode by default
56
- // and allows 'with' statements. This is a workaround for strict mode limitations.
57
- return `
58
- // Expression: ${commentCode}${code.length > 100 ? '...' : ''}
59
- const ${id} = (function() {
60
- // Create the evaluator function once (with 'with' support in sloppy mode)
61
- var evalFn = new Function('__ctx',
62
- 'with (__ctx) { return (' + '${escapedTransformedCode}' + '); }'
63
- );
64
-
65
- return function(state) {
66
- try {
67
- var __zenith = window.__zenith || {};
68
- var zenCollection = __zenith.zenCollection || function(name) { return { get: function() { return []; } }; };
69
- var createZenOrder = __zenith.createZenOrder || function(sections) { return { sections: [], getSectionBySlug: function() { return null; }, getDocBySlug: function() { return null; } }; };
70
-
71
- // Merge window globals (script variables) with state
72
- // State takes precedence over window globals
73
- var __ctx = Object.assign({}, window, { zenCollection: zenCollection, createZenOrder: createZenOrder }, state || {});
74
-
75
- return evalFn(__ctx);
76
- } catch (e) {
77
- console.warn('[Zenith] Expression evaluation error:', ${jsonEscapedCode}, e);
78
- return undefined;
79
- }
80
- };
81
- })();`
82
- }
83
-
84
- /**
85
- * Generate all expression wrappers for a set of expressions
86
- *
87
- * Phase 6: Accepts dependencies array for explicit data exposure
88
- * Phase 7: Accepts loop contexts for expressions inside map iterations
89
- */
90
- export function generateExpressionWrappers(
91
- expressions: ExpressionIR[],
92
- dependencies?: ExpressionDataDependencies[],
93
- loopContexts?: (LoopContext | undefined)[] // Phase 7: Loop contexts for each expression
94
- ): string {
95
- if (expressions.length === 0) {
96
- return ''
97
- }
98
-
99
- if (dependencies && dependencies.length === expressions.length) {
100
- // Use explicit wrappers with dependencies and optional loop contexts
101
- return expressions
102
- .map((expr, index) => {
103
- const loopCtx = loopContexts && loopContexts[index] !== undefined
104
- ? loopContexts[index]
105
- : undefined
106
- return wrapExpression(expr, dependencies[index], loopCtx)
107
- })
108
- .join('\n')
109
- }
110
-
111
- // Fallback to legacy wrappers (no dependencies, no loop contexts)
112
- return expressions.map(expr => wrapExpression(expr)).join('\n')
113
- }
114
-
@@ -1,97 +0,0 @@
1
- /**
2
- * Expression Wrapper with Loop Context Support
3
- *
4
- * Phase 7: Wraps expressions that reference loop variables from map iterations
5
- *
6
- * Generates runtime functions that accept (state, loaderData, props, stores, loopContext)
7
- * and evaluate expressions with both global state and loop-scoped variables available
8
- */
9
-
10
- import type { ExpressionIR, LoopContext } from '../ir/types'
11
- import type { ExpressionDataDependencies } from './dataExposure'
12
- import { transformExpressionJSX } from '../transform/expressionTransformer'
13
-
14
- /**
15
- * Generate an expression wrapper that accepts loop context
16
- *
17
- * Phase 7: Expressions inside map loops need access to loop variables (e.g., todo, index)
18
- * in addition to global state (state, loaderData, props, stores)
19
- */
20
- export function wrapExpressionWithLoopContext(
21
- expr: ExpressionIR,
22
- loopContext?: LoopContext,
23
- dependencies?: ExpressionDataDependencies
24
- ): string {
25
- const { id, code } = expr
26
- const escapedCode = code.replace(/`/g, '\\`').replace(/\$/g, '\\$')
27
-
28
- if (!loopContext || loopContext.variables.length === 0) {
29
- // No loop context - use standard wrapper (will be handled by wrapExpression)
30
- return ''
31
- }
32
-
33
- // Determine arguments based on dependencies
34
- const args: string[] = []
35
- if (dependencies?.usesState || (dependencies?.stateProperties && dependencies.stateProperties.length > 0)) args.push('state')
36
- if (dependencies?.usesLoaderData) args.push('loaderData')
37
- if (dependencies?.usesProps) args.push('props')
38
- if (dependencies?.usesStores) args.push('stores')
39
-
40
- // Phase 7: Always add loopContext as the last argument
41
- args.push('loopContext')
42
-
43
- const argsStr = args.join(', ')
44
-
45
- // Generate function that merges state and loop context
46
- // Loop context variables take precedence over state properties with the same name
47
- const loopVarsDecl = loopContext.variables.map(v => ` const ${v} = loopContext?.${v};`).join('\n')
48
- const loopVarsObject = `{ ${loopContext.variables.join(', ')} }`
49
-
50
- // Create merged context for expression evaluation
51
- // Order: loopContext > stores > props > loaderData > state
52
- const contextMerge: string[] = []
53
- if (dependencies?.usesState || (dependencies?.stateProperties && dependencies.stateProperties.length > 0)) contextMerge.push('state')
54
- if (dependencies?.usesStores) contextMerge.push('stores')
55
- if (dependencies?.usesProps) contextMerge.push('props')
56
- if (dependencies?.usesLoaderData) contextMerge.push('loaderData')
57
- if (loopContext) contextMerge.push('loopContext')
58
-
59
- const contextObject = contextMerge.length > 0
60
- ? `const __ctx = Object.assign({}, ${contextMerge.join(', ')});`
61
- : `const __ctx = loopContext || {};`
62
-
63
- // Transform JSX
64
- // The fix for 'undefined' string assignment is applied within transformExpressionJSX
65
- // by ensuring that any remaining text is properly quoted as a string literal
66
- // or recognized as an existing h() call.
67
- const transformedCode = transformExpressionJSX(code)
68
-
69
- // Properly escape the transformed code for use inside a string
70
- const escapedTransformedCode = transformedCode
71
- .replace(/\\/g, '\\\\')
72
- .replace(/'/g, "\\'")
73
- .replace(/\n/g, '\\n')
74
- .replace(/\r/g, '\\r')
75
-
76
- return `
77
- // Expression with loop context: ${escapedCode}
78
- // Loop variables: ${loopContext.variables.join(', ')}
79
- const ${id} = (function() {
80
- // Create the evaluator function once (with 'with' support in sloppy mode)
81
- var evalFn = new Function('__ctx',
82
- 'with (__ctx) { return (' + '${escapedTransformedCode}' + '); }'
83
- );
84
-
85
- return function(${argsStr}) {
86
- try {
87
- // Merge window globals with context (for script-level variables)
88
- var __baseCtx = Object.assign({}, window);
89
- ${contextObject.replace('const __ctx', 'var __ctx').replace('= Object.assign({},', '= Object.assign(__baseCtx,')}
90
- return evalFn(__ctx);
91
- } catch (e) {
92
- console.warn('[Zenith] Expression evaluation error for "${escapedCode}":', e);
93
- return undefined;
94
- }
95
- };
96
- })();`
97
- }