@zenithbuild/core 0.1.0 → 0.3.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.
- package/LICENSE +1 -1
- package/README.md +24 -40
- package/bin/zen-build.ts +2 -0
- package/bin/zen-dev.ts +2 -0
- package/bin/zen-preview.ts +2 -0
- package/bin/zenith.ts +2 -0
- package/cli/commands/add.ts +37 -0
- package/cli/commands/build.ts +37 -0
- package/cli/commands/create.ts +702 -0
- package/cli/commands/dev.ts +197 -0
- package/cli/commands/index.ts +112 -0
- package/cli/commands/preview.ts +62 -0
- package/cli/commands/remove.ts +33 -0
- package/cli/index.ts +10 -0
- package/cli/main.ts +101 -0
- package/cli/utils/branding.ts +153 -0
- package/cli/utils/logger.ts +40 -0
- package/cli/utils/plugin-manager.ts +114 -0
- package/cli/utils/project.ts +71 -0
- package/compiler/build-analyzer.ts +122 -0
- package/compiler/discovery/layouts.ts +61 -0
- package/compiler/index.ts +40 -24
- package/compiler/ir/types.ts +1 -0
- package/compiler/parse/parseScript.ts +29 -5
- package/compiler/parse/parseTemplate.ts +96 -58
- package/compiler/parse/scriptAnalysis.ts +77 -0
- package/compiler/runtime/dataExposure.ts +49 -31
- package/compiler/runtime/generateDOM.ts +18 -17
- package/compiler/runtime/generateHydrationBundle.ts +24 -5
- package/compiler/runtime/transformIR.ts +140 -49
- package/compiler/runtime/wrapExpressionWithLoop.ts +11 -11
- package/compiler/spa-build.ts +70 -153
- package/compiler/ssg-build.ts +412 -0
- package/compiler/transform/layoutProcessor.ts +132 -0
- package/compiler/transform/transformNode.ts +19 -19
- package/dist/cli.js +11648 -0
- package/dist/zen-build.js +11659 -0
- package/dist/zen-dev.js +11659 -0
- package/dist/zen-preview.js +11659 -0
- package/dist/zenith.js +11659 -0
- package/package.json +22 -2
- package/runtime/bundle-generator.ts +416 -0
- package/runtime/client-runtime.ts +532 -0
- package/.eslintignore +0 -15
- package/.gitattributes +0 -2
- package/.github/ISSUE_TEMPLATE/compiler-errors-for-invalid-state-declarations.md +0 -25
- package/.github/ISSUE_TEMPLATE/new_ticket.yaml +0 -34
- package/.github/pull_request_template.md +0 -15
- package/.github/workflows/discord-changelog.yml +0 -141
- package/.github/workflows/discord-notify.yml +0 -242
- package/.github/workflows/discord-version.yml +0 -195
- package/.prettierignore +0 -13
- package/.prettierrc +0 -21
- package/.zen.d.ts +0 -15
- package/app/components/Button.zen +0 -46
- package/app/components/Link.zen +0 -11
- package/app/favicon.ico +0 -0
- package/app/layouts/Main.zen +0 -59
- package/app/pages/about.zen +0 -23
- package/app/pages/blog/[id].zen +0 -53
- package/app/pages/blog/index.zen +0 -32
- package/app/pages/dynamic-dx.zen +0 -712
- package/app/pages/dynamic-primitives.zen +0 -453
- package/app/pages/index.zen +0 -154
- package/app/pages/navigation-demo.zen +0 -229
- package/app/pages/posts/[...slug].zen +0 -61
- package/app/pages/primitives-demo.zen +0 -273
- 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 +0 -601
- package/assets/logos/README.md +0 -54
- package/assets/logos/zen.icns +0 -0
- package/bun.lock +0 -39
- package/compiler/legacy/binding.ts +0 -254
- package/compiler/legacy/bindings.ts +0 -338
- package/compiler/legacy/component-process.ts +0 -1208
- package/compiler/legacy/component.ts +0 -301
- package/compiler/legacy/event.ts +0 -50
- package/compiler/legacy/expression.ts +0 -1149
- package/compiler/legacy/mutation.ts +0 -280
- package/compiler/legacy/parse.ts +0 -299
- package/compiler/legacy/split.ts +0 -608
- package/compiler/legacy/types.ts +0 -32
- package/docs/COMMENTS.md +0 -111
- package/docs/COMMITS.md +0 -36
- package/docs/CONTRIBUTING.md +0 -116
- package/docs/STYLEGUIDE.md +0 -62
- package/scripts/webhook-proxy.ts +0 -213
package/assets/logos/README.md
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# Zenith Logos
|
|
2
|
-
|
|
3
|
-
This directory contains the Zenith framework logo assets.
|
|
4
|
-
|
|
5
|
-
## Logo Files
|
|
6
|
-
|
|
7
|
-
Based on the logo designs, you should place the following files here:
|
|
8
|
-
|
|
9
|
-
1. **zenith-logo-icon.png** (or .svg) - The file icon version with the folded corner and ".ZENITH" button
|
|
10
|
-
2. **zenith-logo-z.png** (or .svg) - The stylized "Z" logo with gradient and light trails
|
|
11
|
-
3. **zenith-logo-full.png** (or .svg) - The full logo with "ZENITH" text below
|
|
12
|
-
4. **zenith-logo-soon.png** (or .svg) - The logo variant with "Soon" text
|
|
13
|
-
|
|
14
|
-
## File Icon for .zen Files
|
|
15
|
-
|
|
16
|
-
The SVG logo has been converted to a macOS `.icns` file to use as the file icon for `.zen` files.
|
|
17
|
-
|
|
18
|
-
### Setting the Icon
|
|
19
|
-
|
|
20
|
-
**Option 1: Use the automated script (recommended)**
|
|
21
|
-
```bash
|
|
22
|
-
cd assets/logos
|
|
23
|
-
./set-zen-icon.sh path/to/your/file.zen
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
**Option 2: Set icon for all .zen files in a directory**
|
|
27
|
-
```bash
|
|
28
|
-
cd assets/logos
|
|
29
|
-
find ../app -name "*.zen" -exec ./set-zen-icon.sh {} \;
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
**Option 3: Manual method**
|
|
33
|
-
1. Open Finder and navigate to a `.zen` file
|
|
34
|
-
2. Right-click the file and select "Get Info"
|
|
35
|
-
3. Drag `zen.icns` onto the small icon in the top-left of the Get Info window
|
|
36
|
-
4. Close the Get Info window
|
|
37
|
-
|
|
38
|
-
### Recreating the .icns File
|
|
39
|
-
|
|
40
|
-
If you update the SVG and need to regenerate the `.icns` file:
|
|
41
|
-
```bash
|
|
42
|
-
cd assets/logos
|
|
43
|
-
./create-zen-icon.sh
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Usage
|
|
47
|
-
|
|
48
|
-
These logos can be used in:
|
|
49
|
-
- Documentation (`docs/`)
|
|
50
|
-
- README files
|
|
51
|
-
- Website/marketing materials
|
|
52
|
-
- Framework branding
|
|
53
|
-
- File type icons (`.zen` files)
|
|
54
|
-
|
package/assets/logos/zen.icns
DELETED
|
Binary file
|
package/bun.lock
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"lockfileVersion": 1,
|
|
3
|
-
"configVersion": 1,
|
|
4
|
-
"workspaces": {
|
|
5
|
-
"": {
|
|
6
|
-
"name": "zenith",
|
|
7
|
-
"dependencies": {
|
|
8
|
-
"@types/parse5": "^7.0.0",
|
|
9
|
-
"parse5": "^8.0.0",
|
|
10
|
-
},
|
|
11
|
-
"devDependencies": {
|
|
12
|
-
"@types/bun": "latest",
|
|
13
|
-
"prettier": "^3.7.4",
|
|
14
|
-
},
|
|
15
|
-
"peerDependencies": {
|
|
16
|
-
"typescript": "^5",
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
"packages": {
|
|
21
|
-
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
|
22
|
-
|
|
23
|
-
"@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
|
24
|
-
|
|
25
|
-
"@types/parse5": ["@types/parse5@7.0.0", "", { "dependencies": { "parse5": "*" } }, "sha512-f2SeAxumolBmhuR62vNGTsSAvdz/Oj0k682xNrcKJ4dmRnTPODB74j6CPoNPzBPTHsu7Y7W7u93Mgp8Ovo8vWw=="],
|
|
26
|
-
|
|
27
|
-
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
|
28
|
-
|
|
29
|
-
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
|
30
|
-
|
|
31
|
-
"parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="],
|
|
32
|
-
|
|
33
|
-
"prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
|
|
34
|
-
|
|
35
|
-
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
36
|
-
|
|
37
|
-
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
38
|
-
}
|
|
39
|
-
}
|
|
@@ -1,254 +0,0 @@
|
|
|
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
|
-
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
// compiler/bindings.ts
|
|
2
|
-
// Phase 2: Object-style dynamic attribute bindings with quoted expressions
|
|
3
|
-
// Supports :class and :value attributes with synchronous, deterministic updates
|
|
4
|
-
|
|
5
|
-
export function generateAttributeBindingRuntime(bindings: Array<{ type: 'class' | 'value'; expression: string }>): string {
|
|
6
|
-
if (bindings.length === 0) {
|
|
7
|
-
return ''; // No bindings, no runtime needed
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// Generate unique expression IDs for tracking
|
|
11
|
-
const expressionIds = bindings.map((_, i) => `expr_${i}`);
|
|
12
|
-
const expressionsStr = JSON.stringify(bindings.map(b => b.expression));
|
|
13
|
-
const typesStr = JSON.stringify(bindings.map(b => b.type));
|
|
14
|
-
|
|
15
|
-
return `
|
|
16
|
-
// Phase 2: Attribute binding runtime - synchronous, deterministic updates
|
|
17
|
-
// Note: 'use strict' is omitted to allow 'with' statement for expression evaluation
|
|
18
|
-
(function() {
|
|
19
|
-
|
|
20
|
-
// Store all binding elements and their expressions
|
|
21
|
-
const bindingExpressions = ${expressionsStr};
|
|
22
|
-
const bindingTypes = ${typesStr};
|
|
23
|
-
const bindingElements = [];
|
|
24
|
-
|
|
25
|
-
// Reactive state proxy - tracks property access and updates DOM synchronously
|
|
26
|
-
// Initialize with empty object - properties will be added dynamically
|
|
27
|
-
const stateTarget = {};
|
|
28
|
-
const stateProxy = new Proxy(stateTarget, {
|
|
29
|
-
set(target, prop, value) {
|
|
30
|
-
const oldValue = target[prop];
|
|
31
|
-
target[prop] = value;
|
|
32
|
-
|
|
33
|
-
// Log state change for debugging
|
|
34
|
-
// console.log('[Zenith] State change:', prop, '=', value);
|
|
35
|
-
|
|
36
|
-
// Synchronously update all affected bindings
|
|
37
|
-
bindingElements.forEach(binding => {
|
|
38
|
-
try {
|
|
39
|
-
// Re-evaluate expression in context of current state
|
|
40
|
-
// Pass target (the state object) as parameter to the evaluator function
|
|
41
|
-
// If this binding has instance state, merge it with global state
|
|
42
|
-
const mergedState = binding.instanceState
|
|
43
|
-
? Object.assign({}, target, binding.instanceState)
|
|
44
|
-
: target;
|
|
45
|
-
const result = binding.fn(mergedState);
|
|
46
|
-
|
|
47
|
-
if (binding.type === 'class') {
|
|
48
|
-
updateClassBinding(binding.el, result);
|
|
49
|
-
// console.log('[Zenith] Updated :class binding for element:', binding.el, 'result:', result);
|
|
50
|
-
} else if (binding.type === 'value') {
|
|
51
|
-
updateValueBinding(binding.el, result);
|
|
52
|
-
// console.log('[Zenith] Updated :value binding for element:', binding.el, 'result:', result);
|
|
53
|
-
}
|
|
54
|
-
} catch (e) {
|
|
55
|
-
// Log errors for debugging (Phase 2: graceful degradation)
|
|
56
|
-
console.warn('[Zenith] Binding evaluation error:', e, 'for expression:', binding.expression);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
return true;
|
|
61
|
-
},
|
|
62
|
-
get(target, prop) {
|
|
63
|
-
// Return undefined for missing properties (don't throw errors)
|
|
64
|
-
return target[prop];
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// Make stateProxy available globally as 'state'
|
|
69
|
-
window.state = stateProxy;
|
|
70
|
-
|
|
71
|
-
// Function to update attribute bindings for a specific component instance
|
|
72
|
-
// Called when instance-scoped state changes
|
|
73
|
-
function updateAttributeBindingsForInstance(instanceId) {
|
|
74
|
-
const instanceRoot = document.querySelector('[data-zen-instance="' + instanceId + '"]');
|
|
75
|
-
if (!instanceRoot) return;
|
|
76
|
-
|
|
77
|
-
// Update all bindings within this instance
|
|
78
|
-
bindingElements.forEach(binding => {
|
|
79
|
-
// Check if this binding belongs to the instance
|
|
80
|
-
const bindingInstanceRoot = findInstanceRoot(binding.el);
|
|
81
|
-
if (bindingInstanceRoot === instanceRoot) {
|
|
82
|
-
try {
|
|
83
|
-
const instanceState = getInstanceStateForElement(binding.el);
|
|
84
|
-
const mergedState = instanceState
|
|
85
|
-
? Object.assign({}, stateProxy, instanceState)
|
|
86
|
-
: stateProxy;
|
|
87
|
-
const result = binding.fn(mergedState);
|
|
88
|
-
|
|
89
|
-
if (binding.type === 'class') {
|
|
90
|
-
updateClassBinding(binding.el, result);
|
|
91
|
-
} else if (binding.type === 'value') {
|
|
92
|
-
updateValueBinding(binding.el, result);
|
|
93
|
-
}
|
|
94
|
-
} catch (e) {
|
|
95
|
-
console.warn('[Zenith] Attribute binding evaluation error:', e, 'for expression:', binding.expression);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Expose update function globally so text binding runtime can trigger it
|
|
102
|
-
window.__zen_update_attribute_bindings = updateAttributeBindingsForInstance;
|
|
103
|
-
|
|
104
|
-
// Helper: Evaluate class binding expression
|
|
105
|
-
// Handles: objects, strings, empty objects, falsy values
|
|
106
|
-
// Preserves existing static classes from the class attribute
|
|
107
|
-
function updateClassBinding(el, result) {
|
|
108
|
-
// Store static classes on first update (from class attribute)
|
|
109
|
-
if (!el._zenStaticClasses) {
|
|
110
|
-
const staticClasses = el.getAttribute('class') || '';
|
|
111
|
-
el._zenStaticClasses = staticClasses.split(/\\s+/).filter(c => c);
|
|
112
|
-
}
|
|
113
|
-
const staticClassList = el._zenStaticClasses;
|
|
114
|
-
|
|
115
|
-
if (typeof result === 'string') {
|
|
116
|
-
// String value: treat as raw class names, merge with static classes
|
|
117
|
-
const dynamicClasses = result.split(/\\s+/).filter(c => c);
|
|
118
|
-
el.className = [...staticClassList, ...dynamicClasses].join(' ').trim();
|
|
119
|
-
} else if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
120
|
-
// Object value: extract keys with true values
|
|
121
|
-
const dynamicClasses = [];
|
|
122
|
-
for (const key in result) {
|
|
123
|
-
if (result.hasOwnProperty(key) && result[key] === true) {
|
|
124
|
-
dynamicClasses.push(key);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// Merge static and dynamic classes
|
|
128
|
-
el.className = [...staticClassList, ...dynamicClasses].join(' ').trim();
|
|
129
|
-
} else {
|
|
130
|
-
// Falsy, null, undefined, or non-object: keep only static classes
|
|
131
|
-
el.className = staticClassList.join(' ').trim();
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Helper: Evaluate value binding expression
|
|
136
|
-
// Handles: primitives, falsy values
|
|
137
|
-
function updateValueBinding(el, result) {
|
|
138
|
-
if (result === null || result === undefined) {
|
|
139
|
-
el.value = '';
|
|
140
|
-
} else {
|
|
141
|
-
el.value = String(result);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Helper: Safely evaluate expression string
|
|
146
|
-
// Creates a function that evaluates the expression with state properties in scope
|
|
147
|
-
// Supports both global state (via state object) and instance-scoped state (via window)
|
|
148
|
-
function createEvaluator(expression) {
|
|
149
|
-
// Trim whitespace from expression
|
|
150
|
-
let trimmed = expression.trim();
|
|
151
|
-
|
|
152
|
-
// Check if expression is a quoted string (single or double quotes)
|
|
153
|
-
// If so, unquote it and check if it's a simple identifier
|
|
154
|
-
let isSimpleIdentifier = false;
|
|
155
|
-
let evalExpression = trimmed;
|
|
156
|
-
|
|
157
|
-
// Handle quoted strings: "username" or 'username' -> username
|
|
158
|
-
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
159
|
-
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
160
|
-
// Extract the unquoted value
|
|
161
|
-
const unquoted = trimmed.slice(1, -1);
|
|
162
|
-
// Check if unquoted value is a simple identifier
|
|
163
|
-
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(unquoted)) {
|
|
164
|
-
isSimpleIdentifier = true;
|
|
165
|
-
evalExpression = unquoted; // Use unquoted identifier for evaluation
|
|
166
|
-
}
|
|
167
|
-
// Otherwise, treat as string literal (e.g., "'static-class'" -> "static-class")
|
|
168
|
-
} else {
|
|
169
|
-
// Not quoted: check if it's a simple identifier
|
|
170
|
-
isSimpleIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(trimmed);
|
|
171
|
-
if (isSimpleIdentifier) {
|
|
172
|
-
evalExpression = trimmed; // Already unquoted identifier
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
// Create a function that evaluates the expression
|
|
178
|
-
// The expression is written as if state properties are directly accessible
|
|
179
|
-
// We use Function constructor with 'with' statement to make state properties available
|
|
180
|
-
// Note: 'with' is deprecated but necessary for this use case in non-strict mode
|
|
181
|
-
return function(state) {
|
|
182
|
-
try {
|
|
183
|
-
// Use window.__zen_eval_expr for consistent expression evaluation
|
|
184
|
-
// This handles window properties (state variables) correctly
|
|
185
|
-
if (typeof window !== 'undefined' && window.__zen_eval_expr) {
|
|
186
|
-
return window.__zen_eval_expr(evalExpression);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Fallback: Merge state with window to access instance-scoped state variables
|
|
190
|
-
// This allows expressions to reference both global state and instance-scoped state
|
|
191
|
-
// Copy window properties that look like instance-scoped state to the merged context
|
|
192
|
-
const mergedContext = Object.assign({}, state);
|
|
193
|
-
for (const key in window) {
|
|
194
|
-
if (key.startsWith('__zen_comp_') && !(key in mergedContext)) {
|
|
195
|
-
mergedContext[key] = window[key];
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Use Function constructor to create evaluator
|
|
200
|
-
// The 'with' statement makes state properties and instance-scoped state available as variables
|
|
201
|
-
// This allows expressions like "{ active: isActive }" where isActive refers to state.isActive
|
|
202
|
-
// or instance-scoped variables like __zen_comp_0_clicks
|
|
203
|
-
const func = new Function('state',
|
|
204
|
-
'try {' +
|
|
205
|
-
' with (state) {' +
|
|
206
|
-
' return (' + evalExpression + ');' +
|
|
207
|
-
' }' +
|
|
208
|
-
'} catch (e) {' +
|
|
209
|
-
' console.warn("[Zenith] Expression evaluation error:", ' + JSON.stringify(trimmed) + ', e);' +
|
|
210
|
-
' return null;' +
|
|
211
|
-
'}'
|
|
212
|
-
);
|
|
213
|
-
const result = func(mergedContext);
|
|
214
|
-
// console.log('[Zenith] Evaluated expression:', trimmed, 'result:', result, 'state:', state);
|
|
215
|
-
return result;
|
|
216
|
-
} catch (e) {
|
|
217
|
-
// Last resort: return safe default
|
|
218
|
-
console.warn('Expression evaluation error:', trimmed, e);
|
|
219
|
-
const bindingIndex = bindingExpressions.indexOf(expression);
|
|
220
|
-
return bindingIndex >= 0 && bindingTypes[bindingIndex] === 'class' ? {} : '';
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
} catch (e) {
|
|
224
|
-
// If expression is invalid, return a function that returns empty string/object
|
|
225
|
-
console.warn('Invalid binding expression:', expression, e);
|
|
226
|
-
const bindingIndex = bindingExpressions.indexOf(expression);
|
|
227
|
-
return function() {
|
|
228
|
-
return bindingIndex >= 0 && bindingTypes[bindingIndex] === 'class' ? {} : '';
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Helper: Find component instance root for an element
|
|
234
|
-
function findInstanceRoot(el) {
|
|
235
|
-
let current = el;
|
|
236
|
-
while (current) {
|
|
237
|
-
if (current.hasAttribute && current.hasAttribute('data-zen-instance')) {
|
|
238
|
-
return current;
|
|
239
|
-
}
|
|
240
|
-
current = current.parentElement;
|
|
241
|
-
}
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Helper: Get instance-scoped state for an element
|
|
246
|
-
function getInstanceStateForElement(el) {
|
|
247
|
-
const instanceRoot = findInstanceRoot(el);
|
|
248
|
-
if (instanceRoot) {
|
|
249
|
-
const instanceId = instanceRoot.getAttribute('data-zen-instance');
|
|
250
|
-
if (instanceId && window.__zen_instances && window.__zen_instances[instanceId]) {
|
|
251
|
-
return window.__zen_instances[instanceId];
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Enhanced evaluator that supports both global and instance-scoped state
|
|
258
|
-
function createEnhancedEvaluator(expression, instanceState) {
|
|
259
|
-
const baseEvaluator = createEvaluator(expression);
|
|
260
|
-
return function(state) {
|
|
261
|
-
// Merge global state and instance state
|
|
262
|
-
const mergedState = Object.assign({}, state);
|
|
263
|
-
if (instanceState) {
|
|
264
|
-
Object.assign(mergedState, instanceState);
|
|
265
|
-
}
|
|
266
|
-
// Also check window for instance-scoped state variables (e.g., __zen_comp_0_clicks)
|
|
267
|
-
// These are set up by the binding runtime
|
|
268
|
-
return baseEvaluator(mergedState);
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Initialize bindings after DOM is ready
|
|
273
|
-
function initializeBindings() {
|
|
274
|
-
// console.log('[Zenith] Initializing attribute bindings...');
|
|
275
|
-
|
|
276
|
-
// Find all elements with data-zen-class or data-zen-value attributes
|
|
277
|
-
const classElements = document.querySelectorAll('[data-zen-class]');
|
|
278
|
-
const valueElements = document.querySelectorAll('[data-zen-value]');
|
|
279
|
-
|
|
280
|
-
// console.log('[Zenith] Found', classElements.length, ':class bindings and', valueElements.length, ':value bindings');
|
|
281
|
-
|
|
282
|
-
// Process :class bindings
|
|
283
|
-
classElements.forEach((el) => {
|
|
284
|
-
const expression = el.getAttribute('data-zen-class');
|
|
285
|
-
if (expression) {
|
|
286
|
-
// console.log('[Zenith] Setting up :class binding:', expression, 'for element:', el);
|
|
287
|
-
const instanceState = getInstanceStateForElement(el);
|
|
288
|
-
const fn = createEnhancedEvaluator(expression, instanceState);
|
|
289
|
-
|
|
290
|
-
// Use merged state for initial evaluation
|
|
291
|
-
const mergedState = Object.assign({}, stateProxy);
|
|
292
|
-
if (instanceState) {
|
|
293
|
-
Object.assign(mergedState, instanceState);
|
|
294
|
-
}
|
|
295
|
-
const result = fn(mergedState);
|
|
296
|
-
updateClassBinding(el, result);
|
|
297
|
-
bindingElements.push({ el: el, type: 'class', expression, fn, instanceState });
|
|
298
|
-
// console.log('[Zenith] Initial :class result:', result, 'applied classes:', el.className);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// Process :value bindings
|
|
303
|
-
valueElements.forEach((el) => {
|
|
304
|
-
const expression = el.getAttribute('data-zen-value');
|
|
305
|
-
if (expression) {
|
|
306
|
-
// console.log('[Zenith] Setting up :value binding:', expression, 'for element:', el);
|
|
307
|
-
const instanceState = getInstanceStateForElement(el);
|
|
308
|
-
const fn = createEnhancedEvaluator(expression, instanceState);
|
|
309
|
-
|
|
310
|
-
// Use merged state for initial evaluation
|
|
311
|
-
const mergedState = Object.assign({}, stateProxy);
|
|
312
|
-
if (instanceState) {
|
|
313
|
-
Object.assign(mergedState, instanceState);
|
|
314
|
-
}
|
|
315
|
-
const result = fn(mergedState);
|
|
316
|
-
updateValueBinding(el, result);
|
|
317
|
-
bindingElements.push({ el: el, type: 'value', expression, fn, instanceState });
|
|
318
|
-
// console.log('[Zenith] Initial :value result:', result, 'applied value:', el.value);
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
// Instance state proxies are set up by the text binding runtime
|
|
323
|
-
// Attribute binding updates are triggered via window.__zen_update_attribute_bindings
|
|
324
|
-
// when instance-scoped state changes
|
|
325
|
-
|
|
326
|
-
// console.log('[Zenith] Initialized', bindingElements.length, 'bindings. State object:', stateProxy);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Initialize when DOM is ready
|
|
330
|
-
if (document.readyState === 'loading') {
|
|
331
|
-
document.addEventListener('DOMContentLoaded', initializeBindings);
|
|
332
|
-
} else {
|
|
333
|
-
initializeBindings();
|
|
334
|
-
}
|
|
335
|
-
})();
|
|
336
|
-
`;
|
|
337
|
-
}
|
|
338
|
-
|