@zenithbuild/core 0.3.3 → 0.4.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/bin/zen-build.ts +0 -0
- package/bin/zen-dev.ts +0 -0
- package/bin/zen-preview.ts +0 -0
- package/cli/commands/dev.ts +184 -91
- package/cli/utils/branding.ts +25 -0
- package/cli/utils/content.ts +112 -0
- package/cli/utils/logger.ts +22 -16
- package/compiler/parse/parseTemplate.ts +120 -49
- package/compiler/parse/scriptAnalysis.ts +6 -0
- package/compiler/runtime/dataExposure.ts +12 -4
- package/compiler/runtime/generateHydrationBundle.ts +20 -15
- package/compiler/runtime/transformIR.ts +4 -8
- package/compiler/runtime/wrapExpression.ts +23 -12
- package/compiler/runtime/wrapExpressionWithLoop.ts +8 -2
- package/compiler/ssg-build.ts +7 -3
- package/compiler/transform/expressionTransformer.ts +385 -0
- package/compiler/transform/transformNode.ts +1 -1
- package/core/config/index.ts +16 -0
- package/core/config/loader.ts +69 -0
- package/core/config/types.ts +89 -0
- package/core/plugins/index.ts +7 -0
- package/core/plugins/registry.ts +81 -0
- package/dist/cli.js +1 -0
- package/dist/zen-build.js +568 -292
- package/dist/zen-dev.js +568 -292
- package/dist/zen-preview.js +568 -292
- package/dist/zenith.js +568 -292
- package/package.json +4 -1
- package/runtime/bundle-generator.ts +421 -37
- package/runtime/client-runtime.ts +17 -0
|
@@ -30,52 +30,124 @@ function stripBlocks(html: string): string {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* Handles nested braces for complex expressions like props={{ title: 'Home' }}
|
|
33
|
+
* Find the end of a balanced brace expression, handling strings and template literals
|
|
34
|
+
* Returns the index after the closing brace, or -1 if unbalanced
|
|
36
35
|
*/
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
let
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let
|
|
43
|
-
|
|
36
|
+
function findBalancedBraceEnd(html: string, startIndex: number): number {
|
|
37
|
+
let braceCount = 1
|
|
38
|
+
let i = startIndex + 1
|
|
39
|
+
let inString = false
|
|
40
|
+
let stringChar = ''
|
|
41
|
+
let inTemplate = false
|
|
42
|
+
|
|
43
|
+
while (i < html.length && braceCount > 0) {
|
|
44
|
+
const char = html[i]
|
|
45
|
+
const prevChar = i > 0 ? html[i - 1] : ''
|
|
46
|
+
|
|
47
|
+
// Handle escape sequences
|
|
48
|
+
if (prevChar === '\\') {
|
|
49
|
+
i++
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
// Handle string literals (not inside template)
|
|
54
|
+
if (!inString && !inTemplate && (char === '"' || char === "'")) {
|
|
55
|
+
inString = true
|
|
56
|
+
stringChar = char
|
|
57
|
+
i++
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
if (inString && char === stringChar) {
|
|
62
|
+
inString = false
|
|
63
|
+
stringChar = ''
|
|
64
|
+
i++
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
52
67
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
while (i < html.length && braceCount > 0) {
|
|
57
|
-
if (html[i] === '{') braceCount++
|
|
58
|
-
else if (html[i] === '}') braceCount--
|
|
68
|
+
// Handle template literals
|
|
69
|
+
if (!inString && !inTemplate && char === '`') {
|
|
70
|
+
inTemplate = true
|
|
59
71
|
i++
|
|
72
|
+
continue
|
|
60
73
|
}
|
|
61
74
|
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
if (inTemplate && char === '`') {
|
|
76
|
+
inTemplate = false
|
|
77
|
+
i++
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
66
80
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
// Handle ${} inside template literals - need to track nested braces
|
|
82
|
+
if (inTemplate && char === '$' && html[i + 1] === '{') {
|
|
83
|
+
// Skip the ${ and count as opening brace
|
|
84
|
+
i += 2
|
|
85
|
+
let templateBraceCount = 1
|
|
86
|
+
while (i < html.length && templateBraceCount > 0) {
|
|
87
|
+
if (html[i] === '{') templateBraceCount++
|
|
88
|
+
else if (html[i] === '}') templateBraceCount--
|
|
89
|
+
i++
|
|
90
|
+
}
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
70
93
|
|
|
71
|
-
|
|
72
|
-
|
|
94
|
+
// Count braces only when not in strings or templates
|
|
95
|
+
if (!inString && !inTemplate) {
|
|
96
|
+
if (char === '{') braceCount++
|
|
97
|
+
else if (char === '}') braceCount--
|
|
73
98
|
}
|
|
99
|
+
|
|
100
|
+
i++
|
|
74
101
|
}
|
|
75
102
|
|
|
76
|
-
|
|
103
|
+
return braceCount === 0 ? i : -1
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Normalize expressions before parsing
|
|
108
|
+
* Replaces both attr={expr} and {textExpr} with placeholders so parse5 can parse the HTML correctly
|
|
109
|
+
* without being confused by tags or braces inside expressions.
|
|
110
|
+
*
|
|
111
|
+
* Uses balanced brace parsing to correctly handle:
|
|
112
|
+
* - String literals with braces inside
|
|
113
|
+
* - Template literals with ${} interpolations
|
|
114
|
+
* - Arrow functions with object returns
|
|
115
|
+
* - Multi-line JSX expressions
|
|
116
|
+
*/
|
|
117
|
+
function normalizeAllExpressions(html: string): { normalized: string; expressions: Map<string, string> } {
|
|
118
|
+
const exprMap = new Map<string, string>()
|
|
119
|
+
let exprCounter = 0
|
|
120
|
+
let result = ''
|
|
121
|
+
let lastPos = 0
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < html.length; i++) {
|
|
124
|
+
// Look for { and check if it's an expression
|
|
125
|
+
// We handle both text expressions and attribute expressions: attr={...}
|
|
126
|
+
if (html[i] === '{') {
|
|
127
|
+
const j = findBalancedBraceEnd(html, i)
|
|
128
|
+
|
|
129
|
+
if (j !== -1 && j > i + 1) {
|
|
130
|
+
const expr = html.substring(i + 1, j - 1).trim()
|
|
131
|
+
|
|
132
|
+
// Skip empty expressions
|
|
133
|
+
if (expr.length === 0) {
|
|
134
|
+
i++
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const placeholder = `__ZEN_EXPR_${exprCounter++}`
|
|
139
|
+
exprMap.set(placeholder, expr)
|
|
77
140
|
|
|
78
|
-
|
|
141
|
+
result += html.substring(lastPos, i)
|
|
142
|
+
result += placeholder
|
|
143
|
+
lastPos = j
|
|
144
|
+
i = j - 1
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
result += html.substring(lastPos)
|
|
149
|
+
|
|
150
|
+
return { normalized: result, expressions: exprMap }
|
|
79
151
|
}
|
|
80
152
|
|
|
81
153
|
|
|
@@ -103,14 +175,15 @@ function extractExpressionsFromText(
|
|
|
103
175
|
text: string,
|
|
104
176
|
baseLocation: SourceLocation,
|
|
105
177
|
expressions: ExpressionIR[],
|
|
106
|
-
|
|
178
|
+
normalizedExprs: Map<string, string>,
|
|
179
|
+
loopContext?: LoopContext
|
|
107
180
|
): { processedText: string; nodes: (TextNode | ExpressionNode)[] } {
|
|
108
181
|
const nodes: (TextNode | ExpressionNode)[] = []
|
|
109
182
|
let processedText = ''
|
|
110
183
|
let currentIndex = 0
|
|
111
184
|
|
|
112
|
-
// Match
|
|
113
|
-
const expressionRegex =
|
|
185
|
+
// Match __ZEN_EXPR_N placeholders
|
|
186
|
+
const expressionRegex = /__ZEN_EXPR_\d+/g
|
|
114
187
|
let match
|
|
115
188
|
|
|
116
189
|
while ((match = expressionRegex.exec(text)) !== null) {
|
|
@@ -127,12 +200,13 @@ function extractExpressionsFromText(
|
|
|
127
200
|
processedText += beforeExpr
|
|
128
201
|
}
|
|
129
202
|
|
|
130
|
-
//
|
|
131
|
-
const
|
|
203
|
+
// Resolve placeholder to original expression code
|
|
204
|
+
const placeholder = match[0]
|
|
205
|
+
const exprCode = (normalizedExprs.get(placeholder) || '').trim()
|
|
132
206
|
const exprId = generateExpressionId()
|
|
133
207
|
const exprLocation: SourceLocation = {
|
|
134
208
|
line: baseLocation.line,
|
|
135
|
-
column: baseLocation.column + match.index
|
|
209
|
+
column: baseLocation.column + match.index
|
|
136
210
|
}
|
|
137
211
|
|
|
138
212
|
const exprIR: ExpressionIR = {
|
|
@@ -142,11 +216,9 @@ function extractExpressionsFromText(
|
|
|
142
216
|
}
|
|
143
217
|
expressions.push(exprIR)
|
|
144
218
|
|
|
145
|
-
// Phase 7:
|
|
219
|
+
// Phase 7: Loop context detection and attachment
|
|
146
220
|
const mapLoopContext = extractLoopContextFromExpression(exprIR)
|
|
147
221
|
const activeLoopContext = mergeLoopContext(loopContext, mapLoopContext)
|
|
148
|
-
|
|
149
|
-
// Phase 7: Attach loop context if expression references loop variables
|
|
150
222
|
const attachedLoopContext = shouldAttachLoopContext(exprIR, activeLoopContext)
|
|
151
223
|
|
|
152
224
|
nodes.push({
|
|
@@ -156,7 +228,7 @@ function extractExpressionsFromText(
|
|
|
156
228
|
loopContext: attachedLoopContext
|
|
157
229
|
})
|
|
158
230
|
|
|
159
|
-
processedText += `{${exprCode}}`
|
|
231
|
+
processedText += `{${exprCode}}`
|
|
160
232
|
currentIndex = match.index + match[0].length
|
|
161
233
|
}
|
|
162
234
|
|
|
@@ -260,7 +332,7 @@ function parseNode(
|
|
|
260
332
|
|
|
261
333
|
// Extract expressions from text
|
|
262
334
|
// Phase 7: Pass loop context to detect map expressions and attach context
|
|
263
|
-
const { nodes } = extractExpressionsFromText(
|
|
335
|
+
const { nodes } = extractExpressionsFromText(node.value, location, expressions, normalizedExprs, parentLoopContext)
|
|
264
336
|
|
|
265
337
|
// If single text node with no expressions, return it
|
|
266
338
|
if (nodes.length === 1 && nodes[0] && nodes[0].type === 'text') {
|
|
@@ -375,7 +447,7 @@ function parseNode(
|
|
|
375
447
|
// Handle text nodes that may contain expressions
|
|
376
448
|
const text = child.value || ''
|
|
377
449
|
const location = getLocation(child, originalHtml)
|
|
378
|
-
const { nodes: textNodes } = extractExpressionsFromText(text, location, expressions, parentLoopContext)
|
|
450
|
+
const { nodes: textNodes } = extractExpressionsFromText(text, location, expressions, normalizedExprs, parentLoopContext)
|
|
379
451
|
|
|
380
452
|
// Add all nodes from text (can be multiple: text + expression + text)
|
|
381
453
|
for (const textNode of textNodes) {
|
|
@@ -422,12 +494,12 @@ export function parseTemplate(html: string, filePath: string): TemplateIR {
|
|
|
422
494
|
// Strip script and style blocks
|
|
423
495
|
let templateHtml = stripBlocks(html)
|
|
424
496
|
|
|
425
|
-
// Normalize
|
|
426
|
-
const { normalized, expressions: normalizedExprs } =
|
|
497
|
+
// Normalize all expressions so parse5 can parse them safely
|
|
498
|
+
const { normalized, expressions: normalizedExprs } = normalizeAllExpressions(templateHtml)
|
|
427
499
|
templateHtml = normalized
|
|
428
500
|
|
|
429
501
|
try {
|
|
430
|
-
// Parse HTML using parseFragment
|
|
502
|
+
// Parse HTML using parseFragment
|
|
431
503
|
const fragment = parseFragment(templateHtml, {
|
|
432
504
|
sourceCodeLocationInfo: true
|
|
433
505
|
})
|
|
@@ -436,7 +508,6 @@ export function parseTemplate(html: string, filePath: string): TemplateIR {
|
|
|
436
508
|
const nodes: TemplateNode[] = []
|
|
437
509
|
|
|
438
510
|
// Parse fragment children
|
|
439
|
-
// Phase 7: Start with no loop context (top-level expressions)
|
|
440
511
|
if (fragment.childNodes) {
|
|
441
512
|
for (const node of fragment.childNodes) {
|
|
442
513
|
const parsed = parseNode(node, templateHtml, expressions, normalizedExprs, undefined)
|
|
@@ -62,6 +62,12 @@ export function transformStateDeclarations(script: string): string {
|
|
|
62
62
|
// Remove zenith/runtime imports
|
|
63
63
|
transformed = transformed.replace(/import\s+{[^}]+}\s+from\s+['"]zenith\/runtime['"]\s*;?[ \t]*/g, '')
|
|
64
64
|
|
|
65
|
+
// Transform zenith:content imports to global lookups
|
|
66
|
+
transformed = transformed.replace(
|
|
67
|
+
/import\s*{\s*([^}]+)\s*}\s*from\s*['"]zenith:content['"]\s*;?/g,
|
|
68
|
+
(_, imports) => `const { ${imports.trim()} } = window.__zenith;`
|
|
69
|
+
)
|
|
70
|
+
|
|
65
71
|
return transformed.trim()
|
|
66
72
|
}
|
|
67
73
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ExpressionIR } from '../ir/types'
|
|
9
9
|
import { CompilerError } from '../errors/compilerError'
|
|
10
|
+
import { transformExpressionJSX } from '../transform/expressionTransformer'
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Data dependency information for an expression
|
|
@@ -263,10 +264,17 @@ export function generateExplicitExpressionWrapper(
|
|
|
263
264
|
? `const __ctx = Object.assign({}, ${contextParts.join(', ')});\n with (__ctx) {`
|
|
264
265
|
: 'with (state) {'
|
|
265
266
|
|
|
266
|
-
|
|
267
|
+
// Escape the code for use in a single-line comment (replace newlines with spaces)
|
|
268
|
+
const commentCode = code.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ').substring(0, 100)
|
|
269
|
+
|
|
270
|
+
// JSON.stringify the code for error messages (properly escapes quotes, newlines, etc.)
|
|
271
|
+
const jsonEscapedCode = JSON.stringify(code)
|
|
272
|
+
|
|
273
|
+
// Transform JSX
|
|
274
|
+
const transformedCode = transformExpressionJSX(code)
|
|
267
275
|
|
|
268
276
|
return `
|
|
269
|
-
// Expression: ${
|
|
277
|
+
// Expression: ${commentCode}${code.length > 100 ? '...' : ''}
|
|
270
278
|
// Dependencies: ${JSON.stringify({
|
|
271
279
|
loaderData: dependencies.usesLoaderData,
|
|
272
280
|
props: dependencies.usesProps,
|
|
@@ -276,10 +284,10 @@ export function generateExplicitExpressionWrapper(
|
|
|
276
284
|
const ${id} = (${paramList}) => {
|
|
277
285
|
try {
|
|
278
286
|
${contextCode}
|
|
279
|
-
return ${
|
|
287
|
+
return ${transformedCode};
|
|
280
288
|
}
|
|
281
289
|
} catch (e) {
|
|
282
|
-
console.warn('[Zenith] Expression evaluation error:', ${
|
|
290
|
+
console.warn('[Zenith] Expression evaluation error:', ${jsonEscapedCode}, e);
|
|
283
291
|
return undefined;
|
|
284
292
|
}
|
|
285
293
|
};`
|
|
@@ -45,27 +45,32 @@ export function generateHydrationRuntime(): string {
|
|
|
45
45
|
// Handle different result types
|
|
46
46
|
if (result === null || result === undefined || result === false) {
|
|
47
47
|
node.textContent = '';
|
|
48
|
-
} else if (typeof result === 'string'
|
|
48
|
+
} else if (typeof result === 'string') {
|
|
49
|
+
if (result.trim().startsWith('<')) {
|
|
50
|
+
// Render as HTML
|
|
51
|
+
node.innerHTML = result;
|
|
52
|
+
} else {
|
|
53
|
+
node.textContent = result;
|
|
54
|
+
}
|
|
55
|
+
} else if (typeof result === 'number') {
|
|
49
56
|
node.textContent = String(result);
|
|
50
57
|
} else if (result instanceof Node) {
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
58
|
+
// Clear node and append result
|
|
59
|
+
node.innerHTML = '';
|
|
60
|
+
node.appendChild(result);
|
|
55
61
|
} else if (Array.isArray(result)) {
|
|
56
62
|
// Handle array results (for map expressions)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
63
|
+
node.innerHTML = '';
|
|
64
|
+
const fragment = document.createDocumentFragment();
|
|
65
|
+
for (let i = 0; i < result.length; i++) {
|
|
66
|
+
const item = result[i];
|
|
67
|
+
if (item instanceof Node) {
|
|
68
|
+
fragment.appendChild(item);
|
|
69
|
+
} else {
|
|
70
|
+
fragment.appendChild(document.createTextNode(String(item)));
|
|
66
71
|
}
|
|
67
|
-
node.parentNode.replaceChild(fragment, node);
|
|
68
72
|
}
|
|
73
|
+
node.appendChild(fragment);
|
|
69
74
|
} else {
|
|
70
75
|
node.textContent = String(result);
|
|
71
76
|
}
|
|
@@ -118,20 +118,16 @@ ${parts.hydrationRuntime}
|
|
|
118
118
|
|
|
119
119
|
${parts.navigationRuntime}
|
|
120
120
|
|
|
121
|
-
${parts.stateInitCode ? `// State initialization
|
|
122
|
-
${parts.stateInitCode}` : ''}
|
|
123
|
-
|
|
124
121
|
${parts.stylesCode ? `// Style injection
|
|
125
122
|
${parts.stylesCode}` : ''}
|
|
126
123
|
|
|
127
|
-
// User script code
|
|
128
|
-
(function() {
|
|
129
|
-
'use strict';
|
|
130
|
-
|
|
124
|
+
// User script code - executed first to define variables needed by state initialization
|
|
131
125
|
${parts.scriptCode ? parts.scriptCode : ''}
|
|
132
126
|
|
|
133
127
|
${functionRegistrations}
|
|
134
|
-
|
|
128
|
+
|
|
129
|
+
${parts.stateInitCode ? `// State initialization
|
|
130
|
+
${parts.stateInitCode}` : ''}
|
|
135
131
|
|
|
136
132
|
// Export hydration functions
|
|
137
133
|
if (typeof window !== 'undefined') {
|
|
@@ -12,6 +12,8 @@ import type { ExpressionDataDependencies } from './dataExposure'
|
|
|
12
12
|
import { generateExplicitExpressionWrapper } from './dataExposure'
|
|
13
13
|
import { wrapExpressionWithLoopContext } from './wrapExpressionWithLoop'
|
|
14
14
|
|
|
15
|
+
import { transformExpressionJSX } from '../transform/expressionTransformer'
|
|
16
|
+
|
|
15
17
|
/**
|
|
16
18
|
* Wrap an expression into a runtime function with explicit data arguments
|
|
17
19
|
*
|
|
@@ -23,29 +25,38 @@ export function wrapExpression(
|
|
|
23
25
|
dependencies?: ExpressionDataDependencies,
|
|
24
26
|
loopContext?: LoopContext // Phase 7: Loop context for map expressions
|
|
25
27
|
): string {
|
|
28
|
+
const { id, code } = expr
|
|
29
|
+
|
|
26
30
|
// Phase 7: If loop context is provided, use loop-aware wrapper
|
|
27
31
|
if (loopContext && loopContext.variables.length > 0) {
|
|
28
32
|
return wrapExpressionWithLoopContext(expr, loopContext, dependencies)
|
|
29
33
|
}
|
|
30
|
-
|
|
34
|
+
|
|
31
35
|
// If dependencies are provided, use explicit wrapper (Phase 6)
|
|
32
36
|
if (dependencies) {
|
|
33
37
|
return generateExplicitExpressionWrapper(expr, dependencies)
|
|
34
38
|
}
|
|
35
|
-
|
|
39
|
+
|
|
36
40
|
// Fallback to legacy wrapper (backwards compatibility)
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
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
|
+
|
|
40
47
|
return `
|
|
41
|
-
// Expression: ${
|
|
48
|
+
// Expression: ${commentCode}${code.length > 100 ? '...' : ''}
|
|
42
49
|
const ${id} = (state) => {
|
|
43
50
|
try {
|
|
51
|
+
// Expose zenith helpers for JSX and content
|
|
52
|
+
const __zenith = window.__zenith || {};
|
|
53
|
+
const zenCollection = __zenith.zenCollection || ((name) => ({ get: () => [] }));
|
|
54
|
+
|
|
44
55
|
with (state) {
|
|
45
|
-
return ${
|
|
56
|
+
return ${transformedCode};
|
|
46
57
|
}
|
|
47
58
|
} catch (e) {
|
|
48
|
-
console.warn('[Zenith] Expression evaluation error:', ${
|
|
59
|
+
console.warn('[Zenith] Expression evaluation error:', ${jsonEscapedCode}, e);
|
|
49
60
|
return undefined;
|
|
50
61
|
}
|
|
51
62
|
};`
|
|
@@ -65,19 +76,19 @@ export function generateExpressionWrappers(
|
|
|
65
76
|
if (expressions.length === 0) {
|
|
66
77
|
return ''
|
|
67
78
|
}
|
|
68
|
-
|
|
79
|
+
|
|
69
80
|
if (dependencies && dependencies.length === expressions.length) {
|
|
70
81
|
// Use explicit wrappers with dependencies and optional loop contexts
|
|
71
82
|
return expressions
|
|
72
83
|
.map((expr, index) => {
|
|
73
|
-
const loopCtx = loopContexts && loopContexts[index] !== undefined
|
|
74
|
-
? loopContexts[index]
|
|
84
|
+
const loopCtx = loopContexts && loopContexts[index] !== undefined
|
|
85
|
+
? loopContexts[index]
|
|
75
86
|
: undefined
|
|
76
87
|
return wrapExpression(expr, dependencies[index], loopCtx)
|
|
77
88
|
})
|
|
78
89
|
.join('\n')
|
|
79
90
|
}
|
|
80
|
-
|
|
91
|
+
|
|
81
92
|
// Fallback to legacy wrappers (no dependencies, no loop contexts)
|
|
82
93
|
return expressions.map(expr => wrapExpression(expr)).join('\n')
|
|
83
94
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { ExpressionIR, LoopContext } from '../ir/types'
|
|
11
11
|
import type { ExpressionDataDependencies } from './dataExposure'
|
|
12
|
+
import { transformExpressionJSX } from '../transform/expressionTransformer'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Generate an expression wrapper that accepts loop context
|
|
@@ -59,6 +60,12 @@ export function wrapExpressionWithLoopContext(
|
|
|
59
60
|
? `const __ctx = Object.assign({}, ${contextMerge.join(', ')});`
|
|
60
61
|
: `const __ctx = loopContext || {};`
|
|
61
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
|
+
|
|
62
69
|
return `
|
|
63
70
|
// Expression with loop context: ${escapedCode}
|
|
64
71
|
// Loop variables: ${loopContext.variables.join(', ')}
|
|
@@ -66,7 +73,7 @@ export function wrapExpressionWithLoopContext(
|
|
|
66
73
|
try {
|
|
67
74
|
${contextObject}
|
|
68
75
|
with (__ctx) {
|
|
69
|
-
return ${
|
|
76
|
+
return ${transformedCode};
|
|
70
77
|
}
|
|
71
78
|
} catch (e) {
|
|
72
79
|
console.warn('[Zenith] Expression evaluation error for "${escapedCode}":', e);
|
|
@@ -74,4 +81,3 @@ export function wrapExpressionWithLoopContext(
|
|
|
74
81
|
}
|
|
75
82
|
};`
|
|
76
83
|
}
|
|
77
|
-
|
package/compiler/ssg-build.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { processLayout } from "./transform/layoutProcessor"
|
|
|
19
19
|
import { discoverPages, generateRouteDefinition } from "../router/manifest"
|
|
20
20
|
import { analyzePageSource, getAnalysisSummary, getBuildOutputType, type PageAnalysis } from "./build-analyzer"
|
|
21
21
|
import { generateBundleJS } from "../runtime/bundle-generator"
|
|
22
|
+
import { loadContent } from "../cli/utils/content"
|
|
22
23
|
|
|
23
24
|
// ============================================
|
|
24
25
|
// Types
|
|
@@ -130,7 +131,7 @@ function compilePage(
|
|
|
130
131
|
* Static pages: no JS references
|
|
131
132
|
* Hydrated pages: bundle.js + page-specific JS
|
|
132
133
|
*/
|
|
133
|
-
function generatePageHTML(page: CompiledPage, globalStyles: string): string {
|
|
134
|
+
function generatePageHTML(page: CompiledPage, globalStyles: string, contentData: any): string {
|
|
134
135
|
const { html, styles, analysis, routePath, pageScript } = page
|
|
135
136
|
|
|
136
137
|
// Combine styles
|
|
@@ -141,6 +142,7 @@ function generatePageHTML(page: CompiledPage, globalStyles: string): string {
|
|
|
141
142
|
let scriptTags = ''
|
|
142
143
|
if (analysis.needsHydration) {
|
|
143
144
|
scriptTags = `
|
|
145
|
+
<script>window.__ZENITH_CONTENT__ = ${JSON.stringify(contentData)};</script>
|
|
144
146
|
<script src="/assets/bundle.js"></script>`
|
|
145
147
|
|
|
146
148
|
if (pageScript) {
|
|
@@ -238,6 +240,8 @@ ${page.pageScript}
|
|
|
238
240
|
*/
|
|
239
241
|
export function buildSSG(options: SSGBuildOptions): void {
|
|
240
242
|
const { pagesDir, outDir, baseDir = path.dirname(pagesDir) } = options
|
|
243
|
+
const contentDir = path.join(baseDir, 'content')
|
|
244
|
+
const contentData = loadContent(contentDir)
|
|
241
245
|
|
|
242
246
|
console.log('🔨 Zenith SSG Build')
|
|
243
247
|
console.log(` Pages: ${pagesDir}`)
|
|
@@ -316,7 +320,7 @@ export function buildSSG(options: SSGBuildOptions): void {
|
|
|
316
320
|
fs.mkdirSync(pageOutDir, { recursive: true })
|
|
317
321
|
|
|
318
322
|
// Generate and write HTML
|
|
319
|
-
const html = generatePageHTML(page, globalStyles)
|
|
323
|
+
const html = generatePageHTML(page, globalStyles, contentData)
|
|
320
324
|
fs.writeFileSync(path.join(pageOutDir, 'index.html'), html)
|
|
321
325
|
|
|
322
326
|
// Write page-specific JS if needed
|
|
@@ -347,7 +351,7 @@ export function buildSSG(options: SSGBuildOptions): void {
|
|
|
347
351
|
if (fs.existsSync(custom404Path)) {
|
|
348
352
|
try {
|
|
349
353
|
const compiled = compilePage(custom404Path, pagesDir, baseDir)
|
|
350
|
-
const html = generatePageHTML(compiled, globalStyles)
|
|
354
|
+
const html = generatePageHTML(compiled, globalStyles, contentData)
|
|
351
355
|
fs.writeFileSync(path.join(outDir, '404.html'), html)
|
|
352
356
|
console.log('📦 Generated 404.html (custom)')
|
|
353
357
|
has404 = true
|