@zenithbuild/core 1.2.2 → 1.2.3
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/README.md +20 -19
- package/cli/commands/add.ts +2 -2
- package/cli/commands/build.ts +2 -3
- package/cli/commands/dev.ts +93 -73
- package/cli/commands/index.ts +1 -1
- package/cli/commands/preview.ts +1 -1
- package/cli/commands/remove.ts +2 -2
- package/cli/index.ts +1 -1
- package/cli/main.ts +1 -1
- package/cli/utils/logger.ts +1 -1
- package/cli/utils/plugin-manager.ts +1 -1
- package/cli/utils/project.ts +4 -4
- package/core/components/ErrorPage.zen +218 -0
- package/core/components/index.ts +15 -0
- package/core/config.ts +1 -0
- package/core/index.ts +29 -0
- package/dist/compiler-native-frej59m4.node +0 -0
- package/dist/core/compiler-native-frej59m4.node +0 -0
- package/dist/core/index.js +6293 -0
- package/dist/runtime/lifecycle/index.js +1 -0
- package/dist/runtime/reactivity/index.js +1 -0
- package/dist/zen-build.js +1 -20118
- package/dist/zen-dev.js +1 -20118
- package/dist/zen-preview.js +1 -20118
- package/dist/zenith.js +1 -20118
- package/package.json +11 -20
- package/compiler/README.md +0 -380
- package/compiler/build-analyzer.ts +0 -122
- package/compiler/css/index.ts +0 -317
- package/compiler/discovery/componentDiscovery.ts +0 -242
- package/compiler/discovery/layouts.ts +0 -70
- package/compiler/errors/compilerError.ts +0 -56
- package/compiler/finalize/finalizeOutput.ts +0 -192
- package/compiler/finalize/generateFinalBundle.ts +0 -82
- package/compiler/index.ts +0 -83
- package/compiler/ir/types.ts +0 -174
- package/compiler/output/types.ts +0 -48
- package/compiler/parse/detectMapExpressions.ts +0 -102
- package/compiler/parse/importTypes.ts +0 -78
- package/compiler/parse/parseImports.ts +0 -309
- package/compiler/parse/parseScript.ts +0 -46
- package/compiler/parse/parseTemplate.ts +0 -628
- package/compiler/parse/parseZenFile.ts +0 -66
- package/compiler/parse/scriptAnalysis.ts +0 -91
- package/compiler/parse/trackLoopContext.ts +0 -82
- package/compiler/runtime/dataExposure.ts +0 -332
- package/compiler/runtime/generateDOM.ts +0 -255
- package/compiler/runtime/generateHydrationBundle.ts +0 -407
- package/compiler/runtime/hydration.ts +0 -309
- package/compiler/runtime/navigation.ts +0 -432
- package/compiler/runtime/thinRuntime.ts +0 -160
- package/compiler/runtime/transformIR.ts +0 -406
- package/compiler/runtime/wrapExpression.ts +0 -114
- package/compiler/runtime/wrapExpressionWithLoop.ts +0 -97
- package/compiler/spa-build.ts +0 -917
- package/compiler/ssg-build.ts +0 -486
- package/compiler/test/component-stacking.test.ts +0 -365
- package/compiler/test/map-lowering.test.ts +0 -130
- package/compiler/test/validate-test.ts +0 -104
- package/compiler/transform/classifyExpression.ts +0 -444
- package/compiler/transform/componentResolver.ts +0 -350
- package/compiler/transform/componentScriptTransformer.ts +0 -303
- package/compiler/transform/expressionTransformer.ts +0 -385
- package/compiler/transform/fragmentLowering.ts +0 -819
- package/compiler/transform/generateBindings.ts +0 -68
- package/compiler/transform/generateHTML.ts +0 -28
- package/compiler/transform/layoutProcessor.ts +0 -132
- package/compiler/transform/slotResolver.ts +0 -292
- package/compiler/transform/transformNode.ts +0 -314
- package/compiler/transform/transformTemplate.ts +0 -38
- package/compiler/validate/invariants.ts +0 -292
- package/compiler/validate/validateExpressions.ts +0 -168
- package/core/config/index.ts +0 -18
- package/core/config/loader.ts +0 -69
- package/core/config/types.ts +0 -119
- package/core/plugins/bridge.ts +0 -193
- package/core/plugins/index.ts +0 -7
- package/core/plugins/registry.ts +0 -126
- package/dist/cli.js +0 -11675
- package/runtime/build.ts +0 -17
- package/runtime/bundle-generator.ts +0 -1266
- package/runtime/client-runtime.ts +0 -891
- package/runtime/serve.ts +0 -93
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Finalize Output
|
|
3
|
-
*
|
|
4
|
-
* Phase 8/9/10: Generate final compiled HTML + JS output with hydration markers
|
|
5
|
-
*
|
|
6
|
-
* Ensures:
|
|
7
|
-
* - All expressions are replaced with hydration markers
|
|
8
|
-
* - HTML contains no raw {expression} syntax
|
|
9
|
-
* - JS runtime is ready for browser execution
|
|
10
|
-
* - Hydration markers are correctly placed
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { CompiledTemplate } from '../output/types'
|
|
14
|
-
import type { ZenIR } from '../ir/types'
|
|
15
|
-
import { transformIR, type RuntimeCode } from '../runtime/transformIR'
|
|
16
|
-
import { validateExpressionsOrThrow } from '../validate/validateExpressions'
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Finalized output ready for browser
|
|
20
|
-
*/
|
|
21
|
-
export interface FinalizedOutput {
|
|
22
|
-
html: string
|
|
23
|
-
js: string
|
|
24
|
-
styles: string[]
|
|
25
|
-
hasErrors: boolean
|
|
26
|
-
errors: string[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Finalize compiler output
|
|
31
|
-
*
|
|
32
|
-
* This is the final step that ensures:
|
|
33
|
-
* 1. All expressions are validated
|
|
34
|
-
* 2. HTML contains no raw expressions
|
|
35
|
-
* 3. JS runtime is generated
|
|
36
|
-
* 4. Output is ready for browser
|
|
37
|
-
*
|
|
38
|
-
* @param ir - Intermediate representation
|
|
39
|
-
* @param compiled - Compiled template from Phase 2
|
|
40
|
-
* @returns Finalized output
|
|
41
|
-
*/
|
|
42
|
-
export async function finalizeOutput(
|
|
43
|
-
ir: ZenIR,
|
|
44
|
-
compiled: CompiledTemplate
|
|
45
|
-
): Promise<FinalizedOutput> {
|
|
46
|
-
const errors: string[] = []
|
|
47
|
-
|
|
48
|
-
// 1. Validate all expressions (Phase 8/9/10 requirement)
|
|
49
|
-
try {
|
|
50
|
-
validateExpressionsOrThrow(ir.template.expressions, ir.filePath)
|
|
51
|
-
} catch (error: any) {
|
|
52
|
-
if (error instanceof Error) {
|
|
53
|
-
errors.push(error.message)
|
|
54
|
-
return {
|
|
55
|
-
html: '',
|
|
56
|
-
js: '',
|
|
57
|
-
styles: [],
|
|
58
|
-
hasErrors: true,
|
|
59
|
-
errors
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// 2. Verify HTML contains no raw expressions
|
|
65
|
-
const htmlErrors = verifyNoRawExpressions(compiled.html, ir.filePath)
|
|
66
|
-
if (htmlErrors.length > 0) {
|
|
67
|
-
errors.push(...htmlErrors)
|
|
68
|
-
return {
|
|
69
|
-
html: '',
|
|
70
|
-
js: '',
|
|
71
|
-
styles: [],
|
|
72
|
-
hasErrors: true,
|
|
73
|
-
errors
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// 3. Generate runtime code
|
|
78
|
-
let runtimeCode: RuntimeCode
|
|
79
|
-
try {
|
|
80
|
-
runtimeCode = await transformIR(ir)
|
|
81
|
-
} catch (error: any) {
|
|
82
|
-
errors.push(`Runtime generation failed: ${error.message}`)
|
|
83
|
-
return {
|
|
84
|
-
html: '',
|
|
85
|
-
js: '',
|
|
86
|
-
styles: [],
|
|
87
|
-
hasErrors: true,
|
|
88
|
-
errors
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// 4. Combine HTML and JS
|
|
93
|
-
const finalHTML = compiled.html
|
|
94
|
-
const finalJS = runtimeCode.bundle
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
html: finalHTML,
|
|
98
|
-
js: finalJS,
|
|
99
|
-
styles: compiled.styles,
|
|
100
|
-
hasErrors: false,
|
|
101
|
-
errors: []
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Verify HTML contains no raw {expression} syntax
|
|
107
|
-
*
|
|
108
|
-
* This is a critical check - browser must never see raw expressions
|
|
109
|
-
*
|
|
110
|
-
* Excludes:
|
|
111
|
-
* - Content inside <pre>, <code> tags (display code samples)
|
|
112
|
-
* - Content that looks like HTML tags (from entity decoding)
|
|
113
|
-
* - Comments
|
|
114
|
-
* - Data attributes
|
|
115
|
-
*/
|
|
116
|
-
function verifyNoRawExpressions(html: string, filePath: string): string[] {
|
|
117
|
-
const errors: string[] = []
|
|
118
|
-
|
|
119
|
-
// Remove content inside <pre> and <code> tags before checking
|
|
120
|
-
// These are code samples that may contain { } legitimately
|
|
121
|
-
let htmlToCheck = html
|
|
122
|
-
.replace(/<pre[^>]*>[\s\S]*?<\/pre>/gi, '')
|
|
123
|
-
.replace(/<code[^>]*>[\s\S]*?<\/code>/gi, '')
|
|
124
|
-
|
|
125
|
-
// Check for raw {expression} patterns (not data-zen-* attributes)
|
|
126
|
-
// Allow data-zen-text, data-zen-attr-* but not raw { }
|
|
127
|
-
const rawExpressionPattern = /\{[^}]*\}/g
|
|
128
|
-
const matches = htmlToCheck.match(rawExpressionPattern)
|
|
129
|
-
|
|
130
|
-
if (matches && matches.length > 0) {
|
|
131
|
-
// Filter out false positives
|
|
132
|
-
const actualExpressions = matches.filter(match => {
|
|
133
|
-
// Exclude if it's in a comment
|
|
134
|
-
if (html.includes(`<!--${match}`) || html.includes(`${match}-->`)) {
|
|
135
|
-
return false
|
|
136
|
-
}
|
|
137
|
-
// Exclude if it's in a data attribute value (already processed)
|
|
138
|
-
if (match.includes('data-zen-')) {
|
|
139
|
-
return false
|
|
140
|
-
}
|
|
141
|
-
// Exclude if it contains HTML tags (likely from entity decoding in display content)
|
|
142
|
-
// Real expressions don't start with < inside braces
|
|
143
|
-
if (match.match(/^\{[\s]*</)) {
|
|
144
|
-
return false
|
|
145
|
-
}
|
|
146
|
-
// Exclude if it looks like display content containing HTML (spans, divs, etc)
|
|
147
|
-
if (/<[a-zA-Z]/.test(match)) {
|
|
148
|
-
return false
|
|
149
|
-
}
|
|
150
|
-
// Exclude CSS-like content (common in style attributes)
|
|
151
|
-
if (match.includes(';') && match.includes(':')) {
|
|
152
|
-
return false
|
|
153
|
-
}
|
|
154
|
-
// Exclude if it's a single closing tag pattern (from multiline display)
|
|
155
|
-
if (/^\{[\s]*<\//.test(match)) {
|
|
156
|
-
return false
|
|
157
|
-
}
|
|
158
|
-
// This looks like a raw expression
|
|
159
|
-
return true
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
if (actualExpressions.length > 0) {
|
|
163
|
-
errors.push(
|
|
164
|
-
`HTML contains raw expressions that were not compiled: ${actualExpressions.join(', ')}\n` +
|
|
165
|
-
`File: ${filePath}\n` +
|
|
166
|
-
`All expressions must be replaced with hydration markers (data-zen-text, data-zen-attr-*)`
|
|
167
|
-
)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return errors
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Generate final output with error handling
|
|
176
|
-
*
|
|
177
|
-
* Throws if validation fails (build must fail on errors)
|
|
178
|
-
*/
|
|
179
|
-
export async function finalizeOutputOrThrow(
|
|
180
|
-
ir: ZenIR,
|
|
181
|
-
compiled: CompiledTemplate
|
|
182
|
-
): Promise<FinalizedOutput> {
|
|
183
|
-
const output = await finalizeOutput(ir, compiled)
|
|
184
|
-
|
|
185
|
-
if (output.hasErrors) {
|
|
186
|
-
const errorMessage = output.errors.join('\n\n')
|
|
187
|
-
throw new Error(`Compilation failed:\n\n${errorMessage}`)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return output
|
|
191
|
-
}
|
|
192
|
-
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate Final Bundle
|
|
3
|
-
*
|
|
4
|
-
* Phase 8/9/10: Generate final browser-ready bundle
|
|
5
|
-
*
|
|
6
|
-
* Combines:
|
|
7
|
-
* - Compiled HTML
|
|
8
|
-
* - Runtime JS
|
|
9
|
-
* - Expression functions
|
|
10
|
-
* - Event bindings
|
|
11
|
-
* - Style injection
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import type { FinalizedOutput } from './finalizeOutput'
|
|
15
|
-
import type { RuntimeCode } from '../runtime/transformIR'
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Generate final bundle code
|
|
19
|
-
*
|
|
20
|
-
* This is the complete JavaScript bundle that will execute in the browser.
|
|
21
|
-
* All expressions are pre-compiled - no template parsing at runtime.
|
|
22
|
-
*/
|
|
23
|
-
export function generateFinalBundle(finalized: FinalizedOutput): string {
|
|
24
|
-
return `// Zenith Compiled Bundle (Phase 8/9/10)
|
|
25
|
-
// Generated at compile time - no .zen parsing in browser
|
|
26
|
-
// All expressions are pre-compiled - deterministic output
|
|
27
|
-
|
|
28
|
-
${finalized.js}
|
|
29
|
-
|
|
30
|
-
// Bundle complete - ready for browser execution
|
|
31
|
-
`
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Generate HTML with inline script
|
|
36
|
-
*/
|
|
37
|
-
export function generateHTMLWithScript(
|
|
38
|
-
html: string,
|
|
39
|
-
jsBundle: string,
|
|
40
|
-
styles: string[]
|
|
41
|
-
): string {
|
|
42
|
-
// Inject styles as <style> tags
|
|
43
|
-
const styleTags = styles.map(style => `<style>${escapeHTML(style)}</style>`).join('\n')
|
|
44
|
-
|
|
45
|
-
// Inject JS bundle as inline script
|
|
46
|
-
const scriptTag = `<script>${jsBundle}</script>`
|
|
47
|
-
|
|
48
|
-
// Find </head> or <body> to inject styles
|
|
49
|
-
// Find </body> to inject script
|
|
50
|
-
let result = html
|
|
51
|
-
|
|
52
|
-
if (styleTags) {
|
|
53
|
-
if (result.includes('</head>')) {
|
|
54
|
-
result = result.replace('</head>', `${styleTags}\n</head>`)
|
|
55
|
-
} else if (result.includes('<body')) {
|
|
56
|
-
result = result.replace('<body', `${styleTags}\n<body`)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (scriptTag) {
|
|
61
|
-
if (result.includes('</body>')) {
|
|
62
|
-
result = result.replace('</body>', `${scriptTag}\n</body>`)
|
|
63
|
-
} else {
|
|
64
|
-
result += scriptTag
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return result
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Escape HTML for safe embedding
|
|
73
|
-
*/
|
|
74
|
-
function escapeHTML(str: string): string {
|
|
75
|
-
return str
|
|
76
|
-
.replace(/&/g, '&')
|
|
77
|
-
.replace(/</g, '<')
|
|
78
|
-
.replace(/>/g, '>')
|
|
79
|
-
.replace(/"/g, '"')
|
|
80
|
-
.replace(/'/g, ''')
|
|
81
|
-
}
|
|
82
|
-
|
package/compiler/index.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'fs'
|
|
2
|
-
import { parseTemplate } from './parse/parseTemplate'
|
|
3
|
-
import { parseScript } from './parse/parseScript'
|
|
4
|
-
import { transformTemplate } from './transform/transformTemplate'
|
|
5
|
-
import { finalizeOutputOrThrow } from './finalize/finalizeOutput'
|
|
6
|
-
import { validateInvariants } from './validate/invariants'
|
|
7
|
-
import { InvariantError } from './errors/compilerError'
|
|
8
|
-
import type { ZenIR, StyleIR } from './ir/types'
|
|
9
|
-
import type { CompiledTemplate } from './output/types'
|
|
10
|
-
import type { FinalizedOutput } from './finalize/finalizeOutput'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Compile a .zen file into IR and CompiledTemplate
|
|
14
|
-
*/
|
|
15
|
-
export async function compileZen(filePath: string): Promise<{
|
|
16
|
-
ir: ZenIR
|
|
17
|
-
compiled: CompiledTemplate
|
|
18
|
-
finalized?: FinalizedOutput
|
|
19
|
-
}> {
|
|
20
|
-
const source = readFileSync(filePath, 'utf-8')
|
|
21
|
-
return compileZenSource(source, filePath)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Compile Zen source string into IR and CompiledTemplate
|
|
26
|
-
*/
|
|
27
|
-
export async function compileZenSource(
|
|
28
|
-
source: string,
|
|
29
|
-
filePath: string,
|
|
30
|
-
options?: {
|
|
31
|
-
componentsDir?: string
|
|
32
|
-
}
|
|
33
|
-
): Promise<{
|
|
34
|
-
ir: ZenIR
|
|
35
|
-
compiled: CompiledTemplate
|
|
36
|
-
finalized?: FinalizedOutput
|
|
37
|
-
}> {
|
|
38
|
-
// Parse template
|
|
39
|
-
const template = parseTemplate(source, filePath)
|
|
40
|
-
|
|
41
|
-
// Parse script
|
|
42
|
-
const script = parseScript(source)
|
|
43
|
-
|
|
44
|
-
// Parse styles
|
|
45
|
-
const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi
|
|
46
|
-
const styles: StyleIR[] = []
|
|
47
|
-
let match
|
|
48
|
-
while ((match = styleRegex.exec(source)) !== null) {
|
|
49
|
-
if (match[1]) styles.push({ raw: match[1].trim() })
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
let ir: ZenIR = {
|
|
53
|
-
filePath,
|
|
54
|
-
template,
|
|
55
|
-
script,
|
|
56
|
-
// componentScripts: [],
|
|
57
|
-
styles
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Resolve components if components directory is provided
|
|
61
|
-
if (options?.componentsDir) {
|
|
62
|
-
const { discoverComponents } = require('./discovery/componentDiscovery')
|
|
63
|
-
const { resolveComponentsInIR } = require('./transform/componentResolver')
|
|
64
|
-
|
|
65
|
-
// Component resolution may throw InvariantError — let it propagate
|
|
66
|
-
const components = discoverComponents(options.componentsDir)
|
|
67
|
-
ir = resolveComponentsInIR(ir, components)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Validate all compiler invariants after resolution
|
|
71
|
-
// Throws InvariantError if any invariant is violated
|
|
72
|
-
validateInvariants(ir, filePath)
|
|
73
|
-
|
|
74
|
-
const compiled = transformTemplate(ir)
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
const finalized = await finalizeOutputOrThrow(ir, compiled)
|
|
78
|
-
return { ir, compiled, finalized }
|
|
79
|
-
} catch (error: any) {
|
|
80
|
-
throw new Error(`Failed to finalize output for ${filePath}:\\n${error.message}`)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
package/compiler/ir/types.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Intermediate Representation (IR)
|
|
3
|
-
*
|
|
4
|
-
* Phase 1: Parse & Extract
|
|
5
|
-
* This IR represents the parsed structure of a .zen file
|
|
6
|
-
* without any runtime execution or transformation.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Structured ES module import metadata
|
|
11
|
-
* Parsed from component scripts, used for deterministic bundling
|
|
12
|
-
*/
|
|
13
|
-
export interface ScriptImport {
|
|
14
|
-
source: string // Module specifier, e.g. 'gsap'
|
|
15
|
-
specifiers: string // Import clause, e.g. '{ gsap }' or 'gsap' or ''
|
|
16
|
-
typeOnly: boolean // TypeScript type-only import
|
|
17
|
-
sideEffect: boolean // Side-effect import (no specifiers)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Component Script IR - represents a component's script block
|
|
22
|
-
* Used for collecting and bundling component scripts
|
|
23
|
-
*/
|
|
24
|
-
export type ComponentScriptIR = {
|
|
25
|
-
name: string // Component name (e.g., 'HeroSection')
|
|
26
|
-
script: string // Raw script content
|
|
27
|
-
props: string[] // Declared props
|
|
28
|
-
scriptAttributes: Record<string, string> // Script attributes (setup, lang)
|
|
29
|
-
imports: ScriptImport[] // Parsed npm imports for bundling
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export type ZenIR = {
|
|
33
|
-
filePath: string
|
|
34
|
-
template: TemplateIR
|
|
35
|
-
script: ScriptIR | null
|
|
36
|
-
styles: StyleIR[]
|
|
37
|
-
componentScripts?: ComponentScriptIR[] // Scripts from used components
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type TemplateIR = {
|
|
41
|
-
raw: string
|
|
42
|
-
nodes: TemplateNode[]
|
|
43
|
-
expressions: ExpressionIR[]
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export type TemplateNode =
|
|
47
|
-
| ElementNode
|
|
48
|
-
| TextNode
|
|
49
|
-
| ExpressionNode
|
|
50
|
-
| ComponentNode
|
|
51
|
-
| ConditionalFragmentNode // JSX ternary: {cond ? <A /> : <B />}
|
|
52
|
-
| OptionalFragmentNode // JSX logical AND: {cond && <A />}
|
|
53
|
-
| LoopFragmentNode // JSX map: {items.map(i => <li>...</li>)}
|
|
54
|
-
|
|
55
|
-
export type ElementNode = {
|
|
56
|
-
type: 'element'
|
|
57
|
-
tag: string
|
|
58
|
-
attributes: AttributeIR[]
|
|
59
|
-
children: TemplateNode[]
|
|
60
|
-
location: SourceLocation
|
|
61
|
-
loopContext?: LoopContext // Phase 7: Inherited loop context from parent map expressions
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export type ComponentNode = {
|
|
65
|
-
type: 'component'
|
|
66
|
-
name: string
|
|
67
|
-
attributes: AttributeIR[]
|
|
68
|
-
children: TemplateNode[]
|
|
69
|
-
location: SourceLocation
|
|
70
|
-
loopContext?: LoopContext
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export type TextNode = {
|
|
74
|
-
type: 'text'
|
|
75
|
-
value: string
|
|
76
|
-
location: SourceLocation
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export type ExpressionNode = {
|
|
80
|
-
type: 'expression'
|
|
81
|
-
expression: string
|
|
82
|
-
location: SourceLocation
|
|
83
|
-
loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Conditional Fragment Node
|
|
88
|
-
*
|
|
89
|
-
* Represents ternary expressions with JSX branches: {cond ? <A /> : <B />}
|
|
90
|
-
*
|
|
91
|
-
* BOTH branches are compiled at compile time.
|
|
92
|
-
* Runtime toggles visibility — never creates DOM.
|
|
93
|
-
*/
|
|
94
|
-
export type ConditionalFragmentNode = {
|
|
95
|
-
type: 'conditional-fragment'
|
|
96
|
-
condition: string // The condition expression code
|
|
97
|
-
consequent: TemplateNode[] // Precompiled "true" branch
|
|
98
|
-
alternate: TemplateNode[] // Precompiled "false" branch
|
|
99
|
-
location: SourceLocation
|
|
100
|
-
loopContext?: LoopContext
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Optional Fragment Node
|
|
105
|
-
*
|
|
106
|
-
* Represents logical AND expressions with JSX: {cond && <A />}
|
|
107
|
-
*
|
|
108
|
-
* Fragment is compiled at compile time.
|
|
109
|
-
* Runtime toggles mount/unmount based on condition.
|
|
110
|
-
*/
|
|
111
|
-
export type OptionalFragmentNode = {
|
|
112
|
-
type: 'optional-fragment'
|
|
113
|
-
condition: string // The condition expression code
|
|
114
|
-
fragment: TemplateNode[] // Precompiled fragment
|
|
115
|
-
location: SourceLocation
|
|
116
|
-
loopContext?: LoopContext
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Loop Fragment Node
|
|
121
|
-
*
|
|
122
|
-
* Represents .map() expressions with JSX body: {items.map(i => <li>...</li>)}
|
|
123
|
-
*
|
|
124
|
-
* Desugars to @for loop semantics at compile time.
|
|
125
|
-
* Body is compiled once, instantiated per item at runtime.
|
|
126
|
-
* Node identity is compiler-owned via stable keys.
|
|
127
|
-
*/
|
|
128
|
-
export type LoopFragmentNode = {
|
|
129
|
-
type: 'loop-fragment'
|
|
130
|
-
source: string // Array expression (e.g., 'items')
|
|
131
|
-
itemVar: string // Loop variable (e.g., 'item')
|
|
132
|
-
indexVar?: string // Optional index variable
|
|
133
|
-
body: TemplateNode[] // Precompiled loop body template
|
|
134
|
-
location: SourceLocation
|
|
135
|
-
loopContext: LoopContext // Extended with this loop's variables
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export type AttributeIR = {
|
|
139
|
-
name: string
|
|
140
|
-
value: string | ExpressionIR
|
|
141
|
-
location: SourceLocation
|
|
142
|
-
loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Loop context for expressions inside map iterations
|
|
147
|
-
* Phase 7: Tracks loop variables (e.g., todo, index) for expressions inside .map() calls
|
|
148
|
-
*/
|
|
149
|
-
export type LoopContext = {
|
|
150
|
-
variables: string[] // e.g., ['todo', 'index'] for todoItems.map((todo, index) => ...)
|
|
151
|
-
mapSource?: string // The array being mapped, e.g., 'todoItems'
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export type ExpressionIR = {
|
|
155
|
-
id: string
|
|
156
|
-
code: string
|
|
157
|
-
location: SourceLocation
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export type ScriptIR = {
|
|
161
|
-
raw: string
|
|
162
|
-
attributes: Record<string, string>
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export type StyleIR = {
|
|
166
|
-
raw: string
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export type SourceLocation = {
|
|
170
|
-
line: number
|
|
171
|
-
column: number
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
package/compiler/output/types.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compiled Template Output Types
|
|
3
|
-
*
|
|
4
|
-
* Phase 2: Transform IR → Static HTML + Runtime Bindings
|
|
5
|
-
* Phase 8: Extended with fragment binding types (loop, conditional, optional)
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { TemplateNode } from '../ir/types'
|
|
9
|
-
|
|
10
|
-
export type CompiledTemplate = {
|
|
11
|
-
html: string
|
|
12
|
-
bindings: Binding[]
|
|
13
|
-
scripts: string | null
|
|
14
|
-
styles: string[]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type Binding = {
|
|
18
|
-
id: string
|
|
19
|
-
type: 'text' | 'attribute' | 'loop' | 'conditional' | 'optional'
|
|
20
|
-
target: string // e.g., "data-zen-text" or "class" for attribute bindings
|
|
21
|
-
expression: string // The original expression code
|
|
22
|
-
location?: {
|
|
23
|
-
line: number
|
|
24
|
-
column: number
|
|
25
|
-
}
|
|
26
|
-
loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
|
|
27
|
-
loopMeta?: LoopMeta // Phase 8: Metadata for loop bindings
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Loop binding metadata
|
|
32
|
-
* Phase 8: Contains loop variable names and body template for runtime instantiation
|
|
33
|
-
*/
|
|
34
|
-
export type LoopMeta = {
|
|
35
|
-
itemVar: string
|
|
36
|
-
indexVar?: string
|
|
37
|
-
bodyTemplate: TemplateNode[]
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Loop context for expressions inside map iterations
|
|
42
|
-
* Phase 7: Tracks loop variables for runtime setter generation
|
|
43
|
-
*/
|
|
44
|
-
export type LoopContext = {
|
|
45
|
-
variables: string[] // e.g., ['todo', 'index']
|
|
46
|
-
mapSource?: string // The array being mapped, e.g., 'todoItems'
|
|
47
|
-
}
|
|
48
|
-
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Map Expression Detection
|
|
3
|
-
*
|
|
4
|
-
* Phase 7: Detects .map() expressions and extracts loop context information
|
|
5
|
-
*
|
|
6
|
-
* This module analyzes expression code to detect map expressions like:
|
|
7
|
-
* - todoItems.map(todo => ...)
|
|
8
|
-
* - notifications.map((n, index) => ...)
|
|
9
|
-
*
|
|
10
|
-
* It extracts:
|
|
11
|
-
* - The array source (todoItems, notifications)
|
|
12
|
-
* - Loop variable names (todo, n, index)
|
|
13
|
-
* - The map body/template
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type { ExpressionIR } from '../ir/types'
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Detected map expression information
|
|
20
|
-
*/
|
|
21
|
-
export interface MapExpressionInfo {
|
|
22
|
-
isMap: boolean
|
|
23
|
-
arraySource?: string // e.g., 'todoItems'
|
|
24
|
-
itemVariable?: string // e.g., 'todo'
|
|
25
|
-
indexVariable?: string // e.g., 'index'
|
|
26
|
-
mapBody?: string // The template/body inside the map
|
|
27
|
-
fullExpression?: string // The full expression code
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Detect if an expression is a map expression and extract loop context
|
|
32
|
-
*
|
|
33
|
-
* Patterns detected:
|
|
34
|
-
* - arraySource.map(item => body)
|
|
35
|
-
* - arraySource.map((item, index) => body)
|
|
36
|
-
* - arraySource.map(item => (body))
|
|
37
|
-
*/
|
|
38
|
-
export function detectMapExpression(expr: ExpressionIR): MapExpressionInfo {
|
|
39
|
-
const { code } = expr
|
|
40
|
-
|
|
41
|
-
// Pattern: arraySource.map(item => body)
|
|
42
|
-
// Pattern: arraySource.map((item, index) => body)
|
|
43
|
-
// Pattern: arraySource.map(item => (body))
|
|
44
|
-
const mapPattern = /^(.+?)\.\s*map\s*\(\s*\(?([^)=,\s]+)(?:\s*,\s*([^)=,\s]+))?\s*\)?\s*=>\s*(.+?)\)?$/s
|
|
45
|
-
|
|
46
|
-
const match = code.match(mapPattern)
|
|
47
|
-
if (!match) {
|
|
48
|
-
return { isMap: false }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const arraySource = match[1]?.trim()
|
|
52
|
-
const itemVariable = match[2]?.trim()
|
|
53
|
-
const indexVariable = match[3]?.trim()
|
|
54
|
-
const mapBody = match[4]?.trim()
|
|
55
|
-
|
|
56
|
-
if (!arraySource || !itemVariable || !mapBody) {
|
|
57
|
-
return { isMap: false }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
isMap: true,
|
|
62
|
-
arraySource,
|
|
63
|
-
itemVariable,
|
|
64
|
-
indexVariable,
|
|
65
|
-
mapBody,
|
|
66
|
-
fullExpression: code
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Extract loop variables from a map expression
|
|
72
|
-
* Returns array of variable names in order: [itemVariable, indexVariable?]
|
|
73
|
-
*/
|
|
74
|
-
export function extractLoopVariables(mapInfo: MapExpressionInfo): string[] {
|
|
75
|
-
if (!mapInfo.isMap || !mapInfo.itemVariable) {
|
|
76
|
-
return []
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const vars = [mapInfo.itemVariable]
|
|
80
|
-
if (mapInfo.indexVariable) {
|
|
81
|
-
vars.push(mapInfo.indexVariable)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return vars
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Check if an expression references a loop variable
|
|
89
|
-
* Used to determine if an expression needs loop context
|
|
90
|
-
*/
|
|
91
|
-
export function referencesLoopVariable(exprCode: string, loopVars: string[]): boolean {
|
|
92
|
-
for (const loopVar of loopVars) {
|
|
93
|
-
// Match variable references: loopVar.property, loopVar, etc.
|
|
94
|
-
// Use word boundaries to avoid partial matches
|
|
95
|
-
const pattern = new RegExp(`\\b${loopVar}\\b`)
|
|
96
|
-
if (pattern.test(exprCode)) {
|
|
97
|
-
return true
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return false
|
|
101
|
-
}
|
|
102
|
-
|