@zenithbuild/core 0.1.0
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/.eslintignore +15 -0
- package/.gitattributes +2 -0
- package/.github/ISSUE_TEMPLATE/compiler-errors-for-invalid-state-declarations.md +25 -0
- package/.github/ISSUE_TEMPLATE/new_ticket.yaml +34 -0
- package/.github/pull_request_template.md +15 -0
- package/.github/workflows/discord-changelog.yml +141 -0
- package/.github/workflows/discord-notify.yml +242 -0
- package/.github/workflows/discord-version.yml +195 -0
- package/.prettierignore +13 -0
- package/.prettierrc +21 -0
- package/.zen.d.ts +15 -0
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/app/components/Button.zen +46 -0
- package/app/components/Link.zen +11 -0
- package/app/favicon.ico +0 -0
- package/app/layouts/Main.zen +59 -0
- package/app/pages/about.zen +23 -0
- package/app/pages/blog/[id].zen +53 -0
- package/app/pages/blog/index.zen +32 -0
- package/app/pages/dynamic-dx.zen +712 -0
- package/app/pages/dynamic-primitives.zen +453 -0
- package/app/pages/index.zen +154 -0
- package/app/pages/navigation-demo.zen +229 -0
- package/app/pages/posts/[...slug].zen +61 -0
- package/app/pages/primitives-demo.zen +273 -0
- package/assets/logos/0E3B5DDD-605C-4839-BB2E-DFCA8ADC9604.PNG +0 -0
- package/assets/logos/760971E5-79A1-44F9-90B9-925DF30F4278.PNG +0 -0
- package/assets/logos/8A06ED80-9ED2-4689-BCBD-13B2E95EE8E4.JPG +0 -0
- package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.PNG +0 -0
- package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.svg +601 -0
- package/assets/logos/README.md +54 -0
- package/assets/logos/zen.icns +0 -0
- package/bun.lock +39 -0
- package/compiler/README.md +380 -0
- package/compiler/errors/compilerError.ts +24 -0
- package/compiler/finalize/finalizeOutput.ts +163 -0
- package/compiler/finalize/generateFinalBundle.ts +82 -0
- package/compiler/index.ts +44 -0
- package/compiler/ir/types.ts +83 -0
- package/compiler/legacy/binding.ts +254 -0
- package/compiler/legacy/bindings.ts +338 -0
- package/compiler/legacy/component-process.ts +1208 -0
- package/compiler/legacy/component.ts +301 -0
- package/compiler/legacy/event.ts +50 -0
- package/compiler/legacy/expression.ts +1149 -0
- package/compiler/legacy/mutation.ts +280 -0
- package/compiler/legacy/parse.ts +299 -0
- package/compiler/legacy/split.ts +608 -0
- package/compiler/legacy/types.ts +32 -0
- package/compiler/output/types.ts +34 -0
- package/compiler/parse/detectMapExpressions.ts +102 -0
- package/compiler/parse/parseScript.ts +22 -0
- package/compiler/parse/parseTemplate.ts +425 -0
- package/compiler/parse/parseZenFile.ts +66 -0
- package/compiler/parse/trackLoopContext.ts +82 -0
- package/compiler/runtime/dataExposure.ts +291 -0
- package/compiler/runtime/generateDOM.ts +144 -0
- package/compiler/runtime/generateHydrationBundle.ts +383 -0
- package/compiler/runtime/hydration.ts +309 -0
- package/compiler/runtime/navigation.ts +432 -0
- package/compiler/runtime/thinRuntime.ts +160 -0
- package/compiler/runtime/transformIR.ts +256 -0
- package/compiler/runtime/wrapExpression.ts +84 -0
- package/compiler/runtime/wrapExpressionWithLoop.ts +77 -0
- package/compiler/spa-build.ts +1000 -0
- package/compiler/test/validate-test.ts +104 -0
- package/compiler/transform/generateBindings.ts +47 -0
- package/compiler/transform/generateHTML.ts +28 -0
- package/compiler/transform/transformNode.ts +126 -0
- package/compiler/transform/transformTemplate.ts +38 -0
- package/compiler/validate/validateExpressions.ts +168 -0
- package/core/index.ts +135 -0
- package/core/lifecycle/index.ts +49 -0
- package/core/lifecycle/zen-mount.ts +182 -0
- package/core/lifecycle/zen-unmount.ts +88 -0
- package/core/reactivity/index.ts +54 -0
- package/core/reactivity/tracking.ts +167 -0
- package/core/reactivity/zen-batch.ts +57 -0
- package/core/reactivity/zen-effect.ts +139 -0
- package/core/reactivity/zen-memo.ts +146 -0
- package/core/reactivity/zen-ref.ts +52 -0
- package/core/reactivity/zen-signal.ts +121 -0
- package/core/reactivity/zen-state.ts +180 -0
- package/core/reactivity/zen-untrack.ts +44 -0
- package/docs/COMMENTS.md +111 -0
- package/docs/COMMITS.md +36 -0
- package/docs/CONTRIBUTING.md +116 -0
- package/docs/STYLEGUIDE.md +62 -0
- package/package.json +44 -0
- package/router/index.ts +76 -0
- package/router/manifest.ts +314 -0
- package/router/navigation/ZenLink.zen +231 -0
- package/router/navigation/index.ts +78 -0
- package/router/navigation/zen-link.ts +584 -0
- package/router/runtime.ts +458 -0
- package/router/types.ts +168 -0
- package/runtime/build.ts +17 -0
- package/runtime/serve.ts +93 -0
- package/scripts/webhook-proxy.ts +213 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,256 @@
|
|
|
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 } from '../ir/types'
|
|
8
|
+
import { generateExpressionWrappers } from './wrapExpression'
|
|
9
|
+
import { generateDOMFunction } from './generateDOM'
|
|
10
|
+
import { generateHydrationRuntime, generateExpressionRegistry } from './generateHydrationBundle'
|
|
11
|
+
import { analyzeAllExpressions, type ExpressionDataDependencies } from './dataExposure'
|
|
12
|
+
import { generateNavigationRuntime } from './navigation'
|
|
13
|
+
import { extractStateDeclarations } from '../legacy/parse'
|
|
14
|
+
|
|
15
|
+
export interface RuntimeCode {
|
|
16
|
+
expressions: string // Expression wrapper functions
|
|
17
|
+
render: string // renderDynamicPage function (legacy, for reference)
|
|
18
|
+
hydration: string // Phase 5 hydration runtime code
|
|
19
|
+
styles: string // Style injection code
|
|
20
|
+
script: string // Transformed script code
|
|
21
|
+
stateInit: string // State initialization code
|
|
22
|
+
bundle: string // Complete runtime bundle (expressions + hydration + helpers)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Transform ZenIR into runtime JavaScript code
|
|
27
|
+
*/
|
|
28
|
+
export function transformIR(ir: ZenIR): RuntimeCode {
|
|
29
|
+
// Phase 6: Analyze expression dependencies for explicit data exposure
|
|
30
|
+
const expressionDependencies = analyzeAllExpressions(
|
|
31
|
+
ir.template.expressions,
|
|
32
|
+
ir.filePath,
|
|
33
|
+
[], // declaredLoaderProps - can be enhanced with loader analysis
|
|
34
|
+
[], // declaredProps - can be enhanced with component prop analysis
|
|
35
|
+
[] // declaredStores - can be enhanced with store import analysis
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
// Generate expression wrappers with dependencies
|
|
39
|
+
const expressions = generateExpressionWrappers(ir.template.expressions, expressionDependencies)
|
|
40
|
+
|
|
41
|
+
// Generate DOM creation code
|
|
42
|
+
const renderFunction = generateDOMFunction(
|
|
43
|
+
ir.template.nodes,
|
|
44
|
+
ir.template.expressions,
|
|
45
|
+
'renderDynamicPage'
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
// Generate hydrate function (legacy, for reference)
|
|
49
|
+
const hydrateFunction = generateHydrateFunction()
|
|
50
|
+
|
|
51
|
+
// Generate Phase 5 hydration runtime
|
|
52
|
+
const hydrationRuntime = generateHydrationRuntime()
|
|
53
|
+
|
|
54
|
+
// Generate Phase 7 navigation runtime
|
|
55
|
+
const navigationRuntime = generateNavigationRuntime()
|
|
56
|
+
|
|
57
|
+
// Generate expression registry initialization
|
|
58
|
+
const expressionRegistry = generateExpressionRegistry(ir.template.expressions)
|
|
59
|
+
|
|
60
|
+
// Generate style injection code
|
|
61
|
+
const stylesCode = generateStyleInjection(ir.styles)
|
|
62
|
+
|
|
63
|
+
// Extract state declarations and generate initialization
|
|
64
|
+
const scriptContent = ir.script?.raw || ''
|
|
65
|
+
const stateDeclarations = extractStateDeclarations(scriptContent)
|
|
66
|
+
const stateInitCode = generateStateInitialization(stateDeclarations)
|
|
67
|
+
|
|
68
|
+
// Transform script (remove state declarations, they're handled by runtime)
|
|
69
|
+
const scriptCode = transformScript(scriptContent, stateDeclarations)
|
|
70
|
+
|
|
71
|
+
// Generate complete runtime bundle
|
|
72
|
+
const bundle = generateRuntimeBundle({
|
|
73
|
+
expressions,
|
|
74
|
+
expressionRegistry,
|
|
75
|
+
hydrationRuntime,
|
|
76
|
+
navigationRuntime,
|
|
77
|
+
stylesCode,
|
|
78
|
+
scriptCode,
|
|
79
|
+
stateInitCode
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
expressions,
|
|
84
|
+
render: renderFunction,
|
|
85
|
+
hydration: hydrationRuntime,
|
|
86
|
+
styles: stylesCode,
|
|
87
|
+
script: scriptCode,
|
|
88
|
+
stateInit: stateInitCode,
|
|
89
|
+
bundle
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Generate complete runtime bundle
|
|
95
|
+
*/
|
|
96
|
+
function generateRuntimeBundle(parts: {
|
|
97
|
+
expressions: string
|
|
98
|
+
expressionRegistry: string
|
|
99
|
+
hydrationRuntime: string
|
|
100
|
+
navigationRuntime: string
|
|
101
|
+
stylesCode: string
|
|
102
|
+
scriptCode: string
|
|
103
|
+
stateInitCode: string
|
|
104
|
+
}): string {
|
|
105
|
+
return `// Zenith Runtime Bundle (Phase 5)
|
|
106
|
+
// Generated at compile time - no .zen parsing in browser
|
|
107
|
+
|
|
108
|
+
${parts.expressions}
|
|
109
|
+
|
|
110
|
+
${parts.expressionRegistry}
|
|
111
|
+
|
|
112
|
+
${parts.hydrationRuntime}
|
|
113
|
+
|
|
114
|
+
${parts.navigationRuntime}
|
|
115
|
+
|
|
116
|
+
${parts.stateInitCode ? `// State initialization
|
|
117
|
+
${parts.stateInitCode}` : ''}
|
|
118
|
+
|
|
119
|
+
${parts.stylesCode ? `// Style injection
|
|
120
|
+
${parts.stylesCode}` : ''}
|
|
121
|
+
|
|
122
|
+
${parts.scriptCode ? `// User script code
|
|
123
|
+
${parts.scriptCode}` : ''}
|
|
124
|
+
|
|
125
|
+
// Export hydration functions
|
|
126
|
+
if (typeof window !== 'undefined') {
|
|
127
|
+
window.zenithHydrate = window.__zenith_hydrate || function(state, container) {
|
|
128
|
+
console.warn('[Zenith] Hydration runtime not loaded');
|
|
129
|
+
};
|
|
130
|
+
window.zenithUpdate = window.__zenith_update || function(state) {
|
|
131
|
+
console.warn('[Zenith] Update runtime not loaded');
|
|
132
|
+
};
|
|
133
|
+
window.zenithBindEvents = window.__zenith_bindEvents || function(container) {
|
|
134
|
+
console.warn('[Zenith] Event binding runtime not loaded');
|
|
135
|
+
};
|
|
136
|
+
window.zenithCleanup = window.__zenith_cleanup || function(container) {
|
|
137
|
+
console.warn('[Zenith] Cleanup runtime not loaded');
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
`
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Generate hydrate function that mounts the DOM with reactivity
|
|
145
|
+
*/
|
|
146
|
+
function generateHydrateFunction(): string {
|
|
147
|
+
return `function hydrate(root, state) {
|
|
148
|
+
if (!root) {
|
|
149
|
+
// SSR fallback - return initial HTML string
|
|
150
|
+
console.warn('[Zenith] hydrate called without root element - SSR mode');
|
|
151
|
+
return '';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Clear root
|
|
155
|
+
root.innerHTML = '';
|
|
156
|
+
|
|
157
|
+
// Render template
|
|
158
|
+
const dom = renderDynamicPage(state);
|
|
159
|
+
|
|
160
|
+
// Append to root
|
|
161
|
+
if (dom instanceof DocumentFragment) {
|
|
162
|
+
root.appendChild(dom);
|
|
163
|
+
} else if (dom instanceof Node) {
|
|
164
|
+
root.appendChild(dom);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Bind event handlers
|
|
168
|
+
bindEventHandlers(root, state);
|
|
169
|
+
|
|
170
|
+
// Set up reactive updates (if state is reactive)
|
|
171
|
+
setupReactiveUpdates(root, state);
|
|
172
|
+
|
|
173
|
+
return root;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function bindEventHandlers(root, state) {
|
|
177
|
+
// Find all elements with data-zen-* event attributes
|
|
178
|
+
const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur'];
|
|
179
|
+
|
|
180
|
+
for (const eventType of eventTypes) {
|
|
181
|
+
const elements = root.querySelectorAll(\`[data-zen-\${eventType}]\`);
|
|
182
|
+
for (const el of elements) {
|
|
183
|
+
const handlerName = el.getAttribute(\`data-zen-\${eventType}\`);
|
|
184
|
+
if (handlerName && typeof window[handlerName] === 'function') {
|
|
185
|
+
el.addEventListener(eventType, (e) => {
|
|
186
|
+
window[handlerName](e, el);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function setupReactiveUpdates(root, state) {
|
|
194
|
+
// For now, reactive updates are handled by the existing binding system
|
|
195
|
+
// This is a placeholder for future reactive DOM updates
|
|
196
|
+
// The existing runtime handles reactivity via state property setters
|
|
197
|
+
}`
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Generate style injection code
|
|
202
|
+
*/
|
|
203
|
+
function generateStyleInjection(styles: Array<{ raw: string }>): string {
|
|
204
|
+
if (styles.length === 0) {
|
|
205
|
+
return ''
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const styleBlocks = styles.map((style, index) => {
|
|
209
|
+
const escapedStyle = style.raw.replace(/`/g, '\\`').replace(/\$/g, '\\$')
|
|
210
|
+
return `
|
|
211
|
+
const style${index} = document.createElement('style');
|
|
212
|
+
style${index}.textContent = \`${escapedStyle}\`;
|
|
213
|
+
document.head.appendChild(style${index});`
|
|
214
|
+
}).join('')
|
|
215
|
+
|
|
216
|
+
return `function injectStyles() {${styleBlocks}
|
|
217
|
+
}`
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Generate state initialization code
|
|
222
|
+
*/
|
|
223
|
+
function generateStateInitialization(stateDeclarations: Map<string, string>): string {
|
|
224
|
+
if (stateDeclarations.size === 0) {
|
|
225
|
+
return ''
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const initCode = Array.from(stateDeclarations.entries()).map(([name, value]) => {
|
|
229
|
+
return `
|
|
230
|
+
// Initialize state: ${name}
|
|
231
|
+
if (!state.${name}) {
|
|
232
|
+
state.${name} = ${value};
|
|
233
|
+
}`
|
|
234
|
+
}).join('')
|
|
235
|
+
|
|
236
|
+
return `function initializeState(state) {${initCode}
|
|
237
|
+
}`
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Transform script content
|
|
242
|
+
* Removes state declarations (they're handled by state initialization)
|
|
243
|
+
*/
|
|
244
|
+
function transformScript(scriptContent: string, stateDeclarations: Map<string, string>): string {
|
|
245
|
+
// Remove state declarations - they're handled by initializeState
|
|
246
|
+
let transformed = scriptContent
|
|
247
|
+
|
|
248
|
+
for (const [name] of stateDeclarations.entries()) {
|
|
249
|
+
// Remove "state name = value" declarations
|
|
250
|
+
const stateRegex = new RegExp(`state\\s+${name}\\s*=[^;]*;?`, 'g')
|
|
251
|
+
transformed = transformed.replace(stateRegex, '')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return transformed.trim()
|
|
255
|
+
}
|
|
256
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
/**
|
|
16
|
+
* Wrap an expression into a runtime function with explicit data arguments
|
|
17
|
+
*
|
|
18
|
+
* Phase 6: Supports explicit loaderData, props, stores arguments
|
|
19
|
+
* Phase 7: Supports loop context for expressions inside map iterations
|
|
20
|
+
*/
|
|
21
|
+
export function wrapExpression(
|
|
22
|
+
expr: ExpressionIR,
|
|
23
|
+
dependencies?: ExpressionDataDependencies,
|
|
24
|
+
loopContext?: LoopContext // Phase 7: Loop context for map expressions
|
|
25
|
+
): string {
|
|
26
|
+
// Phase 7: If loop context is provided, use loop-aware wrapper
|
|
27
|
+
if (loopContext && loopContext.variables.length > 0) {
|
|
28
|
+
return wrapExpressionWithLoopContext(expr, loopContext, dependencies)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// If dependencies are provided, use explicit wrapper (Phase 6)
|
|
32
|
+
if (dependencies) {
|
|
33
|
+
return generateExplicitExpressionWrapper(expr, dependencies)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Fallback to legacy wrapper (backwards compatibility)
|
|
37
|
+
const { id, code } = expr
|
|
38
|
+
const escapedCode = code.replace(/`/g, '\\`').replace(/\$/g, '\\$')
|
|
39
|
+
|
|
40
|
+
return `
|
|
41
|
+
// Expression: ${escapedCode}
|
|
42
|
+
const ${id} = (state) => {
|
|
43
|
+
try {
|
|
44
|
+
with (state) {
|
|
45
|
+
return ${code};
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.warn('[Zenith] Expression evaluation error:', ${JSON.stringify(code)}, e);
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
};`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generate all expression wrappers for a set of expressions
|
|
56
|
+
*
|
|
57
|
+
* Phase 6: Accepts dependencies array for explicit data exposure
|
|
58
|
+
* Phase 7: Accepts loop contexts for expressions inside map iterations
|
|
59
|
+
*/
|
|
60
|
+
export function generateExpressionWrappers(
|
|
61
|
+
expressions: ExpressionIR[],
|
|
62
|
+
dependencies?: ExpressionDataDependencies[],
|
|
63
|
+
loopContexts?: (LoopContext | undefined)[] // Phase 7: Loop contexts for each expression
|
|
64
|
+
): string {
|
|
65
|
+
if (expressions.length === 0) {
|
|
66
|
+
return ''
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (dependencies && dependencies.length === expressions.length) {
|
|
70
|
+
// Use explicit wrappers with dependencies and optional loop contexts
|
|
71
|
+
return expressions
|
|
72
|
+
.map((expr, index) => {
|
|
73
|
+
const loopCtx = loopContexts && loopContexts[index] !== undefined
|
|
74
|
+
? loopContexts[index]
|
|
75
|
+
: undefined
|
|
76
|
+
return wrapExpression(expr, dependencies[index], loopCtx)
|
|
77
|
+
})
|
|
78
|
+
.join('\n')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Fallback to legacy wrappers (no dependencies, no loop contexts)
|
|
82
|
+
return expressions.map(expr => wrapExpression(expr)).join('\n')
|
|
83
|
+
}
|
|
84
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generate an expression wrapper that accepts loop context
|
|
15
|
+
*
|
|
16
|
+
* Phase 7: Expressions inside map loops need access to loop variables (e.g., todo, index)
|
|
17
|
+
* in addition to global state (state, loaderData, props, stores)
|
|
18
|
+
*/
|
|
19
|
+
export function wrapExpressionWithLoopContext(
|
|
20
|
+
expr: ExpressionIR,
|
|
21
|
+
loopContext?: LoopContext,
|
|
22
|
+
dependencies?: ExpressionDataDependencies
|
|
23
|
+
): string {
|
|
24
|
+
const { id, code } = expr
|
|
25
|
+
const escapedCode = code.replace(/`/g, '\\`').replace(/\$/g, '\\$')
|
|
26
|
+
|
|
27
|
+
if (!loopContext || loopContext.variables.length === 0) {
|
|
28
|
+
// No loop context - use standard wrapper (will be handled by wrapExpression)
|
|
29
|
+
return ''
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Determine arguments based on dependencies
|
|
33
|
+
const args: string[] = []
|
|
34
|
+
if (dependencies?.usesState || dependencies?.stateProperties.length > 0) args.push('state')
|
|
35
|
+
if (dependencies?.usesLoaderData) args.push('loaderData')
|
|
36
|
+
if (dependencies?.usesProps) args.push('props')
|
|
37
|
+
if (dependencies?.usesStores) args.push('stores')
|
|
38
|
+
|
|
39
|
+
// Phase 7: Always add loopContext as the last argument
|
|
40
|
+
args.push('loopContext')
|
|
41
|
+
|
|
42
|
+
const argsStr = args.join(', ')
|
|
43
|
+
|
|
44
|
+
// Generate function that merges state and loop context
|
|
45
|
+
// Loop context variables take precedence over state properties with the same name
|
|
46
|
+
const loopVarsDecl = loopContext.variables.map(v => ` const ${v} = loopContext?.${v};`).join('\n')
|
|
47
|
+
const loopVarsObject = `{ ${loopContext.variables.join(', ')} }`
|
|
48
|
+
|
|
49
|
+
// Create merged context for expression evaluation
|
|
50
|
+
// Order: loopContext > stores > props > loaderData > state
|
|
51
|
+
const contextMerge: string[] = []
|
|
52
|
+
if (dependencies?.usesState || dependencies?.stateProperties.length > 0) contextMerge.push('state')
|
|
53
|
+
if (dependencies?.usesStores) contextMerge.push('stores')
|
|
54
|
+
if (dependencies?.usesProps) contextMerge.push('props')
|
|
55
|
+
if (dependencies?.usesLoaderData) contextMerge.push('loaderData')
|
|
56
|
+
if (loopContext) contextMerge.push('loopContext')
|
|
57
|
+
|
|
58
|
+
const contextObject = contextMerge.length > 0
|
|
59
|
+
? `const __ctx = Object.assign({}, ${contextMerge.join(', ')});`
|
|
60
|
+
: `const __ctx = loopContext || {};`
|
|
61
|
+
|
|
62
|
+
return `
|
|
63
|
+
// Expression with loop context: ${escapedCode}
|
|
64
|
+
// Loop variables: ${loopContext.variables.join(', ')}
|
|
65
|
+
const ${id} = (${argsStr}) => {
|
|
66
|
+
try {
|
|
67
|
+
${contextObject}
|
|
68
|
+
with (__ctx) {
|
|
69
|
+
return ${code};
|
|
70
|
+
}
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.warn('[Zenith] Expression evaluation error for "${escapedCode}":', e);
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
};`
|
|
76
|
+
}
|
|
77
|
+
|