@zenithbuild/core 0.1.0 → 0.3.1
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 +1 -1
- package/README.md +24 -40
- package/bin/zen-build.ts +2 -0
- package/bin/zen-dev.ts +2 -0
- package/bin/zen-preview.ts +2 -0
- package/bin/zenith.ts +2 -0
- package/cli/commands/add.ts +37 -0
- package/cli/commands/build.ts +37 -0
- package/cli/commands/create.ts +702 -0
- package/cli/commands/dev.ts +197 -0
- package/cli/commands/index.ts +112 -0
- package/cli/commands/preview.ts +62 -0
- package/cli/commands/remove.ts +33 -0
- package/cli/index.ts +10 -0
- package/cli/main.ts +101 -0
- package/cli/utils/branding.ts +153 -0
- package/cli/utils/logger.ts +40 -0
- package/cli/utils/plugin-manager.ts +114 -0
- package/cli/utils/project.ts +71 -0
- package/compiler/build-analyzer.ts +122 -0
- package/compiler/discovery/layouts.ts +61 -0
- package/compiler/index.ts +40 -24
- package/compiler/ir/types.ts +1 -0
- package/compiler/parse/parseScript.ts +29 -5
- package/compiler/parse/parseTemplate.ts +96 -58
- package/compiler/parse/scriptAnalysis.ts +77 -0
- package/compiler/runtime/dataExposure.ts +49 -31
- package/compiler/runtime/generateDOM.ts +18 -17
- package/compiler/runtime/generateHydrationBundle.ts +24 -5
- package/compiler/runtime/transformIR.ts +140 -49
- package/compiler/runtime/wrapExpressionWithLoop.ts +11 -11
- package/compiler/spa-build.ts +70 -153
- package/compiler/ssg-build.ts +412 -0
- package/compiler/transform/layoutProcessor.ts +132 -0
- package/compiler/transform/transformNode.ts +19 -19
- package/dist/cli.js +11648 -0
- package/dist/zen-build.js +11659 -0
- package/dist/zen-dev.js +11659 -0
- package/dist/zen-preview.js +11659 -0
- package/dist/zenith.js +11659 -0
- package/package.json +22 -2
- package/runtime/bundle-generator.ts +416 -0
- package/runtime/client-runtime.ts +532 -0
- package/.eslintignore +0 -15
- package/.gitattributes +0 -2
- package/.github/ISSUE_TEMPLATE/compiler-errors-for-invalid-state-declarations.md +0 -25
- package/.github/ISSUE_TEMPLATE/new_ticket.yaml +0 -34
- package/.github/pull_request_template.md +0 -15
- package/.github/workflows/discord-changelog.yml +0 -141
- package/.github/workflows/discord-notify.yml +0 -242
- package/.github/workflows/discord-version.yml +0 -195
- package/.prettierignore +0 -13
- package/.prettierrc +0 -21
- package/.zen.d.ts +0 -15
- package/app/components/Button.zen +0 -46
- package/app/components/Link.zen +0 -11
- package/app/favicon.ico +0 -0
- package/app/layouts/Main.zen +0 -59
- package/app/pages/about.zen +0 -23
- package/app/pages/blog/[id].zen +0 -53
- package/app/pages/blog/index.zen +0 -32
- package/app/pages/dynamic-dx.zen +0 -712
- package/app/pages/dynamic-primitives.zen +0 -453
- package/app/pages/index.zen +0 -154
- package/app/pages/navigation-demo.zen +0 -229
- package/app/pages/posts/[...slug].zen +0 -61
- package/app/pages/primitives-demo.zen +0 -273
- 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 +0 -601
- package/assets/logos/README.md +0 -54
- package/assets/logos/zen.icns +0 -0
- package/bun.lock +0 -39
- package/compiler/legacy/binding.ts +0 -254
- package/compiler/legacy/bindings.ts +0 -338
- package/compiler/legacy/component-process.ts +0 -1208
- package/compiler/legacy/component.ts +0 -301
- package/compiler/legacy/event.ts +0 -50
- package/compiler/legacy/expression.ts +0 -1149
- package/compiler/legacy/mutation.ts +0 -280
- package/compiler/legacy/parse.ts +0 -299
- package/compiler/legacy/split.ts +0 -608
- package/compiler/legacy/types.ts +0 -32
- package/docs/COMMENTS.md +0 -111
- package/docs/COMMITS.md +0 -36
- package/docs/CONTRIBUTING.md +0 -116
- package/docs/STYLEGUIDE.md +0 -62
- package/scripts/webhook-proxy.ts +0 -213
|
@@ -32,22 +32,53 @@ function stripBlocks(html: string): string {
|
|
|
32
32
|
/**
|
|
33
33
|
* Normalize attribute expressions before parsing
|
|
34
34
|
* Replaces attr={expr} with attr="__ZEN_EXPR_base64" so parse5 can parse it
|
|
35
|
+
* Handles nested braces for complex expressions like props={{ title: 'Home' }}
|
|
35
36
|
*/
|
|
36
37
|
function normalizeAttributeExpressions(html: string): { normalized: string; expressions: Map<string, string> } {
|
|
37
38
|
const exprMap = new Map<string, string>()
|
|
38
39
|
let exprCounter = 0
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
|
|
41
|
+
// Improved matching to handle nested braces: attr={ { foo: { bar: 1 } } }
|
|
42
|
+
let lastPos = 0
|
|
43
|
+
let normalized = ''
|
|
44
|
+
|
|
45
|
+
// Use a regex to find the start of an attribute expression: \w+={
|
|
46
|
+
const startRegex = /(\w+)=\{/g
|
|
47
|
+
let match
|
|
48
|
+
|
|
49
|
+
while ((match = startRegex.exec(html)) !== null) {
|
|
50
|
+
const attrName = match[1]
|
|
51
|
+
const startIndex = match.index + match[0].length - 1 // Index of '{'
|
|
52
|
+
|
|
53
|
+
// Find matching closing brace
|
|
54
|
+
let braceCount = 1
|
|
55
|
+
let i = startIndex + 1
|
|
56
|
+
while (i < html.length && braceCount > 0) {
|
|
57
|
+
if (html[i] === '{') braceCount++
|
|
58
|
+
else if (html[i] === '}') braceCount--
|
|
59
|
+
i++
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (braceCount === 0) {
|
|
63
|
+
const expr = html.substring(startIndex + 1, i - 1).trim()
|
|
64
|
+
const placeholder = `__ZEN_EXPR_${exprCounter++}`
|
|
65
|
+
exprMap.set(placeholder, expr)
|
|
66
|
+
|
|
67
|
+
normalized += html.substring(lastPos, match.index)
|
|
68
|
+
normalized += `${attrName}="${placeholder}"`
|
|
69
|
+
lastPos = i
|
|
70
|
+
|
|
71
|
+
// Update regex index to continue after the closing brace
|
|
72
|
+
startRegex.lastIndex = i
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
normalized += html.substring(lastPos)
|
|
77
|
+
|
|
48
78
|
return { normalized, expressions: exprMap }
|
|
49
79
|
}
|
|
50
80
|
|
|
81
|
+
|
|
51
82
|
/**
|
|
52
83
|
* Calculate source location from parse5 node
|
|
53
84
|
*/
|
|
@@ -77,11 +108,11 @@ function extractExpressionsFromText(
|
|
|
77
108
|
const nodes: (TextNode | ExpressionNode)[] = []
|
|
78
109
|
let processedText = ''
|
|
79
110
|
let currentIndex = 0
|
|
80
|
-
|
|
111
|
+
|
|
81
112
|
// Match { ... } expressions (non-greedy)
|
|
82
113
|
const expressionRegex = /\{([^}]+)\}/g
|
|
83
114
|
let match
|
|
84
|
-
|
|
115
|
+
|
|
85
116
|
while ((match = expressionRegex.exec(text)) !== null) {
|
|
86
117
|
const beforeExpr = text.substring(currentIndex, match.index)
|
|
87
118
|
if (beforeExpr) {
|
|
@@ -95,7 +126,7 @@ function extractExpressionsFromText(
|
|
|
95
126
|
})
|
|
96
127
|
processedText += beforeExpr
|
|
97
128
|
}
|
|
98
|
-
|
|
129
|
+
|
|
99
130
|
// Extract expression
|
|
100
131
|
const exprCode = (match[1] || '').trim()
|
|
101
132
|
const exprId = generateExpressionId()
|
|
@@ -103,32 +134,32 @@ function extractExpressionsFromText(
|
|
|
103
134
|
line: baseLocation.line,
|
|
104
135
|
column: baseLocation.column + match.index + 1 // +1 for opening brace
|
|
105
136
|
}
|
|
106
|
-
|
|
137
|
+
|
|
107
138
|
const exprIR: ExpressionIR = {
|
|
108
139
|
id: exprId,
|
|
109
140
|
code: exprCode,
|
|
110
141
|
location: exprLocation
|
|
111
142
|
}
|
|
112
143
|
expressions.push(exprIR)
|
|
113
|
-
|
|
144
|
+
|
|
114
145
|
// Phase 7: Detect if this is a map expression and extract loop context
|
|
115
146
|
const mapLoopContext = extractLoopContextFromExpression(exprIR)
|
|
116
147
|
const activeLoopContext = mergeLoopContext(loopContext, mapLoopContext)
|
|
117
|
-
|
|
148
|
+
|
|
118
149
|
// Phase 7: Attach loop context if expression references loop variables
|
|
119
150
|
const attachedLoopContext = shouldAttachLoopContext(exprIR, activeLoopContext)
|
|
120
|
-
|
|
151
|
+
|
|
121
152
|
nodes.push({
|
|
122
153
|
type: 'expression',
|
|
123
154
|
expression: exprId,
|
|
124
155
|
location: exprLocation,
|
|
125
156
|
loopContext: attachedLoopContext
|
|
126
157
|
})
|
|
127
|
-
|
|
158
|
+
|
|
128
159
|
processedText += `{${exprCode}}` // Keep in processed text for now
|
|
129
160
|
currentIndex = match.index + match[0].length
|
|
130
161
|
}
|
|
131
|
-
|
|
162
|
+
|
|
132
163
|
// Add remaining text
|
|
133
164
|
const remaining = text.substring(currentIndex)
|
|
134
165
|
if (remaining) {
|
|
@@ -142,7 +173,7 @@ function extractExpressionsFromText(
|
|
|
142
173
|
})
|
|
143
174
|
processedText += remaining
|
|
144
175
|
}
|
|
145
|
-
|
|
176
|
+
|
|
146
177
|
// If no expressions found, return single text node
|
|
147
178
|
if (nodes.length === 0) {
|
|
148
179
|
nodes.push({
|
|
@@ -152,7 +183,7 @@ function extractExpressionsFromText(
|
|
|
152
183
|
})
|
|
153
184
|
processedText = text
|
|
154
185
|
}
|
|
155
|
-
|
|
186
|
+
|
|
156
187
|
return { processedText, nodes }
|
|
157
188
|
}
|
|
158
189
|
|
|
@@ -173,41 +204,41 @@ function parseAttributeValue(
|
|
|
173
204
|
if (!exprCode) {
|
|
174
205
|
throw new Error(`Normalized expression placeholder not found: ${value}`)
|
|
175
206
|
}
|
|
176
|
-
|
|
207
|
+
|
|
177
208
|
const exprId = generateExpressionId()
|
|
178
|
-
|
|
209
|
+
|
|
179
210
|
expressions.push({
|
|
180
211
|
id: exprId,
|
|
181
212
|
code: exprCode,
|
|
182
213
|
location: baseLocation
|
|
183
214
|
})
|
|
184
|
-
|
|
215
|
+
|
|
185
216
|
return {
|
|
186
217
|
id: exprId,
|
|
187
218
|
code: exprCode,
|
|
188
219
|
location: baseLocation
|
|
189
220
|
}
|
|
190
221
|
}
|
|
191
|
-
|
|
222
|
+
|
|
192
223
|
// Check if attribute value is an expression { ... } (shouldn't happen after normalization)
|
|
193
224
|
const exprMatch = value.match(/^\{([^}]+)\}$/)
|
|
194
225
|
if (exprMatch && exprMatch[1]) {
|
|
195
226
|
const exprCode = exprMatch[1].trim()
|
|
196
227
|
const exprId = generateExpressionId()
|
|
197
|
-
|
|
228
|
+
|
|
198
229
|
expressions.push({
|
|
199
230
|
id: exprId,
|
|
200
231
|
code: exprCode,
|
|
201
232
|
location: baseLocation
|
|
202
233
|
})
|
|
203
|
-
|
|
234
|
+
|
|
204
235
|
return {
|
|
205
236
|
id: exprId,
|
|
206
237
|
code: exprCode,
|
|
207
238
|
location: baseLocation
|
|
208
239
|
}
|
|
209
240
|
}
|
|
210
|
-
|
|
241
|
+
|
|
211
242
|
// Regular string value
|
|
212
243
|
return value
|
|
213
244
|
}
|
|
@@ -226,16 +257,16 @@ function parseNode(
|
|
|
226
257
|
if (node.nodeName === '#text') {
|
|
227
258
|
const text = node.value || ''
|
|
228
259
|
const location = getLocation(node, originalHtml)
|
|
229
|
-
|
|
260
|
+
|
|
230
261
|
// Extract expressions from text
|
|
231
262
|
// Phase 7: Pass loop context to detect map expressions and attach context
|
|
232
263
|
const { nodes } = extractExpressionsFromText(text, location, expressions, parentLoopContext)
|
|
233
|
-
|
|
264
|
+
|
|
234
265
|
// If single text node with no expressions, return it
|
|
235
266
|
if (nodes.length === 1 && nodes[0] && nodes[0].type === 'text') {
|
|
236
267
|
return nodes[0]
|
|
237
268
|
}
|
|
238
|
-
|
|
269
|
+
|
|
239
270
|
// Otherwise, we need to handle multiple nodes
|
|
240
271
|
// For Phase 1, we'll flatten to text for now (will be handled in future phases)
|
|
241
272
|
// This is a limitation we accept for Phase 1
|
|
@@ -249,32 +280,32 @@ function parseNode(
|
|
|
249
280
|
location
|
|
250
281
|
}
|
|
251
282
|
}
|
|
252
|
-
|
|
283
|
+
|
|
253
284
|
if (node.nodeName === '#comment') {
|
|
254
285
|
// Skip comments for Phase 1
|
|
255
286
|
return null
|
|
256
287
|
}
|
|
257
|
-
|
|
288
|
+
|
|
258
289
|
if (node.nodeName && node.nodeName !== '#text' && node.nodeName !== '#comment') {
|
|
259
290
|
const location = getLocation(node, originalHtml)
|
|
260
291
|
const tag = node.tagName?.toLowerCase() || node.nodeName
|
|
261
|
-
|
|
292
|
+
|
|
262
293
|
// Parse attributes
|
|
263
294
|
const attributes: AttributeIR[] = []
|
|
264
295
|
if (node.attrs) {
|
|
265
296
|
for (const attr of node.attrs) {
|
|
266
|
-
const attrLocation = node.sourceCodeLocation?.attrs?.[attr.name]
|
|
297
|
+
const attrLocation = node.sourceCodeLocation?.attrs?.[attr.name]
|
|
267
298
|
? {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
299
|
+
line: node.sourceCodeLocation.attrs[attr.name].startLine || location.line,
|
|
300
|
+
column: node.sourceCodeLocation.attrs[attr.name].startCol || location.column
|
|
301
|
+
}
|
|
271
302
|
: location
|
|
272
|
-
|
|
303
|
+
|
|
273
304
|
// Handle :attr="expr" syntax (colon-prefixed reactive attributes)
|
|
274
305
|
let attrName = attr.name
|
|
275
306
|
let attrValue = attr.value || ''
|
|
276
307
|
let isReactive = false
|
|
277
|
-
|
|
308
|
+
|
|
278
309
|
if (attrName.startsWith(':')) {
|
|
279
310
|
// This is a reactive attribute like :class="expr"
|
|
280
311
|
attrName = attrName.slice(1) // Remove the colon
|
|
@@ -283,17 +314,17 @@ function parseNode(
|
|
|
283
314
|
// Treat it as an expression
|
|
284
315
|
const exprId = generateExpressionId()
|
|
285
316
|
const exprCode = attrValue.trim()
|
|
286
|
-
|
|
317
|
+
|
|
287
318
|
const exprIR: ExpressionIR = {
|
|
288
319
|
id: exprId,
|
|
289
320
|
code: exprCode,
|
|
290
321
|
location: attrLocation
|
|
291
322
|
}
|
|
292
323
|
expressions.push(exprIR)
|
|
293
|
-
|
|
324
|
+
|
|
294
325
|
// Phase 7: Attach loop context if expression references loop variables
|
|
295
326
|
const attachedLoopContext = shouldAttachLoopContext(exprIR, parentLoopContext)
|
|
296
|
-
|
|
327
|
+
|
|
297
328
|
attributes.push({
|
|
298
329
|
name: attrName, // Store without colon (e.g., "class" not ":class")
|
|
299
330
|
value: exprIR,
|
|
@@ -303,23 +334,30 @@ function parseNode(
|
|
|
303
334
|
} else {
|
|
304
335
|
// Regular attribute or attr={expr} syntax
|
|
305
336
|
const attrValueResult = parseAttributeValue(attrValue, attrLocation, expressions, normalizedExprs, parentLoopContext)
|
|
306
|
-
|
|
337
|
+
|
|
338
|
+
// Transform event attributes: onclick -> data-zen-click, onchange -> data-zen-change, etc.
|
|
339
|
+
let finalAttrName = attrName
|
|
340
|
+
if (attrName.startsWith('on') && attrName.length > 2) {
|
|
341
|
+
const eventType = attrName.slice(2) // Remove "on" prefix
|
|
342
|
+
finalAttrName = `data-zen-${eventType}`
|
|
343
|
+
}
|
|
344
|
+
|
|
307
345
|
if (typeof attrValueResult === 'string') {
|
|
308
346
|
// Static attribute value
|
|
309
347
|
attributes.push({
|
|
310
|
-
name:
|
|
348
|
+
name: finalAttrName,
|
|
311
349
|
value: attrValueResult,
|
|
312
350
|
location: attrLocation
|
|
313
351
|
})
|
|
314
352
|
} else {
|
|
315
353
|
// Expression attribute value
|
|
316
354
|
const exprIR = attrValueResult
|
|
317
|
-
|
|
355
|
+
|
|
318
356
|
// Phase 7: Attach loop context if expression references loop variables
|
|
319
357
|
const attachedLoopContext = shouldAttachLoopContext(exprIR, parentLoopContext)
|
|
320
|
-
|
|
358
|
+
|
|
321
359
|
attributes.push({
|
|
322
|
-
name:
|
|
360
|
+
name: finalAttrName,
|
|
323
361
|
value: exprIR,
|
|
324
362
|
location: attrLocation,
|
|
325
363
|
loopContext: attachedLoopContext
|
|
@@ -328,7 +366,7 @@ function parseNode(
|
|
|
328
366
|
}
|
|
329
367
|
}
|
|
330
368
|
}
|
|
331
|
-
|
|
369
|
+
|
|
332
370
|
// Parse children
|
|
333
371
|
const children: TemplateNode[] = []
|
|
334
372
|
if (node.childNodes) {
|
|
@@ -338,7 +376,7 @@ function parseNode(
|
|
|
338
376
|
const text = child.value || ''
|
|
339
377
|
const location = getLocation(child, originalHtml)
|
|
340
378
|
const { nodes: textNodes } = extractExpressionsFromText(text, location, expressions, parentLoopContext)
|
|
341
|
-
|
|
379
|
+
|
|
342
380
|
// Add all nodes from text (can be multiple: text + expression + text)
|
|
343
381
|
for (const textNode of textNodes) {
|
|
344
382
|
children.push(textNode)
|
|
@@ -351,11 +389,11 @@ function parseNode(
|
|
|
351
389
|
}
|
|
352
390
|
}
|
|
353
391
|
}
|
|
354
|
-
|
|
392
|
+
|
|
355
393
|
// Phase 7: Check if any child expression is a map expression and extract its loop context
|
|
356
394
|
// This allows nested loops to work correctly
|
|
357
395
|
let elementLoopContext = parentLoopContext
|
|
358
|
-
|
|
396
|
+
|
|
359
397
|
// Check children for map expressions (they create new loop contexts)
|
|
360
398
|
for (const child of children) {
|
|
361
399
|
if (child.type === 'expression' && child.loopContext) {
|
|
@@ -363,7 +401,7 @@ function parseNode(
|
|
|
363
401
|
elementLoopContext = mergeLoopContext(elementLoopContext, child.loopContext)
|
|
364
402
|
}
|
|
365
403
|
}
|
|
366
|
-
|
|
404
|
+
|
|
367
405
|
return {
|
|
368
406
|
type: 'element',
|
|
369
407
|
tag,
|
|
@@ -373,7 +411,7 @@ function parseNode(
|
|
|
373
411
|
loopContext: elementLoopContext // Phase 7: Inherited loop context for child processing
|
|
374
412
|
}
|
|
375
413
|
}
|
|
376
|
-
|
|
414
|
+
|
|
377
415
|
return null
|
|
378
416
|
}
|
|
379
417
|
|
|
@@ -383,20 +421,20 @@ function parseNode(
|
|
|
383
421
|
export function parseTemplate(html: string, filePath: string): TemplateIR {
|
|
384
422
|
// Strip script and style blocks
|
|
385
423
|
let templateHtml = stripBlocks(html)
|
|
386
|
-
|
|
424
|
+
|
|
387
425
|
// Normalize attribute expressions so parse5 can parse them
|
|
388
426
|
const { normalized, expressions: normalizedExprs } = normalizeAttributeExpressions(templateHtml)
|
|
389
427
|
templateHtml = normalized
|
|
390
|
-
|
|
428
|
+
|
|
391
429
|
try {
|
|
392
430
|
// Parse HTML using parseFragment (handles fragments without html/body wrapper)
|
|
393
431
|
const fragment = parseFragment(templateHtml, {
|
|
394
432
|
sourceCodeLocationInfo: true
|
|
395
433
|
})
|
|
396
|
-
|
|
434
|
+
|
|
397
435
|
const expressions: ExpressionIR[] = []
|
|
398
436
|
const nodes: TemplateNode[] = []
|
|
399
|
-
|
|
437
|
+
|
|
400
438
|
// Parse fragment children
|
|
401
439
|
// Phase 7: Start with no loop context (top-level expressions)
|
|
402
440
|
if (fragment.childNodes) {
|
|
@@ -407,7 +445,7 @@ export function parseTemplate(html: string, filePath: string): TemplateIR {
|
|
|
407
445
|
}
|
|
408
446
|
}
|
|
409
447
|
}
|
|
410
|
-
|
|
448
|
+
|
|
411
449
|
return {
|
|
412
450
|
raw: templateHtml,
|
|
413
451
|
nodes,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script Analysis Utilities
|
|
3
|
+
*
|
|
4
|
+
* Extracts state and prop declarations from <script> blocks
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface StateInfo {
|
|
8
|
+
name: string
|
|
9
|
+
value: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extract state declarations: state name = value
|
|
14
|
+
*/
|
|
15
|
+
export function extractStateDeclarations(script: string): Map<string, string> {
|
|
16
|
+
const states = new Map<string, string>()
|
|
17
|
+
const statePattern = /state\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*([^;]+?)(?:\s*;|\s*$)/gm
|
|
18
|
+
let match
|
|
19
|
+
|
|
20
|
+
while ((match = statePattern.exec(script)) !== null) {
|
|
21
|
+
if (match[1] && match[2]) {
|
|
22
|
+
states.set(match[1], match[2].trim())
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return states
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extract prop declarations: export let props: Props;
|
|
31
|
+
*/
|
|
32
|
+
export function extractProps(script: string): string[] {
|
|
33
|
+
const props: string[] = []
|
|
34
|
+
const propPattern = /export\s+let\s+props(?:\s*:\s*([^;]+))?[ \t]*;?/g
|
|
35
|
+
let match
|
|
36
|
+
|
|
37
|
+
while ((match = propPattern.exec(script)) !== null) {
|
|
38
|
+
if (!props.includes('props')) {
|
|
39
|
+
props.push('props')
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return props
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Transform script by removing state and prop declarations
|
|
48
|
+
*/
|
|
49
|
+
export function transformStateDeclarations(script: string): string {
|
|
50
|
+
let transformed = script
|
|
51
|
+
|
|
52
|
+
// Remove state declarations (state count = 0)
|
|
53
|
+
transformed = transformed.replace(/state\s+([a-zA-Z_$][a-zA-Z0-9_$]*)[ \t]*=[ \t]*([^;]+?)(?:[ \t]*;|\s*$)/gm, '')
|
|
54
|
+
|
|
55
|
+
// Remove export let props (legacy)
|
|
56
|
+
transformed = transformed.replace(/export\s+let\s+props(?:\s*:\s*([^;]+))?\s*;?[ \t]*/g, '')
|
|
57
|
+
|
|
58
|
+
// Remove type/interface Props (carefully handling comments)
|
|
59
|
+
// We search for the start of the word 'type' or 'interface' and match until the closing brace
|
|
60
|
+
transformed = transformed.replace(/(?:type|interface)\s+Props\s*=?\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}[ \t]*;?/gs, '')
|
|
61
|
+
|
|
62
|
+
// Remove zenith/runtime imports
|
|
63
|
+
transformed = transformed.replace(/import\s+{[^}]+}\s+from\s+['"]zenith\/runtime['"]\s*;?[ \t]*/g, '')
|
|
64
|
+
|
|
65
|
+
return transformed.trim()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Inject props into a setup script as top-level variables
|
|
70
|
+
*/
|
|
71
|
+
export function injectPropsIntoSetup(script: string, props: Record<string, any>): string {
|
|
72
|
+
const propDeclarations = Object.entries(props)
|
|
73
|
+
.map(([key, value]) => `const ${key} = ${typeof value === 'string' ? `'${value}'` : JSON.stringify(value)};`)
|
|
74
|
+
.join('\n')
|
|
75
|
+
|
|
76
|
+
return `${propDeclarations}\n\n${script}`
|
|
77
|
+
}
|
|
@@ -39,7 +39,7 @@ export function analyzeExpressionDependencies(
|
|
|
39
39
|
declaredStores: string[] = []
|
|
40
40
|
): ExpressionDataDependencies {
|
|
41
41
|
const { id, code } = expr
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
const dependencies: ExpressionDataDependencies = {
|
|
44
44
|
expressionId: id,
|
|
45
45
|
usesLoaderData: false,
|
|
@@ -51,14 +51,14 @@ export function analyzeExpressionDependencies(
|
|
|
51
51
|
storeNames: [],
|
|
52
52
|
stateProperties: []
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
// Simple pattern matching (for Phase 6 - can be enhanced with proper AST parsing later)
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
// Check for loader data references (loaderData.property or direct property access)
|
|
58
58
|
// We assume properties not starting with props/stores/state are loader data
|
|
59
59
|
const loaderPattern = /\b(loaderData\.(\w+(?:\.\w+)*)|(?<!props\.|stores\.|state\.)(\w+)\.(\w+))/g
|
|
60
60
|
let match
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
// Check for explicit loaderData references
|
|
63
63
|
if (/loaderData\./.test(code)) {
|
|
64
64
|
dependencies.usesLoaderData = true
|
|
@@ -71,7 +71,7 @@ export function analyzeExpressionDependencies(
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
// Check for props references
|
|
76
76
|
const propsPattern = /\bprops\.(\w+)(?:\.(\w+))*/g
|
|
77
77
|
if (/props\./.test(code)) {
|
|
@@ -83,7 +83,7 @@ export function analyzeExpressionDependencies(
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
// Check for stores references
|
|
88
88
|
const storesPattern = /\bstores\.(\w+)(?:\.(\w+))*/g
|
|
89
89
|
if (/stores\./.test(code)) {
|
|
@@ -95,12 +95,12 @@ export function analyzeExpressionDependencies(
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
// Check for state references (top-level properties)
|
|
100
100
|
// Simple identifiers that aren't part of props/stores/loaderData paths
|
|
101
101
|
const identifierPattern = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\b/g
|
|
102
102
|
const reserved = ['props', 'stores', 'loaderData', 'state', 'true', 'false', 'null', 'undefined', 'this', 'window']
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
const identifiers = new Set<string>()
|
|
105
105
|
while ((match = identifierPattern.exec(code)) !== null) {
|
|
106
106
|
const ident = match[1]
|
|
@@ -108,13 +108,31 @@ export function analyzeExpressionDependencies(
|
|
|
108
108
|
identifiers.add(ident)
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
-
|
|
112
|
-
// If we have identifiers
|
|
113
|
-
if (identifiers.size > 0
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
|
|
112
|
+
// If we have identifiers, check if they are props or state
|
|
113
|
+
if (identifiers.size > 0) {
|
|
114
|
+
const propIdents: string[] = []
|
|
115
|
+
const stateIdents: string[] = []
|
|
116
|
+
|
|
117
|
+
for (const ident of identifiers) {
|
|
118
|
+
if (declaredProps.includes(ident)) {
|
|
119
|
+
propIdents.push(ident)
|
|
120
|
+
} else {
|
|
121
|
+
stateIdents.push(ident)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (propIdents.length > 0) {
|
|
126
|
+
dependencies.usesProps = true
|
|
127
|
+
dependencies.propNames = [...new Set([...dependencies.propNames, ...propIdents])]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (stateIdents.length > 0) {
|
|
131
|
+
dependencies.usesState = true
|
|
132
|
+
dependencies.stateProperties = Array.from(new Set([...dependencies.stateProperties, ...stateIdents]))
|
|
133
|
+
}
|
|
116
134
|
}
|
|
117
|
-
|
|
135
|
+
|
|
118
136
|
return dependencies
|
|
119
137
|
}
|
|
120
138
|
|
|
@@ -129,7 +147,7 @@ export function validateDataDependencies(
|
|
|
129
147
|
declaredStores: string[] = []
|
|
130
148
|
): void {
|
|
131
149
|
const errors: CompilerError[] = []
|
|
132
|
-
|
|
150
|
+
|
|
133
151
|
// Validate loader data properties
|
|
134
152
|
if (dependencies.usesLoaderData && dependencies.loaderProperties.length > 0) {
|
|
135
153
|
// For Phase 6, we'll allow any loader property (can be enhanced with type checking later)
|
|
@@ -145,7 +163,7 @@ export function validateDataDependencies(
|
|
|
145
163
|
}
|
|
146
164
|
}
|
|
147
165
|
}
|
|
148
|
-
|
|
166
|
+
|
|
149
167
|
// Validate props
|
|
150
168
|
if (dependencies.usesProps && dependencies.propNames.length > 0) {
|
|
151
169
|
for (const propName of dependencies.propNames) {
|
|
@@ -155,7 +173,7 @@ export function validateDataDependencies(
|
|
|
155
173
|
}
|
|
156
174
|
}
|
|
157
175
|
}
|
|
158
|
-
|
|
176
|
+
|
|
159
177
|
// Validate stores
|
|
160
178
|
if (dependencies.usesStores && dependencies.storeNames.length > 0) {
|
|
161
179
|
for (const storeName of dependencies.storeNames) {
|
|
@@ -169,7 +187,7 @@ export function validateDataDependencies(
|
|
|
169
187
|
}
|
|
170
188
|
}
|
|
171
189
|
}
|
|
172
|
-
|
|
190
|
+
|
|
173
191
|
if (errors.length > 0) {
|
|
174
192
|
throw errors[0] // Throw first error (can be enhanced to collect all)
|
|
175
193
|
}
|
|
@@ -189,14 +207,14 @@ export function transformExpressionCode(
|
|
|
189
207
|
declaredProps: string[] = []
|
|
190
208
|
): string {
|
|
191
209
|
let transformed = code
|
|
192
|
-
|
|
210
|
+
|
|
193
211
|
// For Phase 6, we keep the code as-is but ensure expressions
|
|
194
212
|
// receive the right arguments. The actual transformation happens
|
|
195
213
|
// in the expression wrapper function signature.
|
|
196
|
-
|
|
214
|
+
|
|
197
215
|
// However, if the code references properties directly (without loaderData/props/stores prefix),
|
|
198
216
|
// we need to assume they're state properties (backwards compatibility)
|
|
199
|
-
|
|
217
|
+
|
|
200
218
|
return transformed
|
|
201
219
|
}
|
|
202
220
|
|
|
@@ -208,10 +226,10 @@ export function generateExplicitExpressionWrapper(
|
|
|
208
226
|
dependencies: ExpressionDataDependencies
|
|
209
227
|
): string {
|
|
210
228
|
const { id, code } = expr
|
|
211
|
-
|
|
229
|
+
|
|
212
230
|
// Build function signature based on dependencies
|
|
213
231
|
const params: string[] = ['state']
|
|
214
|
-
|
|
232
|
+
|
|
215
233
|
if (dependencies.usesLoaderData) {
|
|
216
234
|
params.push('loaderData')
|
|
217
235
|
}
|
|
@@ -221,12 +239,12 @@ export function generateExplicitExpressionWrapper(
|
|
|
221
239
|
if (dependencies.usesStores) {
|
|
222
240
|
params.push('stores')
|
|
223
241
|
}
|
|
224
|
-
|
|
242
|
+
|
|
225
243
|
const paramList = params.join(', ')
|
|
226
|
-
|
|
244
|
+
|
|
227
245
|
// Build evaluation context
|
|
228
246
|
const contextParts: string[] = []
|
|
229
|
-
|
|
247
|
+
|
|
230
248
|
if (dependencies.usesLoaderData) {
|
|
231
249
|
contextParts.push('loaderData')
|
|
232
250
|
}
|
|
@@ -239,14 +257,14 @@ export function generateExplicitExpressionWrapper(
|
|
|
239
257
|
if (dependencies.usesState) {
|
|
240
258
|
contextParts.push('state')
|
|
241
259
|
}
|
|
242
|
-
|
|
260
|
+
|
|
243
261
|
// Create merged context for 'with' statement
|
|
244
262
|
const contextCode = contextParts.length > 0
|
|
245
263
|
? `const __ctx = Object.assign({}, ${contextParts.join(', ')});\n with (__ctx) {`
|
|
246
264
|
: 'with (state) {'
|
|
247
|
-
|
|
265
|
+
|
|
248
266
|
const escapedCode = code.replace(/`/g, '\\`').replace(/\$/g, '\\$')
|
|
249
|
-
|
|
267
|
+
|
|
250
268
|
return `
|
|
251
269
|
// Expression: ${escapedCode}
|
|
252
270
|
// Dependencies: ${JSON.stringify({
|
|
@@ -280,12 +298,12 @@ export function analyzeAllExpressions(
|
|
|
280
298
|
const dependencies = expressions.map(expr =>
|
|
281
299
|
analyzeExpressionDependencies(expr, declaredLoaderProps, declaredProps, declaredStores)
|
|
282
300
|
)
|
|
283
|
-
|
|
301
|
+
|
|
284
302
|
// Validate all dependencies
|
|
285
303
|
for (const dep of dependencies) {
|
|
286
304
|
validateDataDependencies(dep, filePath, declaredLoaderProps, declaredProps, declaredStores)
|
|
287
305
|
}
|
|
288
|
-
|
|
306
|
+
|
|
289
307
|
return dependencies
|
|
290
308
|
}
|
|
291
309
|
|