@zenithbuild/core 0.6.2 → 1.0.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 (87) hide show
  1. package/README.md +20 -19
  2. package/cli/commands/add.ts +2 -2
  3. package/cli/commands/build.ts +2 -3
  4. package/cli/commands/dev.ts +182 -103
  5. package/cli/commands/index.ts +1 -1
  6. package/cli/commands/preview.ts +1 -1
  7. package/cli/commands/remove.ts +2 -2
  8. package/cli/index.ts +1 -1
  9. package/cli/main.ts +1 -1
  10. package/cli/utils/logger.ts +1 -1
  11. package/cli/utils/plugin-manager.ts +1 -1
  12. package/cli/utils/project.ts +4 -4
  13. package/core/components/ErrorPage.zen +218 -0
  14. package/core/components/index.ts +15 -0
  15. package/core/config.ts +1 -0
  16. package/core/index.ts +29 -0
  17. package/dist/compiler-native-frej59m4.node +0 -0
  18. package/dist/core/compiler-native-frej59m4.node +0 -0
  19. package/dist/core/index.js +6293 -0
  20. package/dist/runtime/lifecycle/index.js +1 -0
  21. package/dist/runtime/reactivity/index.js +1 -0
  22. package/dist/zen-build.js +7465 -19128
  23. package/dist/zen-dev.js +7465 -19128
  24. package/dist/zen-preview.js +7465 -19128
  25. package/dist/zenith.js +7465 -19128
  26. package/package.json +21 -22
  27. package/cli/utils/content.ts +0 -112
  28. package/compiler/README.md +0 -380
  29. package/compiler/build-analyzer.ts +0 -122
  30. package/compiler/css/index.ts +0 -317
  31. package/compiler/discovery/componentDiscovery.ts +0 -178
  32. package/compiler/discovery/layouts.ts +0 -70
  33. package/compiler/errors/compilerError.ts +0 -56
  34. package/compiler/finalize/finalizeOutput.ts +0 -192
  35. package/compiler/finalize/generateFinalBundle.ts +0 -82
  36. package/compiler/index.ts +0 -83
  37. package/compiler/ir/types.ts +0 -174
  38. package/compiler/output/types.ts +0 -34
  39. package/compiler/parse/detectMapExpressions.ts +0 -102
  40. package/compiler/parse/importTypes.ts +0 -78
  41. package/compiler/parse/parseImports.ts +0 -309
  42. package/compiler/parse/parseScript.ts +0 -46
  43. package/compiler/parse/parseTemplate.ts +0 -599
  44. package/compiler/parse/parseZenFile.ts +0 -66
  45. package/compiler/parse/scriptAnalysis.ts +0 -91
  46. package/compiler/parse/trackLoopContext.ts +0 -82
  47. package/compiler/runtime/dataExposure.ts +0 -317
  48. package/compiler/runtime/generateDOM.ts +0 -246
  49. package/compiler/runtime/generateHydrationBundle.ts +0 -407
  50. package/compiler/runtime/hydration.ts +0 -309
  51. package/compiler/runtime/navigation.ts +0 -432
  52. package/compiler/runtime/thinRuntime.ts +0 -160
  53. package/compiler/runtime/transformIR.ts +0 -370
  54. package/compiler/runtime/wrapExpression.ts +0 -95
  55. package/compiler/runtime/wrapExpressionWithLoop.ts +0 -83
  56. package/compiler/spa-build.ts +0 -917
  57. package/compiler/ssg-build.ts +0 -422
  58. package/compiler/test/validate-test.ts +0 -104
  59. package/compiler/transform/classifyExpression.ts +0 -444
  60. package/compiler/transform/componentResolver.ts +0 -312
  61. package/compiler/transform/componentScriptTransformer.ts +0 -303
  62. package/compiler/transform/expressionTransformer.ts +0 -385
  63. package/compiler/transform/fragmentLowering.ts +0 -634
  64. package/compiler/transform/generateBindings.ts +0 -47
  65. package/compiler/transform/generateHTML.ts +0 -28
  66. package/compiler/transform/layoutProcessor.ts +0 -132
  67. package/compiler/transform/slotResolver.ts +0 -292
  68. package/compiler/transform/transformNode.ts +0 -126
  69. package/compiler/transform/transformTemplate.ts +0 -38
  70. package/compiler/validate/invariants.ts +0 -292
  71. package/compiler/validate/validateExpressions.ts +0 -168
  72. package/core/config/index.ts +0 -16
  73. package/core/config/loader.ts +0 -69
  74. package/core/config/types.ts +0 -89
  75. package/core/plugins/index.ts +0 -7
  76. package/core/plugins/registry.ts +0 -81
  77. package/dist/cli.js +0 -11665
  78. package/router/manifest.ts +0 -314
  79. package/router/navigation/ZenLink.zen +0 -231
  80. package/router/navigation/index.ts +0 -78
  81. package/router/navigation/zen-link.ts +0 -584
  82. package/router/runtime.ts +0 -458
  83. package/router/types.ts +0 -168
  84. package/runtime/build.ts +0 -17
  85. package/runtime/bundle-generator.ts +0 -1247
  86. package/runtime/client-runtime.ts +0 -549
  87. package/runtime/serve.ts +0 -93
@@ -1,132 +0,0 @@
1
- import type { LayoutMetadata } from '../discovery/layouts'
2
-
3
- /**
4
- * Process a page by inlining a layout
5
- */
6
- export function processLayout(
7
- source: string,
8
- layout: LayoutMetadata,
9
- props: Record<string, any> = {}
10
- ): string {
11
- // 1. Extract scripts and styles from the page source
12
- const pageScripts: string[] = []
13
- const pageStyles: string[] = []
14
- let isTypeScript = false
15
-
16
- // Extract script blocks
17
- const scriptRegex = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi
18
- let scriptMatch
19
- while ((scriptMatch = scriptRegex.exec(source)) !== null) {
20
- const attrString = scriptMatch[1] || ''
21
- const content = scriptMatch[2] || ''
22
- if (attrString.includes('lang="ts"') || attrString.includes('setup="ts"')) {
23
- isTypeScript = true
24
- }
25
- if (content) pageScripts.push(content.trim())
26
- }
27
-
28
- // Extract style blocks
29
- const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi
30
- let styleMatch
31
- while ((styleMatch = styleRegex.exec(source)) !== null) {
32
- if (styleMatch[1]) pageStyles.push(styleMatch[1].trim())
33
- }
34
-
35
- // 2. Extract content from page source and parse props
36
- const layoutTag = layout.name
37
- // Support both <DefaultLayout ...> and <DefaultLayout>...</DefaultLayout>
38
- const layoutRegex = new RegExp(`<${layoutTag}\\b([^>]*)>(?:([\\s\\S]*?)</${layoutTag}>)?`, 'i')
39
- const match = source.match(layoutRegex)
40
-
41
- let pageHtml = ''
42
- let layoutPropsStr = ''
43
-
44
- if (match) {
45
- layoutPropsStr = match[1] || ''
46
- pageHtml = match[2] || ''
47
-
48
- // If it's a self-closing tag or empty, it might not have captured content correctly if regex failed
49
- if (!pageHtml && !source.includes(`</${layoutTag}>`)) {
50
- // Self-closing check? No, Zenith usually expects explicit tags or the layout to wrap everything.
51
- }
52
- } else {
53
- // If layout tag not found as root, assume everything minus script/style is content
54
- pageHtml = source.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
55
- pageHtml = pageHtml.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '').trim()
56
- }
57
-
58
- // 3. Parse props from the tag
59
- const mergedProps = { ...props }
60
- if (layoutPropsStr) {
61
- // Support legacy props={{...}}
62
- const legacyMatch = layoutPropsStr.match(/props=\{\{([^}]+)\}\}/)
63
- if (legacyMatch && legacyMatch[1]) {
64
- const propsBody = legacyMatch[1]
65
- const pairs = propsBody.split(/,(?![^[]*\])(?![^{]*\})/)
66
- for (const pair of pairs) {
67
- const [key, ...valParts] = pair.split(':')
68
- if (key && valParts.length > 0) {
69
- mergedProps[key.trim()] = valParts.join(':').trim()
70
- }
71
- }
72
- }
73
-
74
- // Support natural props: title={"Home"} or title="Home" or title={title}
75
- const attrRegex = /([a-zA-Z0-9-]+)=(?:\{([^}]+)\}|"([^"]*)"|'([^']*)')/g
76
- let attrMatch
77
- while ((attrMatch = attrRegex.exec(layoutPropsStr)) !== null) {
78
- const name = attrMatch[1]
79
- const value = attrMatch[2] || attrMatch[3] || attrMatch[4]
80
- if (name && name !== 'props') {
81
- mergedProps[name] = value
82
- }
83
- }
84
- }
85
-
86
- // 4. Merge Scripts with Prop Injection
87
- // Layout scripts come first, then page scripts. Props are injected at the very top.
88
- const propDeclarations = Object.entries(mergedProps)
89
- .map(([key, value]) => {
90
- // If value looks like a string literal, keep it as is, otherwise wrap if needed
91
- // Actually, if it came from {expression}, we should treat it as code.
92
- // If it came from "string", we treat it as a string.
93
- const isExpression = layoutPropsStr.includes(`${key}={${value}}`)
94
- if (isExpression) {
95
- return `const ${key} = ${value};`
96
- }
97
- return `const ${key} = ${typeof value === 'string' && !value.startsWith("'") && !value.startsWith('"') ? `'${value}'` : value};`
98
- })
99
- .join('\n')
100
-
101
- const mergedScripts = [
102
- propDeclarations,
103
- ...layout.scripts,
104
- ...pageScripts
105
- ].filter(Boolean).join('\n\n')
106
-
107
- // 5. Merge Styles
108
- const mergedStyles = [
109
- ...layout.styles,
110
- ...pageStyles
111
- ].filter(Boolean).join('\n\n')
112
-
113
- // 6. Inline HTML into layout slot
114
- let finalizedHtml = layout.html.replace(/<Slot\s*\/>/gi, pageHtml)
115
- finalizedHtml = finalizedHtml.replace(/<slot\s*>[\s\S]*?<\/slot>/gi, pageHtml)
116
-
117
- // 7. Reconstruct the full .zen source
118
- const propNames = Object.keys(mergedProps).join(',')
119
- const scriptTag = `<script setup${isTypeScript ? '="ts"' : ''}${propNames ? ` props="${propNames}"` : ''}>`
120
-
121
- return `
122
- ${scriptTag}
123
- ${mergedScripts}
124
- </script>
125
-
126
- ${finalizedHtml}
127
-
128
- <style>
129
- ${mergedStyles}
130
- </style>
131
- `.trim()
132
- }
@@ -1,292 +0,0 @@
1
- /**
2
- * Slot Resolution - Compound Component Model
3
- *
4
- * Resolves slots using compound component pattern (Card.Header, Card.Body)
5
- * NOT template tags. This matches React/Astro semantics.
6
- *
7
- * IMPORTANT: Slot content must preserve the parent reactive scope.
8
- * Components are purely structural transforms - they don't create new reactive boundaries.
9
- *
10
- * Example usage:
11
- * <Card>
12
- * <Card.Header><h3>Title</h3></Card.Header>
13
- * <p>Body content goes to default slot</p>
14
- * <Card.Footer><Button>OK</Button></Card.Footer>
15
- * </Card>
16
- */
17
-
18
- import type { TemplateNode, ComponentNode, ElementNode, LoopContext } from '../ir/types'
19
-
20
- export interface ResolvedSlots {
21
- default: TemplateNode[]
22
- named: Map<string, TemplateNode[]>
23
- // Preserve the parent's reactive scope for slot content
24
- parentLoopContext?: LoopContext
25
- }
26
-
27
- /**
28
- * Extract slots from component children using compound component pattern
29
- *
30
- * Children named `ParentComponent.SlotName` become named slots.
31
- * All other children go to the default slot.
32
- * Preserves the parent's reactive scope (loopContext) for all slot content.
33
- *
34
- * @param parentName - Name of the parent component (e.g., "Card")
35
- * @param children - Child nodes from component usage
36
- * @param parentLoopContext - The reactive scope from the parent (must be preserved)
37
- */
38
- export function extractSlotsFromChildren(
39
- parentName: string,
40
- children: TemplateNode[],
41
- parentLoopContext?: LoopContext
42
- ): ResolvedSlots {
43
- const defaultSlot: TemplateNode[] = []
44
- const namedSlots = new Map<string, TemplateNode[]>()
45
-
46
- for (const child of children) {
47
- // Check if this is a compound component (e.g., Card.Header)
48
- if (child.type === 'component') {
49
- const compoundMatch = parseCompoundName(child.name, parentName)
50
-
51
- if (compoundMatch) {
52
- // This is a named slot (e.g., Card.Header -> "header")
53
- const slotName = compoundMatch.toLowerCase()
54
-
55
- if (!namedSlots.has(slotName)) {
56
- namedSlots.set(slotName, [])
57
- }
58
-
59
- // The compound component's children become the slot content
60
- // Preserve parent's loopContext on each child
61
- const scopedChildren = child.children.map(c =>
62
- rebindNodeToScope(c, parentLoopContext)
63
- )
64
- namedSlots.get(slotName)!.push(...scopedChildren)
65
- } else {
66
- // Regular component, goes to default slot
67
- // Preserve parent's loopContext
68
- defaultSlot.push(rebindNodeToScope(child, parentLoopContext))
69
- }
70
- } else {
71
- // Elements, text, expressions go to default slot
72
- // Preserve parent's loopContext
73
- defaultSlot.push(rebindNodeToScope(child, parentLoopContext))
74
- }
75
- }
76
-
77
- return {
78
- default: defaultSlot,
79
- named: namedSlots,
80
- parentLoopContext
81
- }
82
- }
83
-
84
- /**
85
- * Rebind a node to the parent's reactive scope
86
- *
87
- * This ensures that expressions and event bindings in slot content
88
- * remain connected to the parent component's reactive graph.
89
- * Components must be purely structural - they don't create new reactive boundaries.
90
- */
91
- function rebindNodeToScope(node: TemplateNode, loopContext?: LoopContext): TemplateNode {
92
- // If no parent scope to preserve, return as-is
93
- if (!loopContext) {
94
- return node
95
- }
96
-
97
- // Merge the parent's loopContext with existing loopContext
98
- // Parent scope takes precedence to ensure reactivity flows through
99
- switch (node.type) {
100
- case 'expression':
101
- return {
102
- ...node,
103
- loopContext: mergeLoopContext(node.loopContext, loopContext)
104
- }
105
-
106
- case 'element':
107
- return {
108
- ...node,
109
- loopContext: mergeLoopContext(node.loopContext, loopContext),
110
- attributes: node.attributes.map(attr => ({
111
- ...attr,
112
- loopContext: attr.loopContext
113
- ? mergeLoopContext(attr.loopContext, loopContext)
114
- : loopContext
115
- })),
116
- children: node.children.map(c => rebindNodeToScope(c, loopContext))
117
- }
118
-
119
- case 'component':
120
- return {
121
- ...node,
122
- loopContext: mergeLoopContext(node.loopContext, loopContext),
123
- children: node.children.map(c => rebindNodeToScope(c, loopContext))
124
- }
125
-
126
- case 'text':
127
- // Text nodes don't have reactive bindings
128
- return node
129
-
130
- default:
131
- return node
132
- }
133
- }
134
-
135
- /**
136
- * Merge two loop contexts, combining their variables
137
- * Parent context variables take precedence (added last so they shadow)
138
- */
139
- function mergeLoopContext(existing?: LoopContext, parent?: LoopContext): LoopContext | undefined {
140
- if (!existing && !parent) return undefined
141
- if (!existing) return parent
142
- if (!parent) return existing
143
-
144
- // Combine variables, parent variables shadow existing
145
- const allVars = new Set([...existing.variables, ...parent.variables])
146
-
147
- return {
148
- variables: Array.from(allVars),
149
- mapSource: parent.mapSource || existing.mapSource
150
- }
151
- }
152
-
153
- /**
154
- * Parse compound component name
155
- *
156
- * Given "Card.Header" and parent "Card", returns "Header"
157
- * Given "Card.Footer" and parent "Card", returns "Footer"
158
- * Given "Button" and parent "Card", returns null (not a compound)
159
- *
160
- * @param componentName - Full component name (e.g., "Card.Header")
161
- * @param parentName - Parent component name (e.g., "Card")
162
- * @returns Slot name or null if not a compound of this parent
163
- */
164
- function parseCompoundName(componentName: string, parentName: string): string | null {
165
- const prefix = `${parentName}.`
166
-
167
- if (componentName.startsWith(prefix)) {
168
- return componentName.slice(prefix.length)
169
- }
170
-
171
- return null
172
- }
173
-
174
- /**
175
- * Resolve slots in component template nodes
176
- *
177
- * Replaces <slot /> and <slot name="X" /> with children from resolved slots.
178
- * All slot content is rebound to the parent's reactive scope.
179
- */
180
- export function resolveSlots(
181
- componentNodes: TemplateNode[],
182
- slots: ResolvedSlots
183
- ): TemplateNode[] {
184
- const resolved: TemplateNode[] = []
185
-
186
- for (const node of componentNodes) {
187
- const result = resolveNode(node, slots)
188
- if (Array.isArray(result)) {
189
- resolved.push(...result)
190
- } else {
191
- resolved.push(result)
192
- }
193
- }
194
-
195
- return resolved
196
- }
197
-
198
- /**
199
- * Resolve a single node, replacing slot tags with content
200
- * Ensures all slot content maintains the parent's reactive scope
201
- */
202
- function resolveNode(
203
- node: TemplateNode,
204
- slots: ResolvedSlots
205
- ): TemplateNode | TemplateNode[] {
206
- if (node.type === 'element' && node.tag === 'slot') {
207
- // This is a slot tag - replace it with children
208
- const nameAttr = node.attributes.find(attr => attr.name === 'name')
209
- const slotName = typeof nameAttr?.value === 'string' ? nameAttr.value : null
210
-
211
- if (slotName) {
212
- // Named slot
213
- const namedChildren = slots.named.get(slotName.toLowerCase()) || []
214
-
215
- // If no children provided and slot has fallback content, use fallback
216
- if (namedChildren.length === 0 && node.children.length > 0) {
217
- return node.children
218
- }
219
-
220
- // Return slot content (already scoped during extraction)
221
- return namedChildren.length > 0 ? namedChildren : []
222
- } else {
223
- // Default slot
224
- // If no children provided and slot has fallback content, use fallback
225
- if (slots.default.length === 0 && node.children.length > 0) {
226
- return node.children
227
- }
228
-
229
- // Return slot content (already scoped during extraction)
230
- return slots.default
231
- }
232
- }
233
-
234
- if (node.type === 'element') {
235
- // Recursively resolve slots in children
236
- const resolvedChildren: TemplateNode[] = []
237
- for (const child of node.children) {
238
- const result = resolveNode(child, slots)
239
- if (Array.isArray(result)) {
240
- resolvedChildren.push(...result)
241
- } else {
242
- resolvedChildren.push(result)
243
- }
244
- }
245
-
246
- return {
247
- ...node,
248
- children: resolvedChildren
249
- }
250
- }
251
-
252
- if (node.type === 'component') {
253
- // Recursively resolve slots in component children
254
- const resolvedChildren: TemplateNode[] = []
255
- for (const child of node.children) {
256
- const result = resolveNode(child, slots)
257
- if (Array.isArray(result)) {
258
- resolvedChildren.push(...result)
259
- } else {
260
- resolvedChildren.push(result)
261
- }
262
- }
263
-
264
- return {
265
- ...node,
266
- children: resolvedChildren
267
- }
268
- }
269
-
270
- // Text and expression nodes pass through unchanged
271
- return node
272
- }
273
-
274
- /**
275
- * Check if a node tree contains any slots
276
- */
277
- export function hasSlots(nodes: TemplateNode[]): boolean {
278
- function checkNode(node: TemplateNode): boolean {
279
- if (node.type === 'element') {
280
- if (node.tag === 'slot') {
281
- return true
282
- }
283
- return node.children.some(checkNode)
284
- }
285
- if (node.type === 'component') {
286
- return node.children.some(checkNode)
287
- }
288
- return false
289
- }
290
-
291
- return nodes.some(checkNode)
292
- }
@@ -1,126 +0,0 @@
1
- /**
2
- * Transform Template Nodes
3
- *
4
- * Transforms IR nodes into HTML strings and collects bindings
5
- */
6
-
7
- import type { TemplateNode, ElementNode, TextNode, ExpressionNode, ExpressionIR, LoopContext } from '../ir/types'
8
- import type { Binding } from '../output/types'
9
-
10
- let bindingIdCounter = 0
11
-
12
- function generateBindingId(): string {
13
- return `expr_${bindingIdCounter++}`
14
- }
15
-
16
- /**
17
- * Transform a template node to HTML and collect bindings
18
- * Phase 7: Supports loop context propagation for map expressions
19
- */
20
- export function transformNode(
21
- node: TemplateNode,
22
- expressions: ExpressionIR[],
23
- parentLoopContext?: LoopContext // Phase 7: Loop context from parent map expressions
24
- ): { html: string; bindings: Binding[] } {
25
- const bindings: Binding[] = []
26
-
27
- function transform(node: TemplateNode, loopContext?: LoopContext): string {
28
- switch (node.type) {
29
- case 'text':
30
- return escapeHtml((node as TextNode).value)
31
-
32
- case 'expression': {
33
- const exprNode = node as ExpressionNode
34
- // Find the expression in the expressions array
35
- const expr = expressions.find(e => e.id === exprNode.expression)
36
- if (!expr) {
37
- throw new Error(`Expression ${exprNode.expression} not found`)
38
- }
39
-
40
- const bindingId = expr.id
41
- // Phase 7: Use loop context from ExpressionNode if available, otherwise use passed context
42
- const activeLoopContext = exprNode.loopContext || loopContext
43
-
44
- bindings.push({
45
- id: bindingId,
46
- type: 'text',
47
- target: 'data-zen-text',
48
- expression: expr.code,
49
- location: expr.location,
50
- loopContext: activeLoopContext // Phase 7: Attach loop context to binding
51
- })
52
-
53
- return `<span data-zen-text="${bindingId}" style="display: contents;"></span>`
54
- }
55
-
56
- case 'element': {
57
- const elNode = node as ElementNode
58
- const tag = elNode.tag
59
-
60
- // Build attributes
61
- const attrs: string[] = []
62
- for (const attr of elNode.attributes) {
63
- if (typeof attr.value === 'string') {
64
- // Static attribute
65
- const value = escapeHtml(attr.value)
66
- attrs.push(`${attr.name}="${value}"`)
67
- } else {
68
- // Expression attribute
69
- const expr = attr.value as ExpressionIR
70
- const bindingId = expr.id
71
- // Phase 7: Use loop context from AttributeIR if available, otherwise use element's loop context
72
- const activeLoopContext = attr.loopContext || loopContext
73
-
74
- bindings.push({
75
- id: bindingId,
76
- type: 'attribute',
77
- target: attr.name, // e.g., "class", "style"
78
- expression: expr.code,
79
- location: expr.location,
80
- loopContext: activeLoopContext // Phase 7: Attach loop context to binding
81
- })
82
-
83
- // Use data-zen-attr-{name} for attribute expressions
84
- attrs.push(`data-zen-attr-${attr.name}="${bindingId}"`)
85
- }
86
- }
87
-
88
- const attrStr = attrs.length > 0 ? ' ' + attrs.join(' ') : ''
89
-
90
- // Phase 7: Use loop context from ElementNode if available, otherwise use passed context
91
- const activeLoopContext = elNode.loopContext || loopContext
92
-
93
- // Transform children
94
- const childrenHtml = elNode.children.map(child => transform(child, activeLoopContext)).join('')
95
-
96
- // Self-closing tags
97
- const voidElements = new Set([
98
- 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
99
- 'link', 'meta', 'param', 'source', 'track', 'wbr'
100
- ])
101
-
102
- if (voidElements.has(tag.toLowerCase()) && childrenHtml === '') {
103
- return `<${tag}${attrStr} />`
104
- }
105
-
106
- return `<${tag}${attrStr}>${childrenHtml}</${tag}>`
107
- }
108
- }
109
- }
110
-
111
- const html = transform(node, parentLoopContext)
112
- return { html, bindings }
113
- }
114
-
115
- /**
116
- * Escape HTML special characters
117
- */
118
- function escapeHtml(text: string): string {
119
- return text
120
- .replace(/&/g, '&amp;')
121
- .replace(/</g, '&lt;')
122
- .replace(/>/g, '&gt;')
123
- .replace(/"/g, '&quot;')
124
- .replace(/'/g, '&#39;')
125
- }
126
-
@@ -1,38 +0,0 @@
1
- /**
2
- * Transform Template IR to Compiled Template
3
- *
4
- * Phase 2: Transform IR → Static HTML + Runtime Bindings
5
- */
6
-
7
- import type { ZenIR } from '../ir/types'
8
- import type { CompiledTemplate } from '../output/types'
9
- import { generateHTML } from './generateHTML'
10
- import { validateBindings, sortBindings } from './generateBindings'
11
-
12
- /**
13
- * Transform a ZenIR into CompiledTemplate
14
- */
15
- export function transformTemplate(ir: ZenIR): CompiledTemplate {
16
- // Generate HTML and collect bindings
17
- const { html, bindings } = generateHTML(ir.template.nodes, ir.template.expressions)
18
-
19
- // Validate bindings
20
- validateBindings(bindings)
21
-
22
- // Sort bindings by location for deterministic output
23
- const sortedBindings = sortBindings(bindings)
24
-
25
- // Extract scripts (raw content, pass through)
26
- const scripts = ir.script ? ir.script.raw : null
27
-
28
- // Extract styles (raw content, pass through)
29
- const styles = ir.styles.map(s => s.raw)
30
-
31
- return {
32
- html,
33
- bindings: sortedBindings,
34
- scripts,
35
- styles
36
- }
37
- }
38
-