@zenithbuild/compiler 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +30 -0
- package/dist/build-analyzer.d.ts +44 -0
- package/dist/build-analyzer.js +87 -0
- package/dist/bundler.d.ts +31 -0
- package/dist/bundler.js +86 -0
- package/dist/core/components/index.d.ts +9 -0
- package/dist/core/components/index.js +13 -0
- package/dist/core/config/index.d.ts +11 -0
- package/dist/core/config/index.js +10 -0
- package/dist/core/config/loader.d.ts +17 -0
- package/dist/core/config/loader.js +60 -0
- package/dist/core/config/types.d.ts +98 -0
- package/dist/core/config/types.js +32 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +6 -0
- package/dist/core/lifecycle/index.d.ts +16 -0
- package/dist/core/lifecycle/index.js +19 -0
- package/dist/core/lifecycle/zen-mount.d.ts +66 -0
- package/dist/core/lifecycle/zen-mount.js +151 -0
- package/dist/core/lifecycle/zen-unmount.d.ts +54 -0
- package/dist/core/lifecycle/zen-unmount.js +76 -0
- package/dist/core/plugins/bridge.d.ts +116 -0
- package/dist/core/plugins/bridge.js +121 -0
- package/dist/core/plugins/index.d.ts +6 -0
- package/dist/core/plugins/index.js +6 -0
- package/dist/core/plugins/registry.d.ts +67 -0
- package/dist/core/plugins/registry.js +113 -0
- package/dist/core/reactivity/index.d.ts +30 -0
- package/dist/core/reactivity/index.js +33 -0
- package/dist/core/reactivity/tracking.d.ts +74 -0
- package/dist/core/reactivity/tracking.js +136 -0
- package/dist/core/reactivity/zen-batch.d.ts +45 -0
- package/dist/core/reactivity/zen-batch.js +54 -0
- package/dist/core/reactivity/zen-effect.d.ts +48 -0
- package/dist/core/reactivity/zen-effect.js +98 -0
- package/dist/core/reactivity/zen-memo.d.ts +43 -0
- package/dist/core/reactivity/zen-memo.js +100 -0
- package/dist/core/reactivity/zen-ref.d.ts +44 -0
- package/dist/core/reactivity/zen-ref.js +34 -0
- package/dist/core/reactivity/zen-signal.d.ts +48 -0
- package/dist/core/reactivity/zen-signal.js +84 -0
- package/dist/core/reactivity/zen-state.d.ts +35 -0
- package/dist/core/reactivity/zen-state.js +147 -0
- package/dist/core/reactivity/zen-untrack.d.ts +38 -0
- package/dist/core/reactivity/zen-untrack.js +41 -0
- package/dist/css/index.d.ts +73 -0
- package/dist/css/index.js +246 -0
- package/dist/discovery/componentDiscovery.d.ts +42 -0
- package/dist/discovery/componentDiscovery.js +56 -0
- package/dist/discovery/layouts.d.ts +13 -0
- package/dist/discovery/layouts.js +41 -0
- package/dist/errors/compilerError.d.ts +31 -0
- package/dist/errors/compilerError.js +51 -0
- package/dist/finalize/finalizeOutput.d.ts +32 -0
- package/dist/finalize/finalizeOutput.js +62 -0
- package/dist/finalize/generateFinalBundle.d.ts +24 -0
- package/dist/finalize/generateFinalBundle.js +68 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +51 -0
- package/dist/ir/types.d.ts +181 -0
- package/dist/ir/types.js +8 -0
- package/dist/output/types.d.ts +30 -0
- package/dist/output/types.js +6 -0
- package/dist/parse/detectMapExpressions.d.ts +45 -0
- package/dist/parse/detectMapExpressions.js +77 -0
- package/dist/parse/parseScript.d.ts +8 -0
- package/dist/parse/parseScript.js +36 -0
- package/dist/parse/parseTemplate.d.ts +11 -0
- package/dist/parse/parseTemplate.js +487 -0
- package/dist/parse/parseZenFile.d.ts +11 -0
- package/dist/parse/parseZenFile.js +50 -0
- package/dist/parse/scriptAnalysis.d.ts +25 -0
- package/dist/parse/scriptAnalysis.js +60 -0
- package/dist/parse/trackLoopContext.d.ts +20 -0
- package/dist/parse/trackLoopContext.js +62 -0
- package/dist/parseZenFile.d.ts +10 -0
- package/dist/parseZenFile.js +55 -0
- package/dist/runtime/analyzeAndEmit.d.ts +20 -0
- package/dist/runtime/analyzeAndEmit.js +70 -0
- package/dist/runtime/build.d.ts +6 -0
- package/dist/runtime/build.js +13 -0
- package/dist/runtime/bundle-generator.d.ts +27 -0
- package/dist/runtime/bundle-generator.js +1263 -0
- package/dist/runtime/client-runtime.d.ts +41 -0
- package/dist/runtime/client-runtime.js +397 -0
- package/dist/runtime/dataExposure.d.ts +52 -0
- package/dist/runtime/dataExposure.js +227 -0
- package/dist/runtime/generateDOM.d.ts +21 -0
- package/dist/runtime/generateDOM.js +194 -0
- package/dist/runtime/generateHydrationBundle.d.ts +15 -0
- package/dist/runtime/generateHydrationBundle.js +399 -0
- package/dist/runtime/hydration.d.ts +53 -0
- package/dist/runtime/hydration.js +271 -0
- package/dist/runtime/navigation.d.ts +58 -0
- package/dist/runtime/navigation.js +372 -0
- package/dist/runtime/serve.d.ts +13 -0
- package/dist/runtime/serve.js +76 -0
- package/dist/runtime/thinRuntime.d.ts +23 -0
- package/dist/runtime/thinRuntime.js +158 -0
- package/dist/runtime/transformIR.d.ts +19 -0
- package/dist/runtime/transformIR.js +285 -0
- package/dist/runtime/wrapExpression.d.ts +24 -0
- package/dist/runtime/wrapExpression.js +76 -0
- package/dist/runtime/wrapExpressionWithLoop.d.ts +17 -0
- package/dist/runtime/wrapExpressionWithLoop.js +75 -0
- package/dist/spa-build.d.ts +26 -0
- package/dist/spa-build.js +866 -0
- package/dist/ssg-build.d.ts +32 -0
- package/dist/ssg-build.js +408 -0
- package/dist/test/analyze-emit.test.d.ts +1 -0
- package/dist/test/analyze-emit.test.js +88 -0
- package/dist/test/bundler-contract.test.d.ts +1 -0
- package/dist/test/bundler-contract.test.js +137 -0
- package/dist/test/compiler-authority.test.d.ts +1 -0
- package/dist/test/compiler-authority.test.js +90 -0
- package/dist/test/component-instance-test.d.ts +1 -0
- package/dist/test/component-instance-test.js +115 -0
- package/dist/test/error-native-bridge.test.d.ts +1 -0
- package/dist/test/error-native-bridge.test.js +51 -0
- package/dist/test/error-serialization.test.d.ts +1 -0
- package/dist/test/error-serialization.test.js +38 -0
- package/dist/test/macro-inlining.test.d.ts +1 -0
- package/dist/test/macro-inlining.test.js +178 -0
- package/dist/test/validate-test.d.ts +6 -0
- package/dist/test/validate-test.js +95 -0
- package/dist/transform/classifyExpression.d.ts +46 -0
- package/dist/transform/classifyExpression.js +354 -0
- package/dist/transform/componentResolver.d.ts +15 -0
- package/dist/transform/componentResolver.js +30 -0
- package/dist/transform/expressionTransformer.d.ts +19 -0
- package/dist/transform/expressionTransformer.js +333 -0
- package/dist/transform/fragmentLowering.d.ts +25 -0
- package/dist/transform/fragmentLowering.js +468 -0
- package/dist/transform/layoutProcessor.d.ts +5 -0
- package/dist/transform/layoutProcessor.js +34 -0
- package/dist/transform/transformTemplate.d.ts +11 -0
- package/dist/transform/transformTemplate.js +33 -0
- package/dist/validate/invariants.d.ts +23 -0
- package/dist/validate/invariants.js +55 -0
- package/native/compiler-native/compiler-native.node +0 -0
- package/native/compiler-native/index.d.ts +113 -0
- package/native/compiler-native/index.js +19 -0
- package/native/compiler-native/package.json +19 -0
- package/package.json +49 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit Data Exposure Analysis
|
|
3
|
+
*
|
|
4
|
+
* Phase 6: Analyzes expressions to detect data dependencies and ensure
|
|
5
|
+
* all data references are explicit (loader, props, stores) rather than implicit globals
|
|
6
|
+
*/
|
|
7
|
+
import { CompilerError } from '../errors/compilerError';
|
|
8
|
+
import { transformExpressionJSX } from '../transform/expressionTransformer';
|
|
9
|
+
/**
|
|
10
|
+
* Analyze an expression to detect its data dependencies
|
|
11
|
+
*
|
|
12
|
+
* This is a simple heuristic-based analyzer that looks for patterns like:
|
|
13
|
+
* - user.name, user.email → loader data
|
|
14
|
+
* - props.title, props.showWelcome → props
|
|
15
|
+
* - stores.cart, stores.notifications → stores
|
|
16
|
+
* - count, isLoading → state (top-level properties)
|
|
17
|
+
*/
|
|
18
|
+
export function analyzeExpressionDependencies(expr, declaredLoaderProps = [], declaredProps = [], declaredStores = []) {
|
|
19
|
+
const { id, code } = expr;
|
|
20
|
+
const dependencies = {
|
|
21
|
+
expressionId: id,
|
|
22
|
+
usesLoaderData: false,
|
|
23
|
+
usesProps: false,
|
|
24
|
+
usesStores: false,
|
|
25
|
+
usesState: false,
|
|
26
|
+
loaderProperties: [],
|
|
27
|
+
propNames: [],
|
|
28
|
+
storeNames: [],
|
|
29
|
+
stateProperties: []
|
|
30
|
+
};
|
|
31
|
+
// Simple pattern matching (for Phase 6 - can be enhanced with proper AST parsing later)
|
|
32
|
+
// Check for loader data references (loaderData.property or direct property access)
|
|
33
|
+
// We assume properties not starting with props/stores/state are loader data
|
|
34
|
+
const loaderPattern = /\b(loaderData\.(\w+(?:\.\w+)*)|(?<!props\.|stores\.|state\.)(\w+)\.(\w+))/g;
|
|
35
|
+
let match;
|
|
36
|
+
// Check for explicit loaderData references
|
|
37
|
+
if (/loaderData\./.test(code)) {
|
|
38
|
+
dependencies.usesLoaderData = true;
|
|
39
|
+
while ((match = loaderPattern.exec(code)) !== null) {
|
|
40
|
+
if (match[1]?.startsWith('loaderData.')) {
|
|
41
|
+
const propPath = match[1].replace('loaderData.', '');
|
|
42
|
+
if (!dependencies.loaderProperties.includes(propPath)) {
|
|
43
|
+
dependencies.loaderProperties.push(propPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Check for props references
|
|
49
|
+
const propsPattern = /\bprops\.(\w+)(?:\.(\w+))*/g;
|
|
50
|
+
if (/props\./.test(code)) {
|
|
51
|
+
dependencies.usesProps = true;
|
|
52
|
+
while ((match = propsPattern.exec(code)) !== null) {
|
|
53
|
+
const propName = match[1];
|
|
54
|
+
if (propName && !dependencies.propNames.includes(propName)) {
|
|
55
|
+
dependencies.propNames.push(propName);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Check for stores references
|
|
60
|
+
const storesPattern = /\bstores\.(\w+)(?:\.(\w+))*/g;
|
|
61
|
+
if (/stores\./.test(code)) {
|
|
62
|
+
dependencies.usesStores = true;
|
|
63
|
+
while ((match = storesPattern.exec(code)) !== null) {
|
|
64
|
+
const storeName = match[1];
|
|
65
|
+
if (storeName && !dependencies.storeNames.includes(storeName)) {
|
|
66
|
+
dependencies.storeNames.push(storeName);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Check for state references (top-level properties)
|
|
71
|
+
// Simple identifiers that aren't part of props/stores/loaderData paths
|
|
72
|
+
const identifierPattern = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\b/g;
|
|
73
|
+
const reserved = ['props', 'stores', 'loaderData', 'state', 'true', 'false', 'null', 'undefined', 'this', 'window'];
|
|
74
|
+
const identifiers = new Set();
|
|
75
|
+
while ((match = identifierPattern.exec(code)) !== null) {
|
|
76
|
+
const ident = match[1];
|
|
77
|
+
if (ident && !reserved.includes(ident) && !ident.includes('.')) {
|
|
78
|
+
identifiers.add(ident);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// If we have identifiers, check if they are props or state
|
|
82
|
+
if (identifiers.size > 0) {
|
|
83
|
+
const propIdents = [];
|
|
84
|
+
const stateIdents = [];
|
|
85
|
+
for (const ident of identifiers) {
|
|
86
|
+
if (declaredProps.includes(ident)) {
|
|
87
|
+
propIdents.push(ident);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
stateIdents.push(ident);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (propIdents.length > 0) {
|
|
94
|
+
dependencies.usesProps = true;
|
|
95
|
+
dependencies.propNames = [...new Set([...dependencies.propNames, ...propIdents])];
|
|
96
|
+
}
|
|
97
|
+
if (stateIdents.length > 0) {
|
|
98
|
+
dependencies.usesState = true;
|
|
99
|
+
dependencies.stateProperties = Array.from(new Set([...dependencies.stateProperties, ...stateIdents]));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return dependencies;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Validate that all referenced data exists
|
|
106
|
+
*/
|
|
107
|
+
export function validateDataDependencies(dependencies, filePath, declaredLoaderProps = [], declaredProps = [], declaredStores = []) {
|
|
108
|
+
const errors = [];
|
|
109
|
+
// Validate loader data properties
|
|
110
|
+
if (dependencies.usesLoaderData && dependencies.loaderProperties.length > 0) {
|
|
111
|
+
// For Phase 6, we'll allow any loader property (can be enhanced with type checking later)
|
|
112
|
+
// Just warn if property path is suspicious
|
|
113
|
+
for (const prop of dependencies.loaderProperties) {
|
|
114
|
+
if (!/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/.test(prop)) {
|
|
115
|
+
errors.push(new CompilerError(`Invalid loader data property reference: ${prop}`, filePath, 0, 0));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Validate props
|
|
120
|
+
if (dependencies.usesProps && dependencies.propNames.length > 0) {
|
|
121
|
+
for (const propName of dependencies.propNames) {
|
|
122
|
+
if (declaredProps.length > 0 && !declaredProps.includes(propName)) {
|
|
123
|
+
// This is a warning, not an error - props might be passed at runtime
|
|
124
|
+
console.warn(`[Zenith] Prop "${propName}" referenced but not declared in component`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Validate stores
|
|
129
|
+
if (dependencies.usesStores && dependencies.storeNames.length > 0) {
|
|
130
|
+
for (const storeName of dependencies.storeNames) {
|
|
131
|
+
if (declaredStores.length > 0 && !declaredStores.includes(storeName)) {
|
|
132
|
+
errors.push(new CompilerError(`Store "${storeName}" referenced but not imported or declared`, filePath, 0, 0));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (errors.length > 0) {
|
|
137
|
+
throw errors[0]; // Throw first error (can be enhanced to collect all)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Transform expression code to use explicit data arguments
|
|
142
|
+
*
|
|
143
|
+
* Converts patterns like:
|
|
144
|
+
* - user.name → loaderData.user.name
|
|
145
|
+
* - title → props.title (if declared as prop)
|
|
146
|
+
* - cart.items → stores.cart.items
|
|
147
|
+
*/
|
|
148
|
+
export function transformExpressionCode(code, dependencies, declaredProps = []) {
|
|
149
|
+
let transformed = code;
|
|
150
|
+
// For Phase 6, we keep the code as-is but ensure expressions
|
|
151
|
+
// receive the right arguments. The actual transformation happens
|
|
152
|
+
// in the expression wrapper function signature.
|
|
153
|
+
// However, if the code references properties directly (without loaderData/props/stores prefix),
|
|
154
|
+
// we need to assume they're state properties (backwards compatibility)
|
|
155
|
+
return transformed;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Generate expression wrapper with explicit data arguments
|
|
159
|
+
*/
|
|
160
|
+
export function generateExplicitExpressionWrapper(expr, dependencies) {
|
|
161
|
+
const { id, code } = expr;
|
|
162
|
+
// Build function signature based on dependencies
|
|
163
|
+
const params = ['state'];
|
|
164
|
+
if (dependencies.usesLoaderData) {
|
|
165
|
+
params.push('loaderData');
|
|
166
|
+
}
|
|
167
|
+
if (dependencies.usesProps) {
|
|
168
|
+
params.push('props');
|
|
169
|
+
}
|
|
170
|
+
if (dependencies.usesStores) {
|
|
171
|
+
params.push('stores');
|
|
172
|
+
}
|
|
173
|
+
const paramList = params.join(', ');
|
|
174
|
+
// Build evaluation context
|
|
175
|
+
const contextParts = [];
|
|
176
|
+
if (dependencies.usesLoaderData) {
|
|
177
|
+
contextParts.push('loaderData');
|
|
178
|
+
}
|
|
179
|
+
if (dependencies.usesProps) {
|
|
180
|
+
contextParts.push('props');
|
|
181
|
+
}
|
|
182
|
+
if (dependencies.usesStores) {
|
|
183
|
+
contextParts.push('stores');
|
|
184
|
+
}
|
|
185
|
+
if (dependencies.usesState) {
|
|
186
|
+
contextParts.push('state');
|
|
187
|
+
}
|
|
188
|
+
// Create merged context for 'with' statement
|
|
189
|
+
const contextCode = contextParts.length > 0
|
|
190
|
+
? `const __ctx = Object.assign({}, ${contextParts.join(', ')});\n with (__ctx) {`
|
|
191
|
+
: 'with (state) {';
|
|
192
|
+
// Escape the code for use in a single-line comment (replace newlines with spaces)
|
|
193
|
+
const commentCode = code.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ').substring(0, 100);
|
|
194
|
+
// JSON.stringify the code for error messages (properly escapes quotes, newlines, etc.)
|
|
195
|
+
const jsonEscapedCode = JSON.stringify(code);
|
|
196
|
+
// Transform JSX
|
|
197
|
+
const transformedCode = transformExpressionJSX(code);
|
|
198
|
+
return `
|
|
199
|
+
// Expression: ${commentCode}${code.length > 100 ? '...' : ''}
|
|
200
|
+
// Dependencies: ${JSON.stringify({
|
|
201
|
+
loaderData: dependencies.usesLoaderData,
|
|
202
|
+
props: dependencies.usesProps,
|
|
203
|
+
stores: dependencies.usesStores,
|
|
204
|
+
state: dependencies.usesState
|
|
205
|
+
})}
|
|
206
|
+
const ${id} = (${paramList}) => {
|
|
207
|
+
try {
|
|
208
|
+
${contextCode}
|
|
209
|
+
return ${transformedCode};
|
|
210
|
+
}
|
|
211
|
+
} catch (e) {
|
|
212
|
+
console.warn('[Zenith] Expression evaluation error:', ${jsonEscapedCode}, e);
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
};`;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Analyze all expressions in a template
|
|
219
|
+
*/
|
|
220
|
+
export function analyzeAllExpressions(expressions, filePath, declaredLoaderProps = [], declaredProps = [], declaredStores = []) {
|
|
221
|
+
const dependencies = expressions.map(expr => analyzeExpressionDependencies(expr, declaredLoaderProps, declaredProps, declaredStores));
|
|
222
|
+
// Validate all dependencies
|
|
223
|
+
for (const dep of dependencies) {
|
|
224
|
+
validateDataDependencies(dep, filePath, declaredLoaderProps, declaredProps, declaredStores);
|
|
225
|
+
}
|
|
226
|
+
return dependencies;
|
|
227
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM Generation
|
|
3
|
+
*
|
|
4
|
+
* Generates JavaScript code that creates DOM elements from template nodes
|
|
5
|
+
*/
|
|
6
|
+
import type { TemplateNode, ExpressionIR } from '../ir/types';
|
|
7
|
+
/**
|
|
8
|
+
* Generate DOM creation code from a template node
|
|
9
|
+
* Returns JavaScript code that creates and returns a DOM element or text node
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateDOMCode(node: TemplateNode, expressions: ExpressionIR[], indent?: string, varCounter?: {
|
|
12
|
+
count: number;
|
|
13
|
+
}): {
|
|
14
|
+
code: string;
|
|
15
|
+
varName: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Generate DOM creation code for multiple nodes
|
|
19
|
+
* Returns a function that creates DOM elements
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateDOMFunction(nodes: TemplateNode[], expressions: ExpressionIR[], functionName?: string): string;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM Generation
|
|
3
|
+
*
|
|
4
|
+
* Generates JavaScript code that creates DOM elements from template nodes
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Generate DOM creation code from a template node
|
|
8
|
+
* Returns JavaScript code that creates and returns a DOM element or text node
|
|
9
|
+
*/
|
|
10
|
+
export function generateDOMCode(node, expressions, indent = ' ', varCounter = { count: 0 }) {
|
|
11
|
+
const varName = `node_${varCounter.count++}`;
|
|
12
|
+
switch (node.type) {
|
|
13
|
+
case 'text': {
|
|
14
|
+
const textNode = node;
|
|
15
|
+
const escapedValue = JSON.stringify(textNode.value);
|
|
16
|
+
return {
|
|
17
|
+
code: `${indent}const ${varName} = document.createTextNode(${escapedValue});`,
|
|
18
|
+
varName
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
case 'expression': {
|
|
22
|
+
const exprNode = node;
|
|
23
|
+
const expr = expressions.find(e => e.id === exprNode.expression);
|
|
24
|
+
if (!expr) {
|
|
25
|
+
throw new Error(`Expression ${exprNode.expression} not found`);
|
|
26
|
+
}
|
|
27
|
+
// Create a span element to hold the expression result
|
|
28
|
+
return {
|
|
29
|
+
code: `${indent}const ${varName} = document.createElement('span');
|
|
30
|
+
${indent}${varName}.textContent = String(${expr.id}(state) ?? '');
|
|
31
|
+
${indent}${varName}.setAttribute('data-zen-expr', '${exprNode.expression}');`,
|
|
32
|
+
varName
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
case 'element': {
|
|
36
|
+
const elNode = node;
|
|
37
|
+
const tag = elNode.tag;
|
|
38
|
+
let code = `${indent}const ${varName} = document.createElement('${tag}');\n`;
|
|
39
|
+
// Handle attributes
|
|
40
|
+
for (const attr of elNode.attributes) {
|
|
41
|
+
if (typeof attr.value === 'string') {
|
|
42
|
+
// Static attribute
|
|
43
|
+
const escapedValue = JSON.stringify(attr.value);
|
|
44
|
+
code += `${indent}${varName}.setAttribute('${attr.name}', ${escapedValue});\n`;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Expression attribute
|
|
48
|
+
const expr = attr.value;
|
|
49
|
+
const attrName = attr.name === 'className' ? 'class' : attr.name;
|
|
50
|
+
// Handle special attributes
|
|
51
|
+
if (attrName === 'class' || attrName === 'className') {
|
|
52
|
+
code += `${indent}${varName}.className = String(${expr.id}(state) ?? '');\n`;
|
|
53
|
+
}
|
|
54
|
+
else if (attrName === 'style') {
|
|
55
|
+
code += `${indent}const styleValue_${varCounter.count} = ${expr.id}(state);
|
|
56
|
+
${indent}if (typeof styleValue_${varCounter.count} === 'string') {
|
|
57
|
+
${indent} ${varName}.style.cssText = styleValue_${varCounter.count};
|
|
58
|
+
${indent}}\n`;
|
|
59
|
+
}
|
|
60
|
+
else if (attrName.startsWith('on')) {
|
|
61
|
+
// Event handler - store handler name/id, will be bound later
|
|
62
|
+
const eventType = attrName.slice(2).toLowerCase(); // Remove 'on' prefix
|
|
63
|
+
const value = typeof attr.value === 'string' ? attr.value : attr.value.id;
|
|
64
|
+
code += `${indent}${varName}.setAttribute('data-zen-${eventType}', ${JSON.stringify(value)});\n`;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const tempVar = `attr_${varCounter.count++}`;
|
|
68
|
+
code += `${indent}const ${tempVar} = ${expr.id}(state);
|
|
69
|
+
${indent}if (${tempVar} != null && ${tempVar} !== false) {
|
|
70
|
+
${indent} ${varName}.setAttribute('${attrName}', String(${tempVar}));
|
|
71
|
+
${indent}}\n`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Handle children
|
|
76
|
+
if (elNode.children.length > 0) {
|
|
77
|
+
for (const child of elNode.children) {
|
|
78
|
+
const childResult = generateDOMCode(child, expressions, indent, varCounter);
|
|
79
|
+
code += `${childResult.code}\n`;
|
|
80
|
+
code += `${indent}${varName}.appendChild(${childResult.varName});\n`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { code, varName };
|
|
84
|
+
}
|
|
85
|
+
case 'component': {
|
|
86
|
+
// Components should be resolved before reaching DOM generation
|
|
87
|
+
// If we get here, it means component resolution failed
|
|
88
|
+
throw new Error(`[Zenith] Unresolved component: ${node.name}. Components must be resolved before DOM generation.`);
|
|
89
|
+
}
|
|
90
|
+
case 'conditional-fragment': {
|
|
91
|
+
// Conditional fragment: {condition ? <A /> : <B />}
|
|
92
|
+
// Both branches are precompiled, runtime toggles visibility
|
|
93
|
+
const condNode = node;
|
|
94
|
+
const containerVar = varName;
|
|
95
|
+
const conditionId = `cond_${varCounter.count++}`;
|
|
96
|
+
let code = `${indent}const ${containerVar} = document.createDocumentFragment();\n`;
|
|
97
|
+
code += `${indent}const ${conditionId}_result = (function() { with (state) { return ${condNode.condition}; } })();\n`;
|
|
98
|
+
// Generate consequent branch
|
|
99
|
+
code += `${indent}if (${conditionId}_result) {\n`;
|
|
100
|
+
for (const child of condNode.consequent) {
|
|
101
|
+
const childResult = generateDOMCode(child, expressions, indent + ' ', varCounter);
|
|
102
|
+
code += `${childResult.code}\n`;
|
|
103
|
+
code += `${indent} ${containerVar}.appendChild(${childResult.varName});\n`;
|
|
104
|
+
}
|
|
105
|
+
code += `${indent}} else {\n`;
|
|
106
|
+
// Generate alternate branch
|
|
107
|
+
for (const child of condNode.alternate) {
|
|
108
|
+
const childResult = generateDOMCode(child, expressions, indent + ' ', varCounter);
|
|
109
|
+
code += `${childResult.code}\n`;
|
|
110
|
+
code += `${indent} ${containerVar}.appendChild(${childResult.varName});\n`;
|
|
111
|
+
}
|
|
112
|
+
code += `${indent}}\n`;
|
|
113
|
+
return { code, varName: containerVar };
|
|
114
|
+
}
|
|
115
|
+
case 'optional-fragment': {
|
|
116
|
+
// Optional fragment: {condition && <A />}
|
|
117
|
+
// Fragment is precompiled, runtime mounts/unmounts based on condition
|
|
118
|
+
const optNode = node;
|
|
119
|
+
const containerVar = varName;
|
|
120
|
+
const conditionId = `opt_${varCounter.count++}`;
|
|
121
|
+
let code = `${indent}const ${containerVar} = document.createDocumentFragment();\n`;
|
|
122
|
+
code += `${indent}const ${conditionId}_result = (function() { with (state) { return ${optNode.condition}; } })();\n`;
|
|
123
|
+
code += `${indent}if (${conditionId}_result) {\n`;
|
|
124
|
+
for (const child of optNode.fragment) {
|
|
125
|
+
const childResult = generateDOMCode(child, expressions, indent + ' ', varCounter);
|
|
126
|
+
code += `${childResult.code}\n`;
|
|
127
|
+
code += `${indent} ${containerVar}.appendChild(${childResult.varName});\n`;
|
|
128
|
+
}
|
|
129
|
+
code += `${indent}}\n`;
|
|
130
|
+
return { code, varName: containerVar };
|
|
131
|
+
}
|
|
132
|
+
case 'loop-fragment': {
|
|
133
|
+
// Loop fragment: {items.map(item => <li>...</li>)}
|
|
134
|
+
// Body is precompiled once, instantiated per item at runtime
|
|
135
|
+
const loopNode = node;
|
|
136
|
+
const containerVar = varName;
|
|
137
|
+
const loopId = `loop_${varCounter.count++}`;
|
|
138
|
+
let code = `${indent}const ${containerVar} = document.createDocumentFragment();\n`;
|
|
139
|
+
code += `${indent}const ${loopId}_items = (function() { with (state) { return ${loopNode.source}; } })() || [];\n`;
|
|
140
|
+
// Loop parameters
|
|
141
|
+
const itemVar = loopNode.itemVar;
|
|
142
|
+
const indexVar = loopNode.indexVar || `${loopId}_idx`;
|
|
143
|
+
code += `${indent}${loopId}_items.forEach(function(${itemVar}, ${indexVar}) {\n`;
|
|
144
|
+
// Generate loop body with loop context variables in scope
|
|
145
|
+
for (const child of loopNode.body) {
|
|
146
|
+
const childResult = generateDOMCode(child, expressions, indent + ' ', varCounter);
|
|
147
|
+
// Inject loop variables into the child code
|
|
148
|
+
let childCode = childResult.code;
|
|
149
|
+
code += `${childCode}\n`;
|
|
150
|
+
code += `${indent} ${containerVar}.appendChild(${childResult.varName});\n`;
|
|
151
|
+
}
|
|
152
|
+
code += `${indent}});\n`;
|
|
153
|
+
return { code, varName: containerVar };
|
|
154
|
+
}
|
|
155
|
+
default: {
|
|
156
|
+
throw new Error(`[Zenith] Unknown node type: ${node.type}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Generate DOM creation code for multiple nodes
|
|
162
|
+
* Returns a function that creates DOM elements
|
|
163
|
+
*/
|
|
164
|
+
export function generateDOMFunction(nodes, expressions, functionName = 'renderTemplate') {
|
|
165
|
+
if (nodes.length === 0) {
|
|
166
|
+
return `function ${functionName}(state) {
|
|
167
|
+
const fragment = document.createDocumentFragment();
|
|
168
|
+
return fragment;
|
|
169
|
+
}`;
|
|
170
|
+
}
|
|
171
|
+
const varCounter = { count: 0 };
|
|
172
|
+
let code = `function ${functionName}(state) {
|
|
173
|
+
`;
|
|
174
|
+
if (nodes.length === 1) {
|
|
175
|
+
const node = nodes[0];
|
|
176
|
+
if (!node) {
|
|
177
|
+
throw new Error('Empty nodes array passed to generateDOMFunction');
|
|
178
|
+
}
|
|
179
|
+
const result = generateDOMCode(node, expressions, ' ', varCounter);
|
|
180
|
+
code += result.code;
|
|
181
|
+
code += `\n return ${result.varName};\n}`;
|
|
182
|
+
return code;
|
|
183
|
+
}
|
|
184
|
+
// Multiple nodes - create a fragment
|
|
185
|
+
code += ` const fragment = document.createDocumentFragment();\n`;
|
|
186
|
+
for (const node of nodes) {
|
|
187
|
+
const result = generateDOMCode(node, expressions, ' ', varCounter);
|
|
188
|
+
code += `${result.code}\n`;
|
|
189
|
+
code += ` fragment.appendChild(${result.varName});\n`;
|
|
190
|
+
}
|
|
191
|
+
code += ` return fragment;
|
|
192
|
+
}`;
|
|
193
|
+
return code;
|
|
194
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate Hydration Bundle
|
|
3
|
+
*
|
|
4
|
+
* Phase 5: Generates the complete runtime bundle including expressions and hydration code
|
|
5
|
+
*/
|
|
6
|
+
import type { ExpressionIR } from '../ir/types';
|
|
7
|
+
/**
|
|
8
|
+
* Generate the hydration runtime code as a string
|
|
9
|
+
* This is the browser-side runtime that hydrates DOM placeholders
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateHydrationRuntime(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Generate expression registry initialization code
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateExpressionRegistry(expressions: ExpressionIR[]): string;
|