@zenithbuild/core 0.4.6 → 0.5.0-beta.2.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.
Files changed (109) hide show
  1. package/CORE_CONTRACT.md +143 -0
  2. package/README.md +11 -31
  3. package/bin/zenith.js +68 -0
  4. package/package.json +40 -52
  5. package/src/config.js +134 -0
  6. package/src/core-template.js +30 -0
  7. package/src/errors.js +54 -0
  8. package/src/guards.js +61 -0
  9. package/src/hash.js +52 -0
  10. package/src/index.js +26 -0
  11. package/src/ir/index.js +1 -0
  12. package/src/order.js +69 -0
  13. package/src/path.js +131 -0
  14. package/src/schema.js +28 -0
  15. package/src/version.js +67 -0
  16. package/bin/zen-build.ts +0 -2
  17. package/bin/zen-dev.ts +0 -2
  18. package/bin/zen-preview.ts +0 -2
  19. package/bin/zenith.ts +0 -2
  20. package/cli/commands/add.ts +0 -37
  21. package/cli/commands/build.ts +0 -37
  22. package/cli/commands/create.ts +0 -702
  23. package/cli/commands/dev.ts +0 -335
  24. package/cli/commands/index.ts +0 -112
  25. package/cli/commands/preview.ts +0 -62
  26. package/cli/commands/remove.ts +0 -33
  27. package/cli/index.ts +0 -10
  28. package/cli/main.ts +0 -101
  29. package/cli/utils/branding.ts +0 -178
  30. package/cli/utils/content.ts +0 -112
  31. package/cli/utils/logger.ts +0 -46
  32. package/cli/utils/plugin-manager.ts +0 -114
  33. package/cli/utils/project.ts +0 -77
  34. package/compiler/README.md +0 -380
  35. package/compiler/build-analyzer.ts +0 -122
  36. package/compiler/css/index.ts +0 -317
  37. package/compiler/discovery/componentDiscovery.ts +0 -174
  38. package/compiler/discovery/layouts.ts +0 -61
  39. package/compiler/errors/compilerError.ts +0 -56
  40. package/compiler/finalize/finalizeOutput.ts +0 -192
  41. package/compiler/finalize/generateFinalBundle.ts +0 -82
  42. package/compiler/index.ts +0 -81
  43. package/compiler/ir/types.ts +0 -150
  44. package/compiler/output/types.ts +0 -34
  45. package/compiler/parse/detectMapExpressions.ts +0 -102
  46. package/compiler/parse/parseScript.ts +0 -46
  47. package/compiler/parse/parseTemplate.ts +0 -591
  48. package/compiler/parse/parseZenFile.ts +0 -66
  49. package/compiler/parse/scriptAnalysis.ts +0 -83
  50. package/compiler/parse/trackLoopContext.ts +0 -82
  51. package/compiler/runtime/dataExposure.ts +0 -317
  52. package/compiler/runtime/generateDOM.ts +0 -246
  53. package/compiler/runtime/generateHydrationBundle.ts +0 -407
  54. package/compiler/runtime/hydration.ts +0 -309
  55. package/compiler/runtime/navigation.ts +0 -432
  56. package/compiler/runtime/thinRuntime.ts +0 -160
  57. package/compiler/runtime/transformIR.ts +0 -343
  58. package/compiler/runtime/wrapExpression.ts +0 -95
  59. package/compiler/runtime/wrapExpressionWithLoop.ts +0 -83
  60. package/compiler/spa-build.ts +0 -917
  61. package/compiler/ssg-build.ts +0 -422
  62. package/compiler/test/validate-test.ts +0 -104
  63. package/compiler/transform/classifyExpression.ts +0 -444
  64. package/compiler/transform/componentResolver.ts +0 -289
  65. package/compiler/transform/expressionTransformer.ts +0 -385
  66. package/compiler/transform/fragmentLowering.ts +0 -634
  67. package/compiler/transform/generateBindings.ts +0 -47
  68. package/compiler/transform/generateHTML.ts +0 -28
  69. package/compiler/transform/layoutProcessor.ts +0 -132
  70. package/compiler/transform/slotResolver.ts +0 -292
  71. package/compiler/transform/transformNode.ts +0 -126
  72. package/compiler/transform/transformTemplate.ts +0 -38
  73. package/compiler/validate/invariants.ts +0 -292
  74. package/compiler/validate/validateExpressions.ts +0 -168
  75. package/core/config/index.ts +0 -16
  76. package/core/config/loader.ts +0 -69
  77. package/core/config/types.ts +0 -89
  78. package/core/index.ts +0 -135
  79. package/core/lifecycle/index.ts +0 -49
  80. package/core/lifecycle/zen-mount.ts +0 -182
  81. package/core/lifecycle/zen-unmount.ts +0 -88
  82. package/core/plugins/index.ts +0 -7
  83. package/core/plugins/registry.ts +0 -81
  84. package/core/reactivity/index.ts +0 -54
  85. package/core/reactivity/tracking.ts +0 -167
  86. package/core/reactivity/zen-batch.ts +0 -57
  87. package/core/reactivity/zen-effect.ts +0 -139
  88. package/core/reactivity/zen-memo.ts +0 -146
  89. package/core/reactivity/zen-ref.ts +0 -52
  90. package/core/reactivity/zen-signal.ts +0 -121
  91. package/core/reactivity/zen-state.ts +0 -180
  92. package/core/reactivity/zen-untrack.ts +0 -44
  93. package/dist/cli.js +0 -11653
  94. package/dist/zen-build.js +0 -15388
  95. package/dist/zen-dev.js +0 -15388
  96. package/dist/zen-preview.js +0 -15388
  97. package/dist/zenith.js +0 -15388
  98. package/router/index.ts +0 -76
  99. package/router/manifest.ts +0 -314
  100. package/router/navigation/ZenLink.zen +0 -231
  101. package/router/navigation/index.ts +0 -78
  102. package/router/navigation/zen-link.ts +0 -584
  103. package/router/runtime.ts +0 -458
  104. package/router/types.ts +0 -168
  105. package/runtime/build.ts +0 -17
  106. package/runtime/bundle-generator.ts +0 -800
  107. package/runtime/client-runtime.ts +0 -549
  108. package/runtime/serve.ts +0 -93
  109. package/tsconfig.json +0 -28
@@ -1,591 +0,0 @@
1
- /**
2
- * Template Parser
3
- *
4
- * Parses HTML template and extracts expressions
5
- * Phase 1: Only extracts, does not execute
6
- */
7
-
8
- import { parse, parseFragment } from 'parse5'
9
- import type { TemplateIR, TemplateNode, ElementNode, TextNode, ExpressionNode, AttributeIR, ExpressionIR, SourceLocation, LoopContext } from '../ir/types'
10
- import { CompilerError, InvariantError } from '../errors/compilerError'
11
- import { parseScript } from './parseScript'
12
- import { detectMapExpression, extractLoopVariables, referencesLoopVariable } from './detectMapExpressions'
13
- import { shouldAttachLoopContext, mergeLoopContext, extractLoopContextFromExpression } from './trackLoopContext'
14
- import { INVARIANT } from '../validate/invariants'
15
- import { lowerFragments } from '../transform/fragmentLowering'
16
-
17
- // Generate stable IDs for expressions
18
- let expressionIdCounter = 0
19
- function generateExpressionId(): string {
20
- return `expr_${expressionIdCounter++}`
21
- }
22
-
23
- /**
24
- * Strip script and style blocks from HTML before parsing
25
- */
26
- function stripBlocks(html: string): string {
27
- // Remove script blocks
28
- let stripped = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
29
- // Remove style blocks
30
- stripped = stripped.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
31
- return stripped
32
- }
33
-
34
- /**
35
- * Find the end of a balanced brace expression, handling strings and template literals
36
- * Returns the index after the closing brace, or -1 if unbalanced
37
- */
38
- function findBalancedBraceEnd(html: string, startIndex: number): number {
39
- let braceCount = 1
40
- let i = startIndex + 1
41
- let inString = false
42
- let stringChar = ''
43
- let inTemplate = false
44
-
45
- while (i < html.length && braceCount > 0) {
46
- const char = html[i]
47
- const prevChar = i > 0 ? html[i - 1] : ''
48
-
49
- // Handle escape sequences
50
- if (prevChar === '\\') {
51
- i++
52
- continue
53
- }
54
-
55
- // Handle string literals (not inside template)
56
- if (!inString && !inTemplate && (char === '"' || char === "'")) {
57
- inString = true
58
- stringChar = char
59
- i++
60
- continue
61
- }
62
-
63
- if (inString && char === stringChar) {
64
- inString = false
65
- stringChar = ''
66
- i++
67
- continue
68
- }
69
-
70
- // Handle template literals
71
- if (!inString && !inTemplate && char === '`') {
72
- inTemplate = true
73
- i++
74
- continue
75
- }
76
-
77
- if (inTemplate && char === '`') {
78
- inTemplate = false
79
- i++
80
- continue
81
- }
82
-
83
- // Handle ${} inside template literals - need to track nested braces
84
- if (inTemplate && char === '$' && html[i + 1] === '{') {
85
- // Skip the ${ and count as opening brace
86
- i += 2
87
- let templateBraceCount = 1
88
- while (i < html.length && templateBraceCount > 0) {
89
- if (html[i] === '{') templateBraceCount++
90
- else if (html[i] === '}') templateBraceCount--
91
- i++
92
- }
93
- continue
94
- }
95
-
96
- // Count braces only when not in strings or templates
97
- if (!inString && !inTemplate) {
98
- if (char === '{') braceCount++
99
- else if (char === '}') braceCount--
100
- }
101
-
102
- i++
103
- }
104
-
105
- return braceCount === 0 ? i : -1
106
- }
107
-
108
- /**
109
- * Normalize expressions before parsing
110
- * Replaces both attr={expr} and {textExpr} with placeholders so parse5 can parse the HTML correctly
111
- * without being confused by tags or braces inside expressions.
112
- *
113
- * Uses balanced brace parsing to correctly handle:
114
- * - String literals with braces inside
115
- * - Template literals with ${} interpolations
116
- * - Arrow functions with object returns
117
- * - Multi-line JSX expressions
118
- */
119
- function normalizeAllExpressions(html: string): { normalized: string; expressions: Map<string, string> } {
120
- const exprMap = new Map<string, string>()
121
- let exprCounter = 0
122
- let result = ''
123
- let lastPos = 0
124
-
125
- for (let i = 0; i < html.length; i++) {
126
- // Look for { and check if it's an expression
127
- // We handle both text expressions and attribute expressions: attr={...}
128
- if (html[i] === '{') {
129
- const j = findBalancedBraceEnd(html, i)
130
-
131
- if (j !== -1 && j > i + 1) {
132
- const expr = html.substring(i + 1, j - 1).trim()
133
-
134
- // Skip empty expressions
135
- if (expr.length === 0) {
136
- i++
137
- continue
138
- }
139
-
140
- const placeholder = `__ZEN_EXPR_${exprCounter++}`
141
- exprMap.set(placeholder, expr)
142
-
143
- result += html.substring(lastPos, i)
144
- result += placeholder
145
- lastPos = j
146
- i = j - 1
147
- }
148
- }
149
- }
150
- result += html.substring(lastPos)
151
-
152
- return { normalized: result, expressions: exprMap }
153
- }
154
-
155
-
156
- /**
157
- * Calculate source location from parse5 node
158
- */
159
- function getLocation(node: any, originalHtml: string): SourceLocation {
160
- // parse5 provides sourceCodeLocation if available
161
- if (node.sourceCodeLocation) {
162
- return {
163
- line: node.sourceCodeLocation.startLine || 1,
164
- column: node.sourceCodeLocation.startCol || 1
165
- }
166
- }
167
- // Fallback if location info not available
168
- return { line: 1, column: 1 }
169
- }
170
-
171
- /**
172
- * Extract expressions from text content
173
- * Returns array of { expression, location } and the text with expressions replaced
174
- * Phase 7: Supports loop context for expressions inside map iterations
175
- */
176
- function extractExpressionsFromText(
177
- text: string,
178
- baseLocation: SourceLocation,
179
- expressions: ExpressionIR[],
180
- normalizedExprs: Map<string, string>,
181
- loopContext?: LoopContext
182
- ): { processedText: string; nodes: (TextNode | ExpressionNode)[] } {
183
- const nodes: (TextNode | ExpressionNode)[] = []
184
- let processedText = ''
185
- let currentIndex = 0
186
-
187
- // Match __ZEN_EXPR_N placeholders
188
- const expressionRegex = /__ZEN_EXPR_\d+/g
189
- let match
190
-
191
- while ((match = expressionRegex.exec(text)) !== null) {
192
- const beforeExpr = text.substring(currentIndex, match.index)
193
- if (beforeExpr) {
194
- nodes.push({
195
- type: 'text',
196
- value: beforeExpr,
197
- location: {
198
- line: baseLocation.line,
199
- column: baseLocation.column + currentIndex
200
- }
201
- })
202
- processedText += beforeExpr
203
- }
204
-
205
- // Resolve placeholder to original expression code
206
- const placeholder = match[0]
207
- const exprCode = (normalizedExprs.get(placeholder) || '').trim()
208
- const exprId = generateExpressionId()
209
- const exprLocation: SourceLocation = {
210
- line: baseLocation.line,
211
- column: baseLocation.column + match.index
212
- }
213
-
214
- const exprIR: ExpressionIR = {
215
- id: exprId,
216
- code: exprCode,
217
- location: exprLocation
218
- }
219
- expressions.push(exprIR)
220
-
221
- // Phase 7: Loop context detection and attachment
222
- const mapLoopContext = extractLoopContextFromExpression(exprIR)
223
- const activeLoopContext = mergeLoopContext(loopContext, mapLoopContext)
224
- const attachedLoopContext = shouldAttachLoopContext(exprIR, activeLoopContext)
225
-
226
- nodes.push({
227
- type: 'expression',
228
- expression: exprId,
229
- location: exprLocation,
230
- loopContext: attachedLoopContext
231
- })
232
-
233
- processedText += `{${exprCode}}`
234
- currentIndex = match.index + match[0].length
235
- }
236
-
237
- // Add remaining text
238
- const remaining = text.substring(currentIndex)
239
- if (remaining) {
240
- nodes.push({
241
- type: 'text',
242
- value: remaining,
243
- location: {
244
- line: baseLocation.line,
245
- column: baseLocation.column + currentIndex
246
- }
247
- })
248
- processedText += remaining
249
- }
250
-
251
- // If no expressions found, return single text node
252
- if (nodes.length === 0) {
253
- nodes.push({
254
- type: 'text',
255
- value: text,
256
- location: baseLocation
257
- })
258
- processedText = text
259
- }
260
-
261
- return { processedText, nodes }
262
- }
263
-
264
- /**
265
- * Parse attribute value - may contain expressions
266
- * Phase 7: Supports loop context for expressions inside map iterations
267
- */
268
- function parseAttributeValue(
269
- value: string,
270
- baseLocation: SourceLocation,
271
- expressions: ExpressionIR[],
272
- normalizedExprs: Map<string, string>,
273
- loopContext?: LoopContext // Phase 7: Loop context from parent map expressions
274
- ): string | ExpressionIR {
275
- // Check if this is a normalized expression placeholder
276
- if (value.startsWith('__ZEN_EXPR_')) {
277
- const exprCode = normalizedExprs.get(value)
278
- if (!exprCode) {
279
- throw new Error(`Normalized expression placeholder not found: ${value}`)
280
- }
281
-
282
- const exprId = generateExpressionId()
283
-
284
- expressions.push({
285
- id: exprId,
286
- code: exprCode,
287
- location: baseLocation
288
- })
289
-
290
- return {
291
- id: exprId,
292
- code: exprCode,
293
- location: baseLocation
294
- }
295
- }
296
-
297
- // Check if attribute value is an expression { ... } (shouldn't happen after normalization)
298
- const exprMatch = value.match(/^\{([^}]+)\}$/)
299
- if (exprMatch && exprMatch[1]) {
300
- const exprCode = exprMatch[1].trim()
301
- const exprId = generateExpressionId()
302
-
303
- expressions.push({
304
- id: exprId,
305
- code: exprCode,
306
- location: baseLocation
307
- })
308
-
309
- return {
310
- id: exprId,
311
- code: exprCode,
312
- location: baseLocation
313
- }
314
- }
315
-
316
- // Regular string value
317
- return value
318
- }
319
-
320
- /**
321
- * Convert parse5 node to TemplateNode
322
- * Phase 7: Supports loop context propagation for map expressions
323
- */
324
- function parseNode(
325
- node: any,
326
- originalHtml: string,
327
- expressions: ExpressionIR[],
328
- normalizedExprs: Map<string, string>,
329
- parentLoopContext?: LoopContext // Phase 7: Loop context from parent map expressions
330
- ): TemplateNode | null {
331
- if (node.nodeName === '#text') {
332
- const text = node.value || ''
333
- const location = getLocation(node, originalHtml)
334
-
335
- // Extract expressions from text
336
- // Phase 7: Pass loop context to detect map expressions and attach context
337
- const { nodes } = extractExpressionsFromText(node.value, location, expressions, normalizedExprs, parentLoopContext)
338
-
339
- // If single text node with no expressions, return it
340
- if (nodes.length === 1 && nodes[0] && nodes[0].type === 'text') {
341
- return nodes[0]
342
- }
343
-
344
- // Otherwise, we need to handle multiple nodes
345
- // For Phase 1, we'll flatten to text for now (will be handled in future phases)
346
- // This is a limitation we accept for Phase 1
347
- const firstNode = nodes[0]
348
- if (firstNode) {
349
- return firstNode
350
- }
351
- return {
352
- type: 'text',
353
- value: text,
354
- location
355
- }
356
- }
357
-
358
- if (node.nodeName === '#comment') {
359
- // Skip comments for Phase 1
360
- return null
361
- }
362
-
363
- if (node.nodeName && node.nodeName !== '#text' && node.nodeName !== '#comment') {
364
- const location = getLocation(node, originalHtml)
365
- const tag = node.tagName?.toLowerCase() || node.nodeName
366
-
367
- // Extract original tag name from source HTML to preserve casing (parse5 lowercases everything)
368
- let originalTag = node.tagName || node.nodeName
369
- if (node.sourceCodeLocation && node.sourceCodeLocation.startOffset !== undefined) {
370
- const startOffset = node.sourceCodeLocation.startOffset
371
- // Find the tag name in original HTML (after '<')
372
- const tagMatch = originalHtml.slice(startOffset).match(/^<([a-zA-Z][a-zA-Z0-9._-]*)/)
373
- if (tagMatch && tagMatch[1]) {
374
- originalTag = tagMatch[1]
375
- }
376
- }
377
-
378
- // INV005: <template> tags are forbidden — use compound components instead
379
- if (tag === 'template') {
380
- throw new InvariantError(
381
- INVARIANT.TEMPLATE_TAG,
382
- `<template> tags are forbidden in Zenith. Use compound components (e.g., Card.Header) for named slots.`,
383
- 'Named slots use compound component pattern (Card.Header), not <template> tags.',
384
- 'unknown', // filePath passed to parseTemplate
385
- location.line,
386
- location.column
387
- )
388
- }
389
-
390
- // Parse attributes
391
- const attributes: AttributeIR[] = []
392
- if (node.attrs) {
393
- for (const attr of node.attrs) {
394
- const attrLocation = node.sourceCodeLocation?.attrs?.[attr.name]
395
- ? {
396
- line: node.sourceCodeLocation.attrs[attr.name].startLine || location.line,
397
- column: node.sourceCodeLocation.attrs[attr.name].startCol || location.column
398
- }
399
- : location
400
-
401
- // INV006: slot="" attributes are forbidden — use compound components instead
402
- if (attr.name === 'slot') {
403
- throw new InvariantError(
404
- INVARIANT.SLOT_ATTRIBUTE,
405
- `slot="${attr.value || ''}" attribute is forbidden. Use compound components (e.g., Card.Header) for named slots.`,
406
- 'Named slots use compound component pattern (Card.Header), not slot="" attributes.',
407
- 'unknown',
408
- attrLocation.line,
409
- attrLocation.column
410
- )
411
- }
412
-
413
- // Handle :attr="expr" syntax (colon-prefixed reactive attributes)
414
- let attrName = attr.name
415
- let attrValue = attr.value || ''
416
- let isReactive = false
417
-
418
- if (attrName.startsWith(':')) {
419
- // This is a reactive attribute like :class="expr"
420
- attrName = attrName.slice(1) // Remove the colon
421
- isReactive = true
422
- // The value is already a string expression (not in braces)
423
- // Treat it as an expression
424
- const exprId = generateExpressionId()
425
- const exprCode = attrValue.trim()
426
-
427
- const exprIR: ExpressionIR = {
428
- id: exprId,
429
- code: exprCode,
430
- location: attrLocation
431
- }
432
- expressions.push(exprIR)
433
-
434
- // Phase 7: Attach loop context if expression references loop variables
435
- const attachedLoopContext = shouldAttachLoopContext(exprIR, parentLoopContext)
436
-
437
- attributes.push({
438
- name: attrName, // Store without colon (e.g., "class" not ":class")
439
- value: exprIR,
440
- location: attrLocation,
441
- loopContext: attachedLoopContext
442
- })
443
- } else {
444
- // Regular attribute or attr={expr} syntax
445
- const attrValueResult = parseAttributeValue(attrValue, attrLocation, expressions, normalizedExprs, parentLoopContext)
446
-
447
- // Transform event attributes: onclick -> data-zen-click, onchange -> data-zen-change, etc.
448
- let finalAttrName = attrName
449
- if (attrName.startsWith('on') && attrName.length > 2) {
450
- const eventType = attrName.slice(2) // Remove "on" prefix
451
- finalAttrName = `data-zen-${eventType}`
452
- }
453
-
454
- if (typeof attrValueResult === 'string') {
455
- // Static attribute value
456
- attributes.push({
457
- name: finalAttrName,
458
- value: attrValueResult,
459
- location: attrLocation
460
- })
461
- } else {
462
- // Expression attribute value
463
- const exprIR = attrValueResult
464
-
465
- // Phase 7: Attach loop context if expression references loop variables
466
- const attachedLoopContext = shouldAttachLoopContext(exprIR, parentLoopContext)
467
-
468
- attributes.push({
469
- name: finalAttrName,
470
- value: exprIR,
471
- location: attrLocation,
472
- loopContext: attachedLoopContext
473
- })
474
- }
475
- }
476
- }
477
- }
478
-
479
- // Parse children
480
- const children: TemplateNode[] = []
481
- if (node.childNodes) {
482
- for (const child of node.childNodes) {
483
- if (child.nodeName === '#text') {
484
- // Handle text nodes that may contain expressions
485
- const text = child.value || ''
486
- const location = getLocation(child, originalHtml)
487
- const { nodes: textNodes } = extractExpressionsFromText(text, location, expressions, normalizedExprs, parentLoopContext)
488
-
489
- // Add all nodes from text (can be multiple: text + expression + text)
490
- for (const textNode of textNodes) {
491
- children.push(textNode)
492
- }
493
- } else {
494
- const childNode = parseNode(child, originalHtml, expressions, normalizedExprs, parentLoopContext)
495
- if (childNode) {
496
- children.push(childNode)
497
- }
498
- }
499
- }
500
- }
501
-
502
- // Phase 7: Check if any child expression is a map expression and extract its loop context
503
- // This allows nested loops to work correctly
504
- let elementLoopContext = parentLoopContext
505
-
506
- // Check children for map expressions (they create new loop contexts)
507
- for (const child of children) {
508
- if (child.type === 'expression' && child.loopContext) {
509
- // If we find a map expression child, merge its context
510
- elementLoopContext = mergeLoopContext(elementLoopContext, child.loopContext)
511
- }
512
- }
513
-
514
- // Check if this is a custom component (starts with uppercase)
515
- const isComponent = originalTag.length > 0 && originalTag[0] === originalTag[0].toUpperCase()
516
-
517
- if (isComponent) {
518
- // This is a component node
519
- return {
520
- type: 'component',
521
- name: originalTag,
522
- attributes,
523
- children,
524
- location,
525
- loopContext: elementLoopContext
526
- }
527
- } else {
528
- // This is a regular HTML element
529
- return {
530
- type: 'element',
531
- tag,
532
- attributes,
533
- children,
534
- location,
535
- loopContext: elementLoopContext
536
- }
537
- }
538
- }
539
-
540
- return null
541
- }
542
-
543
- /**
544
- * Parse template from HTML string
545
- */
546
- export function parseTemplate(html: string, filePath: string): TemplateIR {
547
- // Strip script and style blocks
548
- let templateHtml = stripBlocks(html)
549
-
550
- // Normalize all expressions so parse5 can parse them safely
551
- const { normalized, expressions: normalizedExprs } = normalizeAllExpressions(templateHtml)
552
- templateHtml = normalized
553
-
554
- try {
555
- // Parse HTML using parseFragment
556
- const fragment = parseFragment(templateHtml, {
557
- sourceCodeLocationInfo: true
558
- })
559
-
560
- const expressions: ExpressionIR[] = []
561
- const nodes: TemplateNode[] = []
562
-
563
- // Parse fragment children
564
- if (fragment.childNodes) {
565
- for (const node of fragment.childNodes) {
566
- const parsed = parseNode(node, templateHtml, expressions, normalizedExprs, undefined)
567
- if (parsed) {
568
- nodes.push(parsed)
569
- }
570
- }
571
- }
572
-
573
- // Phase 8: Lower JSX expressions to structural fragments
574
- // This transforms expressions like {cond ? <A /> : <B />} into ConditionalFragmentNode
575
- const loweredNodes = lowerFragments(nodes, filePath, expressions)
576
-
577
- return {
578
- raw: templateHtml,
579
- nodes: loweredNodes,
580
- expressions
581
- }
582
- } catch (error: any) {
583
- throw new CompilerError(
584
- `Template parsing failed: ${error.message}`,
585
- filePath,
586
- 1,
587
- 1
588
- )
589
- }
590
- }
591
-
@@ -1,66 +0,0 @@
1
- /**
2
- * Zenith File Parser
3
- *
4
- * Main entry point for parsing .zen files
5
- * Phase 1: Parse & Extract only
6
- */
7
-
8
- import { readFileSync } from 'fs'
9
- import type { ZenIR, StyleIR } from '../ir/types'
10
- import { parseTemplate } from './parseTemplate'
11
- import { parseScript } from './parseScript'
12
- import { CompilerError } from '../errors/compilerError'
13
-
14
- /**
15
- * Extract style blocks from HTML
16
- */
17
- function parseStyles(html: string): StyleIR[] {
18
- const styles: StyleIR[] = []
19
- const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi
20
- let match
21
-
22
- while ((match = styleRegex.exec(html)) !== null) {
23
- if (match[1]) {
24
- styles.push({
25
- raw: match[1].trim()
26
- })
27
- }
28
- }
29
-
30
- return styles
31
- }
32
-
33
- /**
34
- * Parse a .zen file into IR
35
- */
36
- export function parseZenFile(filePath: string): ZenIR {
37
- let source: string
38
-
39
- try {
40
- source = readFileSync(filePath, 'utf-8')
41
- } catch (error: any) {
42
- throw new CompilerError(
43
- `Failed to read file: ${error.message}`,
44
- filePath,
45
- 1,
46
- 1
47
- )
48
- }
49
-
50
- // Parse template
51
- const template = parseTemplate(source, filePath)
52
-
53
- // Parse script
54
- const script = parseScript(source)
55
-
56
- // Parse styles
57
- const styles = parseStyles(source)
58
-
59
- return {
60
- filePath,
61
- template,
62
- script,
63
- styles
64
- }
65
- }
66
-