@zenithbuild/core 0.1.0
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/.eslintignore +15 -0
- package/.gitattributes +2 -0
- package/.github/ISSUE_TEMPLATE/compiler-errors-for-invalid-state-declarations.md +25 -0
- package/.github/ISSUE_TEMPLATE/new_ticket.yaml +34 -0
- package/.github/pull_request_template.md +15 -0
- package/.github/workflows/discord-changelog.yml +141 -0
- package/.github/workflows/discord-notify.yml +242 -0
- package/.github/workflows/discord-version.yml +195 -0
- package/.prettierignore +13 -0
- package/.prettierrc +21 -0
- package/.zen.d.ts +15 -0
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/app/components/Button.zen +46 -0
- package/app/components/Link.zen +11 -0
- package/app/favicon.ico +0 -0
- package/app/layouts/Main.zen +59 -0
- package/app/pages/about.zen +23 -0
- package/app/pages/blog/[id].zen +53 -0
- package/app/pages/blog/index.zen +32 -0
- package/app/pages/dynamic-dx.zen +712 -0
- package/app/pages/dynamic-primitives.zen +453 -0
- package/app/pages/index.zen +154 -0
- package/app/pages/navigation-demo.zen +229 -0
- package/app/pages/posts/[...slug].zen +61 -0
- package/app/pages/primitives-demo.zen +273 -0
- package/assets/logos/0E3B5DDD-605C-4839-BB2E-DFCA8ADC9604.PNG +0 -0
- package/assets/logos/760971E5-79A1-44F9-90B9-925DF30F4278.PNG +0 -0
- package/assets/logos/8A06ED80-9ED2-4689-BCBD-13B2E95EE8E4.JPG +0 -0
- package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.PNG +0 -0
- package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.svg +601 -0
- package/assets/logos/README.md +54 -0
- package/assets/logos/zen.icns +0 -0
- package/bun.lock +39 -0
- package/compiler/README.md +380 -0
- package/compiler/errors/compilerError.ts +24 -0
- package/compiler/finalize/finalizeOutput.ts +163 -0
- package/compiler/finalize/generateFinalBundle.ts +82 -0
- package/compiler/index.ts +44 -0
- package/compiler/ir/types.ts +83 -0
- package/compiler/legacy/binding.ts +254 -0
- package/compiler/legacy/bindings.ts +338 -0
- package/compiler/legacy/component-process.ts +1208 -0
- package/compiler/legacy/component.ts +301 -0
- package/compiler/legacy/event.ts +50 -0
- package/compiler/legacy/expression.ts +1149 -0
- package/compiler/legacy/mutation.ts +280 -0
- package/compiler/legacy/parse.ts +299 -0
- package/compiler/legacy/split.ts +608 -0
- package/compiler/legacy/types.ts +32 -0
- package/compiler/output/types.ts +34 -0
- package/compiler/parse/detectMapExpressions.ts +102 -0
- package/compiler/parse/parseScript.ts +22 -0
- package/compiler/parse/parseTemplate.ts +425 -0
- package/compiler/parse/parseZenFile.ts +66 -0
- package/compiler/parse/trackLoopContext.ts +82 -0
- package/compiler/runtime/dataExposure.ts +291 -0
- package/compiler/runtime/generateDOM.ts +144 -0
- package/compiler/runtime/generateHydrationBundle.ts +383 -0
- package/compiler/runtime/hydration.ts +309 -0
- package/compiler/runtime/navigation.ts +432 -0
- package/compiler/runtime/thinRuntime.ts +160 -0
- package/compiler/runtime/transformIR.ts +256 -0
- package/compiler/runtime/wrapExpression.ts +84 -0
- package/compiler/runtime/wrapExpressionWithLoop.ts +77 -0
- package/compiler/spa-build.ts +1000 -0
- package/compiler/test/validate-test.ts +104 -0
- package/compiler/transform/generateBindings.ts +47 -0
- package/compiler/transform/generateHTML.ts +28 -0
- package/compiler/transform/transformNode.ts +126 -0
- package/compiler/transform/transformTemplate.ts +38 -0
- package/compiler/validate/validateExpressions.ts +168 -0
- package/core/index.ts +135 -0
- package/core/lifecycle/index.ts +49 -0
- package/core/lifecycle/zen-mount.ts +182 -0
- package/core/lifecycle/zen-unmount.ts +88 -0
- package/core/reactivity/index.ts +54 -0
- package/core/reactivity/tracking.ts +167 -0
- package/core/reactivity/zen-batch.ts +57 -0
- package/core/reactivity/zen-effect.ts +139 -0
- package/core/reactivity/zen-memo.ts +146 -0
- package/core/reactivity/zen-ref.ts +52 -0
- package/core/reactivity/zen-signal.ts +121 -0
- package/core/reactivity/zen-state.ts +180 -0
- package/core/reactivity/zen-untrack.ts +44 -0
- package/docs/COMMENTS.md +111 -0
- package/docs/COMMITS.md +36 -0
- package/docs/CONTRIBUTING.md +116 -0
- package/docs/STYLEGUIDE.md +62 -0
- package/package.json +44 -0
- package/router/index.ts +76 -0
- package/router/manifest.ts +314 -0
- package/router/navigation/ZenLink.zen +231 -0
- package/router/navigation/index.ts +78 -0
- package/router/navigation/zen-link.ts +584 -0
- package/router/runtime.ts +458 -0
- package/router/types.ts +168 -0
- package/runtime/build.ts +17 -0
- package/runtime/serve.ts +93 -0
- package/scripts/webhook-proxy.ts +213 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zenith Compiler
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: Parse & Extract
|
|
5
|
+
* Phase 2: Transform IR → Static HTML + Runtime Bindings
|
|
6
|
+
* Phase 8/9/10: Finalize Output with Validation
|
|
7
|
+
*
|
|
8
|
+
* This compiler observes .zen files, extracts their structure,
|
|
9
|
+
* transforms them into static HTML with explicit bindings,
|
|
10
|
+
* and validates/finalizes output for browser execution.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { parseZenFile } from './parse/parseZenFile'
|
|
14
|
+
import { transformTemplate } from './transform/transformTemplate'
|
|
15
|
+
import { finalizeOutputOrThrow } from './finalize/finalizeOutput'
|
|
16
|
+
import type { ZenIR } from './ir/types'
|
|
17
|
+
import type { CompiledTemplate } from './output/types'
|
|
18
|
+
import type { FinalizedOutput } from './finalize/finalizeOutput'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Compile a .zen file into IR and CompiledTemplate
|
|
22
|
+
*
|
|
23
|
+
* Phase 1: Parses and extracts structure
|
|
24
|
+
* Phase 2: Transforms IR into static HTML with bindings
|
|
25
|
+
* Phase 8/9/10: Validates and finalizes output
|
|
26
|
+
*/
|
|
27
|
+
export function compileZen(filePath: string): {
|
|
28
|
+
ir: ZenIR
|
|
29
|
+
compiled: CompiledTemplate
|
|
30
|
+
finalized?: FinalizedOutput
|
|
31
|
+
} {
|
|
32
|
+
const ir = parseZenFile(filePath)
|
|
33
|
+
const compiled = transformTemplate(ir)
|
|
34
|
+
|
|
35
|
+
// Phase 8/9/10: Finalize output with validation
|
|
36
|
+
// This ensures build fails on invalid expressions
|
|
37
|
+
try {
|
|
38
|
+
const finalized = finalizeOutputOrThrow(ir, compiled)
|
|
39
|
+
return { ir, compiled, finalized }
|
|
40
|
+
} catch (error: any) {
|
|
41
|
+
// Re-throw with context
|
|
42
|
+
throw new Error(`Failed to finalize output for ${filePath}:\n${error.message}`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
export type ZenIR = {
|
|
10
|
+
filePath: string
|
|
11
|
+
template: TemplateIR
|
|
12
|
+
script: ScriptIR | null
|
|
13
|
+
styles: StyleIR[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type TemplateIR = {
|
|
17
|
+
raw: string
|
|
18
|
+
nodes: TemplateNode[]
|
|
19
|
+
expressions: ExpressionIR[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type TemplateNode =
|
|
23
|
+
| ElementNode
|
|
24
|
+
| TextNode
|
|
25
|
+
| ExpressionNode
|
|
26
|
+
|
|
27
|
+
export type ElementNode = {
|
|
28
|
+
type: 'element'
|
|
29
|
+
tag: string
|
|
30
|
+
attributes: AttributeIR[]
|
|
31
|
+
children: TemplateNode[]
|
|
32
|
+
location: SourceLocation
|
|
33
|
+
loopContext?: LoopContext // Phase 7: Inherited loop context from parent map expressions
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type TextNode = {
|
|
37
|
+
type: 'text'
|
|
38
|
+
value: string
|
|
39
|
+
location: SourceLocation
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ExpressionNode = {
|
|
43
|
+
type: 'expression'
|
|
44
|
+
expression: string
|
|
45
|
+
location: SourceLocation
|
|
46
|
+
loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type AttributeIR = {
|
|
50
|
+
name: string
|
|
51
|
+
value: string | ExpressionIR
|
|
52
|
+
location: SourceLocation
|
|
53
|
+
loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Loop context for expressions inside map iterations
|
|
58
|
+
* Phase 7: Tracks loop variables (e.g., todo, index) for expressions inside .map() calls
|
|
59
|
+
*/
|
|
60
|
+
export type LoopContext = {
|
|
61
|
+
variables: string[] // e.g., ['todo', 'index'] for todoItems.map((todo, index) => ...)
|
|
62
|
+
mapSource?: string // The array being mapped, e.g., 'todoItems'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type ExpressionIR = {
|
|
66
|
+
id: string
|
|
67
|
+
code: string
|
|
68
|
+
location: SourceLocation
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type ScriptIR = {
|
|
72
|
+
raw: string
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type StyleIR = {
|
|
76
|
+
raw: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type SourceLocation = {
|
|
80
|
+
line: number
|
|
81
|
+
column: number
|
|
82
|
+
}
|
|
83
|
+
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// compiler/binding.ts
|
|
2
|
+
// Phase 2: Reactive text bindings runtime generator
|
|
3
|
+
// Generates code to update DOM when state values change
|
|
4
|
+
// Extended to support component instance-scoped state
|
|
5
|
+
|
|
6
|
+
import type { StateBinding } from "./types"
|
|
7
|
+
|
|
8
|
+
// Extract instance ID and base state name from instance-scoped state name
|
|
9
|
+
// e.g., "__zen_comp_0_clicks" -> { instanceId: "comp-0", baseState: "clicks" }
|
|
10
|
+
function parseInstanceState(stateName: string): { instanceId: string; baseState: string } | null {
|
|
11
|
+
const match = stateName.match(/^__zen_comp_(\d+)_(.+)$/);
|
|
12
|
+
if (match) {
|
|
13
|
+
const instanceNum = match[1];
|
|
14
|
+
const baseState = match[2];
|
|
15
|
+
return { instanceId: `comp-${instanceNum}`, baseState };
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function generateBindingRuntime(
|
|
21
|
+
stateBindings: StateBinding[],
|
|
22
|
+
stateDeclarations: Map<string, string>
|
|
23
|
+
): string {
|
|
24
|
+
if (stateBindings.length === 0 && stateDeclarations.size === 0) {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const stateNames = Array.from(stateDeclarations.keys());
|
|
29
|
+
|
|
30
|
+
// Separate global state and instance-scoped state
|
|
31
|
+
const globalStates = new Set<string>();
|
|
32
|
+
const instanceStates = new Map<string, Map<string, string[]>>(); // instanceId -> baseState -> fullStateNames
|
|
33
|
+
|
|
34
|
+
for (const stateName of stateNames) {
|
|
35
|
+
const instanceInfo = parseInstanceState(stateName);
|
|
36
|
+
if (instanceInfo) {
|
|
37
|
+
if (!instanceStates.has(instanceInfo.instanceId)) {
|
|
38
|
+
instanceStates.set(instanceInfo.instanceId, new Map());
|
|
39
|
+
}
|
|
40
|
+
const instanceMap = instanceStates.get(instanceInfo.instanceId)!;
|
|
41
|
+
if (!instanceMap.has(instanceInfo.baseState)) {
|
|
42
|
+
instanceMap.set(instanceInfo.baseState, []);
|
|
43
|
+
}
|
|
44
|
+
instanceMap.get(instanceInfo.baseState)!.push(stateName);
|
|
45
|
+
} else {
|
|
46
|
+
globalStates.add(stateName);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Generate binding update map - collect all nodes for each state
|
|
51
|
+
// Order is preserved: bindings are processed in the order they appear in stateBindings array
|
|
52
|
+
// (which matches DOM traversal order from compilation)
|
|
53
|
+
const bindingMapEntries: string[] = [];
|
|
54
|
+
const bindingMap = new Map<string, string[]>();
|
|
55
|
+
|
|
56
|
+
// Iterate over stateBindings array to preserve compile-time order
|
|
57
|
+
// Maps preserve insertion order, so this maintains deterministic ordering
|
|
58
|
+
for (const stateBinding of stateBindings) {
|
|
59
|
+
if (!bindingMap.has(stateBinding.stateName)) {
|
|
60
|
+
bindingMap.set(stateBinding.stateName, []);
|
|
61
|
+
}
|
|
62
|
+
const selectors = bindingMap.get(stateBinding.stateName)!;
|
|
63
|
+
// Push bindings in the order they appear in stateBinding.bindings array
|
|
64
|
+
// This preserves the DOM traversal order from compilation
|
|
65
|
+
for (const binding of stateBinding.bindings) {
|
|
66
|
+
const bindId = `bind-${binding.nodeIndex}`;
|
|
67
|
+
|
|
68
|
+
// Check if this is an instance-scoped binding
|
|
69
|
+
const instanceInfo = parseInstanceState(stateBinding.stateName);
|
|
70
|
+
if (instanceInfo) {
|
|
71
|
+
// Scope selector to component instance root
|
|
72
|
+
selectors.push(`[data-zen-instance="${instanceInfo.instanceId}"] span[data-zen-bind="${stateBinding.stateName}"][data-zen-bind-id="${bindId}"]`);
|
|
73
|
+
} else {
|
|
74
|
+
// Global binding
|
|
75
|
+
selectors.push(`span[data-zen-bind="${stateBinding.stateName}"][data-zen-bind-id="${bindId}"]`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Generate update functions for each binding
|
|
81
|
+
// Each function captures a DOM node reference directly
|
|
82
|
+
// Map.entries() preserves insertion order, maintaining compile-time binding order
|
|
83
|
+
for (const [stateName, selectors] of bindingMap.entries()) {
|
|
84
|
+
if (selectors.length > 0) {
|
|
85
|
+
// Generate an array of update functions, each capturing a node reference
|
|
86
|
+
const updateFunctions = selectors.map((selector, index) => {
|
|
87
|
+
const escapedSelector = selector.replace(/"/g, '\\"');
|
|
88
|
+
// Create a function that captures the node and updates it
|
|
89
|
+
// We'll query the node once during init, then the function captures it
|
|
90
|
+
return `(function() {
|
|
91
|
+
const node = document.querySelector("${escapedSelector}");
|
|
92
|
+
return function(value) {
|
|
93
|
+
if (node) node.textContent = String(value);
|
|
94
|
+
};
|
|
95
|
+
})()`;
|
|
96
|
+
}).join(",\n ");
|
|
97
|
+
|
|
98
|
+
bindingMapEntries.push(
|
|
99
|
+
` "${stateName}": [\n ${updateFunctions}\n ]`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const bindingMapCode = bindingMapEntries.length > 0
|
|
105
|
+
? `__zen_bindings = {\n${bindingMapEntries.join(",\n")}\n };`
|
|
106
|
+
: `__zen_bindings = {};`;
|
|
107
|
+
|
|
108
|
+
// Generate global state initialization code
|
|
109
|
+
const globalStateInitCode = Array.from(globalStates).map(name => {
|
|
110
|
+
const initialValue = stateDeclarations.get(name) || "undefined";
|
|
111
|
+
return `
|
|
112
|
+
// Initialize global state: ${name}
|
|
113
|
+
(function() {
|
|
114
|
+
let __zen_${name} = ${initialValue};
|
|
115
|
+
Object.defineProperty(window, "${name}", {
|
|
116
|
+
get: function() { return __zen_${name}; },
|
|
117
|
+
set: function(value) {
|
|
118
|
+
__zen_${name} = value;
|
|
119
|
+
// Immediately trigger synchronous updates - no batching, no async
|
|
120
|
+
__zen_update_bindings("${name}", value);
|
|
121
|
+
// Also trigger dynamic expression updates
|
|
122
|
+
if (window.__zen_trigger_expression_updates) {
|
|
123
|
+
window.__zen_trigger_expression_updates("${name}");
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
enumerable: true,
|
|
127
|
+
configurable: true
|
|
128
|
+
});
|
|
129
|
+
})();`;
|
|
130
|
+
}).join("");
|
|
131
|
+
|
|
132
|
+
// Generate instance state initialization code
|
|
133
|
+
const instanceStateInitCode: string[] = [];
|
|
134
|
+
for (const [instanceId, baseStateMap] of instanceStates.entries()) {
|
|
135
|
+
const safeInstanceId = instanceId.replace(/-/g, '_');
|
|
136
|
+
|
|
137
|
+
for (const [baseState, fullStateNames] of baseStateMap.entries()) {
|
|
138
|
+
// For each instance, create a state proxy scoped to that instance
|
|
139
|
+
// Only process the first fullStateName (they should all have the same instance)
|
|
140
|
+
const fullStateName = fullStateNames[0];
|
|
141
|
+
const initialValue = stateDeclarations.get(fullStateName) || "undefined";
|
|
142
|
+
|
|
143
|
+
instanceStateInitCode.push(`
|
|
144
|
+
// Initialize instance-scoped state: ${fullStateName} (${instanceId}.${baseState})
|
|
145
|
+
(function() {
|
|
146
|
+
const instanceRoot = document.querySelector('[data-zen-instance="${instanceId}"]');
|
|
147
|
+
if (!instanceRoot) {
|
|
148
|
+
console.warn('[Zenith] Component instance "${instanceId}" not found in DOM');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let __zen_${safeInstanceId}_${baseState} = ${initialValue};
|
|
153
|
+
|
|
154
|
+
// Create instance-scoped state proxy
|
|
155
|
+
const instanceState = new Proxy({}, {
|
|
156
|
+
get(target, prop) {
|
|
157
|
+
if (prop === '${baseState}') {
|
|
158
|
+
return __zen_${safeInstanceId}_${baseState};
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
},
|
|
162
|
+
set(target, prop, value) {
|
|
163
|
+
if (prop === '${baseState}') {
|
|
164
|
+
__zen_${safeInstanceId}_${baseState} = value;
|
|
165
|
+
// Trigger updates only for bindings within this instance
|
|
166
|
+
__zen_update_bindings("${fullStateName}", value);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Store instance state on window for component access
|
|
174
|
+
if (!window.__zen_instances) {
|
|
175
|
+
window.__zen_instances = {};
|
|
176
|
+
}
|
|
177
|
+
if (!window.__zen_instances["${instanceId}"]) {
|
|
178
|
+
window.__zen_instances["${instanceId}"] = instanceState;
|
|
179
|
+
} else {
|
|
180
|
+
window.__zen_instances["${instanceId}"].${baseState} = __zen_${safeInstanceId}_${baseState};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Create global property accessor for instance-scoped state
|
|
184
|
+
Object.defineProperty(window, "${fullStateName}", {
|
|
185
|
+
get: function() { return __zen_${safeInstanceId}_${baseState}; },
|
|
186
|
+
set: function(value) {
|
|
187
|
+
__zen_${safeInstanceId}_${baseState} = value;
|
|
188
|
+
// Update instance state object
|
|
189
|
+
if (window.__zen_instances && window.__zen_instances["${instanceId}"]) {
|
|
190
|
+
window.__zen_instances["${instanceId}"].${baseState} = value;
|
|
191
|
+
}
|
|
192
|
+
__zen_update_bindings("${fullStateName}", value);
|
|
193
|
+
// Also trigger attribute binding updates for this instance
|
|
194
|
+
if (window.__zen_update_attribute_bindings) {
|
|
195
|
+
window.__zen_update_attribute_bindings("${instanceId}");
|
|
196
|
+
}
|
|
197
|
+
// Also trigger dynamic expression updates
|
|
198
|
+
if (window.__zen_trigger_expression_updates) {
|
|
199
|
+
window.__zen_trigger_expression_updates("${fullStateName}");
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
enumerable: true,
|
|
203
|
+
configurable: true
|
|
204
|
+
});
|
|
205
|
+
})();`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Generate initialization call (after DOM is ready)
|
|
210
|
+
const initBindingsCode = stateNames.map(name => {
|
|
211
|
+
return ` __zen_update_bindings("${name}", ${name});`;
|
|
212
|
+
}).join("\n");
|
|
213
|
+
|
|
214
|
+
return `
|
|
215
|
+
// Phase 2: Reactive text bindings runtime (with component instance support)
|
|
216
|
+
(function() {
|
|
217
|
+
let __zen_bindings = {};
|
|
218
|
+
|
|
219
|
+
// Update function for a specific state
|
|
220
|
+
// Calls all registered update functions for the given state property
|
|
221
|
+
// Executes synchronously, immediately, in compile-order (array order)
|
|
222
|
+
// No batching, no async scheduling, no reordering
|
|
223
|
+
function __zen_update_bindings(stateName, value) {
|
|
224
|
+
const updaters = __zen_bindings[stateName];
|
|
225
|
+
if (updaters) {
|
|
226
|
+
// Execute update functions in deterministic order (compile-time order)
|
|
227
|
+
// forEach executes synchronously, preserving array order
|
|
228
|
+
updaters.forEach(updateFn => {
|
|
229
|
+
if (typeof updateFn === 'function') {
|
|
230
|
+
updateFn(value);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
${globalStateInitCode}
|
|
237
|
+
${instanceStateInitCode.join('')}
|
|
238
|
+
|
|
239
|
+
// Initialize binding map and bindings after DOM is ready
|
|
240
|
+
function __zen_init_bindings() {
|
|
241
|
+
${bindingMapCode.split('\n').map(line => ' ' + line).join('\n')}
|
|
242
|
+
${initBindingsCode.split('\n').map(line => ' ' + line).join('\n')}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Initialize bindings when DOM is ready (scripts are deferred, so DOM should be ready)
|
|
246
|
+
if (document.readyState === 'loading') {
|
|
247
|
+
document.addEventListener('DOMContentLoaded', __zen_init_bindings);
|
|
248
|
+
} else {
|
|
249
|
+
__zen_init_bindings();
|
|
250
|
+
}
|
|
251
|
+
})();
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
|