@ape-egg/vibe 4.0.1 → 4.1.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/README.md +3 -1
- package/boot.js +10 -9
- package/component.js +0 -29
- package/hot-module-refresh.js +0 -0
- package/index.js +0 -28
- package/package.json +3 -2
- package/runtime/_vibe-compiled-iteration-batch.js +0 -56
- package/runtime/affected.js +6 -189
- package/runtime/cleanup.js +4 -35
- package/runtime/component-cache.js +0 -53
- package/runtime/component.js +1 -406
- package/runtime/conditionals.js +1 -143
- package/runtime/constants.js +19 -82
- package/runtime/debug.js +22 -47
- package/runtime/dispatch.js +0 -46
- package/runtime/hydrate.js +10 -129
- package/runtime/index.js +7 -365
- package/runtime/iterate.js +7 -595
- package/runtime/iteration-utils.js +18 -71
- package/runtime/loop-scope.js +0 -58
- package/runtime/manifest.js +0 -27
- package/runtime/parse.js +2 -116
- package/runtime/pre-compiled-iterations.js +3 -51
- package/runtime/pre-compiled-manifest.js +6 -169
- package/runtime/raw-html.js +0 -5
- package/runtime/reconcile.js +4 -159
- package/runtime/staging.js +0 -57
- package/runtime/state.js +0 -61
- package/runtime/this-scope.js +0 -17
- package/runtime/tracking.js +0 -65
- package/runtime/utils.js +1 -144
- package/runtime/vibe-css.js +54 -0
- package/spa.js +0 -76
- package/vibe.css +22 -44
package/runtime/conditionals.js
CHANGED
|
@@ -9,20 +9,10 @@ import { evalInScope } from './utils.js';
|
|
|
9
9
|
import { collectComponentIds, releaseOrphanedComponentState, executeCompiledComponentScriptsIn } from './component.js';
|
|
10
10
|
import { isOutgoing } from './staging.js';
|
|
11
11
|
|
|
12
|
-
// Registry of DOM nodes owned by conditional branches.
|
|
13
|
-
// Maps a DOM node to { nodes: array_ref, index: number } so that
|
|
14
|
-
// processMutations can update the reference when processComponent
|
|
15
|
-
// replaces the node (el.replaceWith). Keeps conditional state in
|
|
16
|
-
// sync with actual DOM without sweeps or special properties.
|
|
17
12
|
export const branchNodeRegistry = new WeakMap();
|
|
18
13
|
|
|
19
|
-
// Nodes that have been processed by mountBranch or renderIteration.
|
|
20
|
-
// processMutations checks this to avoid re-processing already-handled content.
|
|
21
14
|
export const managedNodes = new WeakSet();
|
|
22
15
|
|
|
23
|
-
// Find the dot path of a conditional node in the manifest.
|
|
24
|
-
// Resolves the parent element through the reverse index, then appends the
|
|
25
|
-
// conditional's key.
|
|
26
16
|
const findConditionalPath = (node, manifest) => {
|
|
27
17
|
const parentPath = manifestPathOf(manifest, node.meta.startComment.parentNode);
|
|
28
18
|
if (parentPath === null) return null;
|
|
@@ -30,7 +20,6 @@ const findConditionalPath = (node, manifest) => {
|
|
|
30
20
|
return `${parentPath}.${node._key}`;
|
|
31
21
|
};
|
|
32
22
|
|
|
33
|
-
// Register branch tree nodes in the manifest (recursive)
|
|
34
23
|
const addBranchToManifest = (tree, manifest, basePath) => {
|
|
35
24
|
if (tree.element) {
|
|
36
25
|
setManifestEntry(manifest, basePath, tree.element);
|
|
@@ -42,7 +31,6 @@ const addBranchToManifest = (tree, manifest, basePath) => {
|
|
|
42
31
|
}
|
|
43
32
|
};
|
|
44
33
|
|
|
45
|
-
// Remove branch tree nodes from the manifest (recursive)
|
|
46
34
|
const removeBranchFromManifest = (tree, manifest, basePath) => {
|
|
47
35
|
delete manifest[basePath];
|
|
48
36
|
if (tree.children) {
|
|
@@ -52,39 +40,29 @@ const removeBranchFromManifest = (tree, manifest, basePath) => {
|
|
|
52
40
|
}
|
|
53
41
|
};
|
|
54
42
|
|
|
55
|
-
// Evaluate conditional expression in state context
|
|
56
43
|
const evaluateCondition = (expression, state, element = null) => !!evalInScope(expression, state, element);
|
|
57
44
|
|
|
58
|
-
// Extract state dependencies from an expression
|
|
59
|
-
// e.g., "count > 5" → ["count"]
|
|
60
|
-
// e.g., "isLoggedIn && isAdmin" → ["isLoggedIn", "isAdmin"]
|
|
61
45
|
export const extractDependencies = (expression) => {
|
|
62
46
|
const regex = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\b/g;
|
|
63
47
|
const matches = [];
|
|
64
48
|
let match;
|
|
65
49
|
while ((match = regex.exec(expression))) {
|
|
66
50
|
const identifier = match[1];
|
|
67
|
-
// Filter out JavaScript keywords and common literals
|
|
68
51
|
if (!['true', 'false', 'null', 'undefined', 'this', 'return'].includes(identifier)) {
|
|
69
52
|
matches.push(identifier);
|
|
70
53
|
}
|
|
71
54
|
}
|
|
72
|
-
return [...new Set(matches)];
|
|
55
|
+
return [...new Set(matches)];
|
|
73
56
|
};
|
|
74
57
|
|
|
75
|
-
|
|
76
|
-
// Render all conditionals in the parsed tree
|
|
77
58
|
export const renderAllConditionals = (tree, state, manifest, parentScope = {}) => {
|
|
78
59
|
let count = 0;
|
|
79
60
|
|
|
80
|
-
// If this is a conditional node, render it
|
|
81
61
|
if (tree.type === 'conditional') {
|
|
82
62
|
renderConditional(tree, state, manifest, parentScope);
|
|
83
63
|
return 1;
|
|
84
64
|
}
|
|
85
65
|
|
|
86
|
-
// Recursively render conditionals in child nodes — except under an
|
|
87
|
-
// outgoing wrapper, whose content the pending swap will replace wholesale.
|
|
88
66
|
if (tree.children && !(tree.element && isOutgoing(tree.element))) {
|
|
89
67
|
Object.keys(tree.children).forEach((key) => {
|
|
90
68
|
const child = tree.children[key];
|
|
@@ -97,21 +75,9 @@ export const renderAllConditionals = (tree, state, manifest, parentScope = {}) =
|
|
|
97
75
|
return count;
|
|
98
76
|
};
|
|
99
77
|
|
|
100
|
-
// Re-settle every ALREADY-RENDERED conditional against the current world.
|
|
101
|
-
// The boot's initial pass can run before module scripts have evaluated
|
|
102
|
-
// (compiled shell: boot.js rides the vibe-module chain), so a gate on a
|
|
103
|
-
// module-provided global — `<!-- if window.isDev -->` — first evaluates
|
|
104
|
-
// against the pre-module world and carries no reactive dependency that would
|
|
105
|
-
// ever re-check it. Walks like renderAllConditionals but routes through
|
|
106
|
-
// updateConditional: value-unchanged branches are a no-op, changed ones
|
|
107
|
-
// mount/unmount through the normal branch machinery.
|
|
108
78
|
export const settleConditionals = (tree, state, manifest, parentScope = {}) => {
|
|
109
79
|
if (tree.type === 'conditional') {
|
|
110
80
|
updateConditional(tree, state, state, manifest, parentScope);
|
|
111
|
-
// A COMPILED pre-rendered conditional keeps its live content in
|
|
112
|
-
// `children` (never mountBranch'd) — nested conditionals there need
|
|
113
|
-
// settling too. Connected-root check skips a runtime conditional's
|
|
114
|
-
// detached template nodes.
|
|
115
81
|
if (tree.children) {
|
|
116
82
|
Object.keys(tree.children).forEach((key) => {
|
|
117
83
|
const child = tree.children[key];
|
|
@@ -133,35 +99,17 @@ export const settleConditionals = (tree, state, manifest, parentScope = {}) => {
|
|
|
133
99
|
}
|
|
134
100
|
};
|
|
135
101
|
|
|
136
|
-
// Initial render of a conditional block
|
|
137
102
|
export const renderConditional = (node, state, manifest, parentScope = {}) => {
|
|
138
103
|
const { expression, startComment, endComment, branches } = node.meta;
|
|
139
104
|
|
|
140
|
-
// Mark conditionals that live inside an iteration row so update-time branch
|
|
141
|
-
// flips can route `<component src>` props through the iteration-prop
|
|
142
|
-
// registry. Skipping this for top-level conditionals keeps their props as
|
|
143
|
-
// live `@[stateKey]` bindings — which is what global state-change reactivity
|
|
144
|
-
// depends on (the registry path snapshots a value and doesn't react to
|
|
145
|
-
// global state changes on its own).
|
|
146
|
-
// `parentScope` is populated on the initial-render path, but a conditional
|
|
147
|
-
// mounted via the update path (a parent conditional flipping true after load)
|
|
148
|
-
// — or any deeper-nested conditional — arrives with an empty parentScope, the
|
|
149
|
-
// iteration's scoped state flowing in through `state` instead. `scopeAliases`
|
|
150
|
-
// is set at parse time and persists, so it reliably marks a conditional that
|
|
151
|
-
// lexically lives inside an iteration regardless of which path mounts it — the
|
|
152
|
-
// Brawling loader, whose conditional flips true only once combat starts.
|
|
153
105
|
if (Object.keys(parentScope).length > 0 || node.meta.scopeAliases?.length) {
|
|
154
106
|
node.runtime.inIteration = true;
|
|
155
107
|
}
|
|
156
108
|
|
|
157
|
-
// Check if already rendered (using marker on comment node)
|
|
158
|
-
// @ts-ignore - adding custom property to comment node
|
|
159
109
|
if (startComment.__vibeRendered) {
|
|
160
110
|
return;
|
|
161
111
|
}
|
|
162
112
|
|
|
163
|
-
// Remove original template nodes from DOM (between start and end comments)
|
|
164
|
-
// Only do this on first render (when template hasn't been removed yet)
|
|
165
113
|
if (!node.runtime.templateRemoved) {
|
|
166
114
|
let currentNode = startComment.nextSibling;
|
|
167
115
|
while (currentNode && currentNode !== endComment) {
|
|
@@ -172,58 +120,40 @@ export const renderConditional = (node, state, manifest, parentScope = {}) => {
|
|
|
172
120
|
currentNode = nextNode;
|
|
173
121
|
}
|
|
174
122
|
|
|
175
|
-
// Mark that template has been removed
|
|
176
123
|
node.runtime.templateRemoved = true;
|
|
177
124
|
}
|
|
178
125
|
|
|
179
|
-
// Mark comment as rendered (survives re-parsing)
|
|
180
|
-
// @ts-ignore - adding custom property to comment node
|
|
181
126
|
startComment.__vibeRendered = true;
|
|
182
127
|
|
|
183
|
-
// Evaluate condition with current state — inside a tracking window, so the
|
|
184
|
-
// conditional subscribes to what its expression read from first render on.
|
|
185
128
|
const trackSub = nodeSubscriberOf(node, 'conditional');
|
|
186
129
|
trackSub.lastScope = state;
|
|
187
130
|
beginTracking(trackSub, overlayKeysOf(state));
|
|
188
131
|
const conditionResult = evaluateCondition(expression, state, startComment.parentElement);
|
|
189
132
|
endTracking();
|
|
190
133
|
|
|
191
|
-
// Determine which branch to mount
|
|
192
134
|
const branchToMount = conditionResult ? branches.if : branches.else;
|
|
193
135
|
|
|
194
|
-
// Mount the appropriate branch
|
|
195
136
|
mountBranch(node, branchToMount, state, manifest, parentScope);
|
|
196
137
|
|
|
197
|
-
// Store active branch reference
|
|
198
138
|
node.runtime.activeBranch = branchToMount;
|
|
199
139
|
};
|
|
200
140
|
|
|
201
|
-
// Mount a specific branch
|
|
202
141
|
const mountBranch = (node, branchData, state, manifest, parentScope) => {
|
|
203
142
|
const { startComment, endComment } = node.meta;
|
|
204
143
|
|
|
205
|
-
// If branch doesn't exist (no else clause), just unmount current
|
|
206
144
|
if (!branchData) {
|
|
207
145
|
unmountBranch(node, manifest);
|
|
208
146
|
return;
|
|
209
147
|
}
|
|
210
148
|
|
|
211
|
-
// Unmount current branch first (if any)
|
|
212
149
|
unmountBranch(node, manifest);
|
|
213
150
|
|
|
214
|
-
// Clone the template element
|
|
215
151
|
const templateContent = branchData.element.childNodes;
|
|
216
152
|
const parent = startComment.parentNode;
|
|
217
153
|
|
|
218
|
-
// Create scoped state (with parent scope if inside iteration)
|
|
219
154
|
const scopedState =
|
|
220
155
|
Object.keys(parentScope).length > 0 ? createScopedState(state, parentScope) : state;
|
|
221
156
|
|
|
222
|
-
// Initialize block (clone, parse, hydrate). Pass the enclosing loop aliases so
|
|
223
|
-
// the re-parse rewrites loop-scoped handlers in this branch — including ones
|
|
224
|
-
// nested deeper in further conditionals, which the parser reaches by carrying
|
|
225
|
-
// the alias set down. `scopeAliases` is set at parse time and persists, so this
|
|
226
|
-
// works on both the initial-render and update (hydrate) mount paths.
|
|
227
157
|
const aliasSet = node.meta.scopeAliases?.length ? new Set(node.meta.scopeAliases) : undefined;
|
|
228
158
|
const {
|
|
229
159
|
element: firstElement,
|
|
@@ -231,44 +161,22 @@ const mountBranch = (node, branchData, state, manifest, parentScope) => {
|
|
|
231
161
|
clonedNodes,
|
|
232
162
|
} = initializeBlock(templateContent, scopedState, null, null, aliasSet);
|
|
233
163
|
|
|
234
|
-
// For conditionals living inside an iteration, route any `<component src>`
|
|
235
|
-
// props through the iteration-prop registry against the active scopedState
|
|
236
|
-
// (which carries the iteration's local vars). Without this, processSingle
|
|
237
|
-
// would inline `@[item.x]` bindings into the component template, where
|
|
238
|
-
// `item` isn't reachable in global scope and props resolve to undefined.
|
|
239
|
-
// Skipped for top-level conditionals because their props reference live
|
|
240
|
-
// global-state bindings — going through the registry would snapshot the
|
|
241
|
-
// value and break reactivity.
|
|
242
164
|
if (node.runtime.inIteration) {
|
|
243
165
|
resolveIterationComponentProps(clonedNodes, scopedState, aliasSet);
|
|
244
166
|
}
|
|
245
167
|
|
|
246
|
-
// Insert cloned nodes into DOM and register in branch registry
|
|
247
168
|
clonedNodes.forEach((clonedNode, i) => {
|
|
248
169
|
parent.insertBefore(clonedNode, endComment);
|
|
249
170
|
branchNodeRegistry.set(clonedNode, { nodes: clonedNodes, index: i });
|
|
250
171
|
if (clonedNode.nodeType === 1) managedNodes.add(clonedNode);
|
|
251
172
|
});
|
|
252
173
|
|
|
253
|
-
// Branch content is mounted outside the enclosing iteration instance's
|
|
254
|
-
// clonedNodes, so it doesn't inherit the instance's `__vibeScope` stamp by DOM
|
|
255
|
-
// ancestry. When this conditional lives inside a loop, stamp `scopedState` —
|
|
256
|
-
// the same scope that hydrates the branch's `@[alias.x]` bindings — onto the
|
|
257
|
-
// branch's root elements so loop-scoped `$scope(this,'alias')` handlers resolve.
|
|
258
|
-
// Gate on `scopeAliases` (set at parse time, persists) rather than the runtime
|
|
259
|
-
// parentScope/inIteration, because the update path (hydrate -> updateConditional)
|
|
260
|
-
// and deeper-nested conditionals mount with an empty parentScope yet still flow
|
|
261
|
-
// the iteration's scoped state in as `state`.
|
|
262
174
|
if (node.meta.scopeAliases?.length) {
|
|
263
175
|
for (let i = 0; i < clonedNodes.length; i++) {
|
|
264
176
|
if (clonedNodes[i].nodeType === 1) clonedNodes[i].__vibeScope = scopedState;
|
|
265
177
|
}
|
|
266
178
|
}
|
|
267
179
|
|
|
268
|
-
// Integrate branch tree into the conditional node's children and manifest.
|
|
269
|
-
// This makes branch content visible to the main update loop (hydrate,
|
|
270
|
-
// renderAllConditionals, renderAllIterations) and to MutationObserver
|
|
271
|
-
// (which looks up parents in the manifest).
|
|
272
180
|
if (branchTree?.children) {
|
|
273
181
|
for (const key in branchTree.children) {
|
|
274
182
|
node.children[key] = branchTree.children[key];
|
|
@@ -282,42 +190,19 @@ const mountBranch = (node, branchData, state, manifest, parentScope) => {
|
|
|
282
190
|
}
|
|
283
191
|
}
|
|
284
192
|
|
|
285
|
-
// Compiled pages inline each component's setup script as type="vibe-module".
|
|
286
|
-
// The boot-time pass only runs scripts present at boot, so a branch that
|
|
287
|
-
// mounts (or re-mounts after unmount) must run its own scripts here to
|
|
288
|
-
// re-register component-local state. Must happen BEFORE rendering nested
|
|
289
|
-
// iterations/conditionals so `<!-- each _cN.x -->` sees the registered state.
|
|
290
193
|
const scriptsPending = executeCompiledComponentScriptsIn(clonedNodes);
|
|
291
194
|
|
|
292
|
-
// Recursively render any nested iterations and conditionals
|
|
293
195
|
if (branchTree) {
|
|
294
196
|
renderAllIterations(branchTree, scopedState, manifest, parentScope);
|
|
295
197
|
renderAllConditionals(branchTree, scopedState, manifest, parentScope);
|
|
296
198
|
}
|
|
297
199
|
|
|
298
|
-
// Store active instance
|
|
299
200
|
node.runtime.activeInstance = {
|
|
300
201
|
branch: branchData,
|
|
301
202
|
nodes: clonedNodes,
|
|
302
203
|
parsedTree: branchTree,
|
|
303
204
|
};
|
|
304
205
|
|
|
305
|
-
// Import-bearing branch scripts run async through the script chain, so the
|
|
306
|
-
// eager render above may evaluate expressions before the window helpers those
|
|
307
|
-
// scripts define exist — and defining a global is not a state write, so no
|
|
308
|
-
// flush ever re-dispatches them (the fetch path never has this gap: it mounts
|
|
309
|
-
// DOM only after its scripts ran). Finish the mount when the chain settles:
|
|
310
|
-
// re-render this branch's directives against the fully-scripted world.
|
|
311
|
-
// Idempotent — rendered iterations early-return, value-unchanged conditionals
|
|
312
|
-
// are a no-op — and skipped when a remount superseded this instance.
|
|
313
|
-
//
|
|
314
|
-
// The settle evaluates CURRENT state (the live `$`), never the mount-time
|
|
315
|
-
// snapshot: state that legitimately changed while the imports were pending
|
|
316
|
-
// (a nested gate flipping true off a socket flush) already dispatched and
|
|
317
|
-
// mounted — settling against the stale snapshot would unmount it again.
|
|
318
|
-
// Loop-scoped branches keep their mount scope (the row's aliases aren't
|
|
319
|
-
// reachable from the root proxy); a row update supersedes activeInstance
|
|
320
|
-
// and the guard skips the settle entirely.
|
|
321
206
|
if (scriptsPending && branchTree) {
|
|
322
207
|
scriptsPending.then(() => {
|
|
323
208
|
if (node.runtime.activeInstance?.nodes !== clonedNodes) return;
|
|
@@ -328,13 +213,11 @@ const mountBranch = (node, branchData, state, manifest, parentScope) => {
|
|
|
328
213
|
}
|
|
329
214
|
};
|
|
330
215
|
|
|
331
|
-
// Unmount currently active branch
|
|
332
216
|
const unmountBranch = (node, manifest) => {
|
|
333
217
|
const { activeInstance } = node.runtime;
|
|
334
218
|
|
|
335
219
|
if (!activeInstance) return;
|
|
336
220
|
|
|
337
|
-
// Remove branch children from the conditional node's tree and manifest
|
|
338
221
|
if (activeInstance.parsedTree?.children) {
|
|
339
222
|
const condPath = manifest ? findConditionalPath(node, manifest) : null;
|
|
340
223
|
|
|
@@ -346,13 +229,6 @@ const unmountBranch = (node, manifest) => {
|
|
|
346
229
|
}
|
|
347
230
|
}
|
|
348
231
|
|
|
349
|
-
// The conditional owns everything between its comments. That's more than
|
|
350
|
-
// activeInstance.nodes: nested directives at the branch's top level (each
|
|
351
|
-
// rows, deeper if branches) insert their rendered output between their own
|
|
352
|
-
// comments after the branch mounts, so it never appears in the original
|
|
353
|
-
// clonedNodes list. Sweep the live range (same pattern as iteration
|
|
354
|
-
// teardown), collecting componentIds BEFORE detaching so we can check
|
|
355
|
-
// after removal whether any live DOM still holds them.
|
|
356
232
|
const { startComment, endComment } = node.meta;
|
|
357
233
|
const ids = new Set();
|
|
358
234
|
let current = startComment.nextSibling;
|
|
@@ -366,22 +242,12 @@ const unmountBranch = (node, manifest) => {
|
|
|
366
242
|
current = next;
|
|
367
243
|
}
|
|
368
244
|
|
|
369
|
-
// CLEANUP OF CURRENT STATE
|
|
370
245
|
releaseOrphanedComponentState(ids);
|
|
371
|
-
// The swept range's binding subscribers are dead — their anchors just left
|
|
372
|
-
// the document. (The conditional's own subscriber survives: its anchor is
|
|
373
|
-
// the start comment, which stays.)
|
|
374
246
|
pruneDisconnected();
|
|
375
247
|
|
|
376
|
-
// Clear active instance
|
|
377
248
|
node.runtime.activeInstance = null;
|
|
378
249
|
};
|
|
379
250
|
|
|
380
|
-
// Subscription dispatch: flip the branch if the expression's value moved,
|
|
381
|
-
// nothing else. Branch CONTENT updates arrive through the content's own
|
|
382
|
-
// subscribers — re-walking the branch here (updateConditional's else path)
|
|
383
|
-
// would reintroduce a partial walk per dirty conditional. A mounting branch
|
|
384
|
-
// registers its fresh subscribers through initializeBlock's hydrate pass.
|
|
385
251
|
export const dispatchConditional = (node, newState, manifest, parentScope = {}) => {
|
|
386
252
|
if (!node.runtime.templateRemoved) return;
|
|
387
253
|
const sub = nodeSubscriberOf(node, 'conditional');
|
|
@@ -396,17 +262,13 @@ export const dispatchConditional = (node, newState, manifest, parentScope = {})
|
|
|
396
262
|
}
|
|
397
263
|
};
|
|
398
264
|
|
|
399
|
-
// Update conditional when dependencies change
|
|
400
265
|
export const updateConditional = (node, newState, oldState, manifest, parentScope = {}) => {
|
|
401
266
|
const { expression, branches, startComment } = node.meta;
|
|
402
267
|
|
|
403
|
-
// If not yet rendered, skip (renderConditional handles initial render)
|
|
404
268
|
if (!node.runtime.templateRemoved) {
|
|
405
269
|
return;
|
|
406
270
|
}
|
|
407
271
|
|
|
408
|
-
// Evaluate expression with new state — re-records the subscription every
|
|
409
|
-
// update, so branch-dependent reads self-heal.
|
|
410
272
|
const trackSub = nodeSubscriberOf(node, 'conditional');
|
|
411
273
|
trackSub.lastScope = newState;
|
|
412
274
|
beginTracking(trackSub, overlayKeysOf(newState));
|
|
@@ -414,16 +276,12 @@ export const updateConditional = (node, newState, oldState, manifest, parentScop
|
|
|
414
276
|
endTracking();
|
|
415
277
|
const newBranchData = newConditionResult ? branches.if : branches.else;
|
|
416
278
|
|
|
417
|
-
// Check if branch changed (compare references)
|
|
418
279
|
const branchChanged = node.runtime.activeBranch !== newBranchData;
|
|
419
280
|
|
|
420
281
|
if (branchChanged) {
|
|
421
|
-
// Switch branches
|
|
422
282
|
mountBranch(node, newBranchData, newState, manifest, parentScope);
|
|
423
283
|
node.runtime.activeBranch = newBranchData;
|
|
424
284
|
} else {
|
|
425
|
-
// Same branch, but state might have changed — rehydrate bindings
|
|
426
|
-
// and update nested conditionals/iterations
|
|
427
285
|
const { activeInstance } = node.runtime;
|
|
428
286
|
|
|
429
287
|
if (activeInstance && activeInstance.parsedTree) {
|
package/runtime/constants.js
CHANGED
|
@@ -1,49 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
// package.json, the READMEs and the CHANGELOG.
|
|
3
|
-
export const VERSION = '4.0.1';
|
|
1
|
+
export const VERSION = '4.1.1';
|
|
4
2
|
|
|
5
|
-
// Debug logger name
|
|
6
3
|
export const DEBUGGER_NAME = '[vibe-debug]:';
|
|
7
|
-
export const FOUC_CLASS_OR_ATTR = 'vibe-fouc';
|
|
8
|
-
export const DEHYDRATE_CLASS_OR_ATTR = 'vibe-dehydrate';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export const
|
|
13
|
-
export const
|
|
14
|
-
|
|
15
|
-
export const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export const
|
|
19
|
-
export const
|
|
20
|
-
export const
|
|
21
|
-
export const
|
|
22
|
-
export const
|
|
23
|
-
export const
|
|
24
|
-
|
|
25
|
-
export const PHASE_MUTATE = 'Mutation'; // DOM mutations detected by observer (index.js)
|
|
26
|
-
export const PHASE_HYPERSPEED = 'Hyperspeed'; // Restores @[...] markers from pre-compiled manifest (pre-compiled-manifest.js)
|
|
27
|
-
|
|
28
|
-
// Elements that should not have reactive bindings
|
|
29
|
-
// Note: COMPONENT is NOT in this list - inline component wrappers need to be parsed
|
|
30
|
-
// Fetched components (<component src="">) are handled separately by processComponent()
|
|
4
|
+
export const FOUC_CLASS_OR_ATTR = 'vibe-fouc';
|
|
5
|
+
export const DEHYDRATE_CLASS_OR_ATTR = 'vibe-dehydrate';
|
|
6
|
+
|
|
7
|
+
export const PHASE_ATTACH = 'Attached';
|
|
8
|
+
export const PHASE_MANIFEST = 'Manifested';
|
|
9
|
+
export const PHASE_OBSERVE = 'Observer';
|
|
10
|
+
export const PHASE_READY = 'Ready';
|
|
11
|
+
|
|
12
|
+
export const PHASE_PARSE = 'Parsed';
|
|
13
|
+
export const PHASE_HYDRATE = 'Hydrated';
|
|
14
|
+
export const PHASE_ITERATE = 'Iterated';
|
|
15
|
+
export const PHASE_CONDITION = 'Evaluated';
|
|
16
|
+
export const PHASE_FETCH = 'Fetched';
|
|
17
|
+
export const PHASE_FETCH_CACHED = 'Fet(ca)ched';
|
|
18
|
+
export const PHASE_UPDATE = 'Proxy';
|
|
19
|
+
export const PHASE_MUTATE = 'Mutation';
|
|
20
|
+
export const PHASE_HYPERSPEED = 'Hyperspeed';
|
|
21
|
+
|
|
31
22
|
export const NON_REACTIVE_ELEMENTS = ['SCRIPT', 'HEAD', 'PRE'];
|
|
32
23
|
|
|
33
|
-
// Elements whose src-family attributes the browser fetches the moment they are
|
|
34
|
-
// set — a raw `src="@[binding]"` reaching one of these fires a network request
|
|
35
|
-
// for the literal binding text before hydration can stamp the real value.
|
|
36
|
-
// Bindings on them are parked on data-vibe-<attr> (the established transport)
|
|
37
|
-
// until hydration writes the evaluated URL. <component src> is NOT here — its
|
|
38
|
-
// src is consumed by processComponent, never by the browser.
|
|
39
24
|
export const FETCH_SRC_ATTRS = ['src', 'srcset', 'poster'];
|
|
40
25
|
export const FETCH_SRC_SELECTOR = 'img, source, iframe, video, audio, embed, track';
|
|
41
26
|
export const FETCH_SRC_ELEMENTS = ['IMG', 'SOURCE', 'IFRAME', 'VIDEO', 'AUDIO', 'EMBED', 'TRACK'];
|
|
42
27
|
|
|
43
|
-
// Attributes where the string value is meaningful (should NOT be removed when falsy)
|
|
44
|
-
// All other attributes are treated as boolean-like (removed when falsy, present when truthy)
|
|
45
28
|
export const VALUE_ATTRS = [
|
|
46
|
-
// Global attributes
|
|
47
29
|
'class',
|
|
48
30
|
'style',
|
|
49
31
|
'id',
|
|
@@ -59,7 +41,6 @@ export const VALUE_ATTRS = [
|
|
|
59
41
|
'popover',
|
|
60
42
|
'anchor',
|
|
61
43
|
|
|
62
|
-
// Enumerated (take specific string values, not truly boolean)
|
|
63
44
|
'contenteditable',
|
|
64
45
|
'draggable',
|
|
65
46
|
'spellcheck',
|
|
@@ -69,7 +50,6 @@ export const VALUE_ATTRS = [
|
|
|
69
50
|
'enterkeyhint',
|
|
70
51
|
'virtualkeyboardpolicy',
|
|
71
52
|
|
|
72
|
-
// URLs and sources
|
|
73
53
|
'href',
|
|
74
54
|
'src',
|
|
75
55
|
'action',
|
|
@@ -84,7 +64,6 @@ export const VALUE_ATTRS = [
|
|
|
84
64
|
'manifest',
|
|
85
65
|
'codebase',
|
|
86
66
|
|
|
87
|
-
// Form attributes
|
|
88
67
|
'name',
|
|
89
68
|
'type',
|
|
90
69
|
'value',
|
|
@@ -109,13 +88,11 @@ export const VALUE_ATTRS = [
|
|
|
109
88
|
'for',
|
|
110
89
|
'dirname',
|
|
111
90
|
|
|
112
|
-
// Text/accessibility
|
|
113
91
|
'alt',
|
|
114
92
|
'label',
|
|
115
93
|
'summary',
|
|
116
94
|
'abbr',
|
|
117
95
|
|
|
118
|
-
// Dimensions and layout
|
|
119
96
|
'width',
|
|
120
97
|
'height',
|
|
121
98
|
'cols',
|
|
@@ -127,7 +104,6 @@ export const VALUE_ATTRS = [
|
|
|
127
104
|
'high',
|
|
128
105
|
'optimum',
|
|
129
106
|
|
|
130
|
-
// Link/resource hints
|
|
131
107
|
'target',
|
|
132
108
|
'rel',
|
|
133
109
|
'hreflang',
|
|
@@ -146,81 +122,42 @@ export const VALUE_ATTRS = [
|
|
|
146
122
|
'imagesizes',
|
|
147
123
|
'sizes',
|
|
148
124
|
|
|
149
|
-
// Media
|
|
150
125
|
'preload',
|
|
151
126
|
'kind',
|
|
152
127
|
'srclang',
|
|
153
128
|
|
|
154
|
-
// Meta
|
|
155
129
|
'content',
|
|
156
130
|
'http-equiv',
|
|
157
131
|
|
|
158
|
-
// iframe/embed
|
|
159
132
|
'sandbox',
|
|
160
133
|
'allow',
|
|
161
134
|
'srcdoc',
|
|
162
135
|
'credentialless',
|
|
163
136
|
|
|
164
|
-
// Table
|
|
165
137
|
'headers',
|
|
166
138
|
'scope',
|
|
167
139
|
|
|
168
|
-
// Datetime
|
|
169
140
|
'datetime',
|
|
170
141
|
|
|
171
|
-
// Object/embed legacy
|
|
172
142
|
'coords',
|
|
173
143
|
'shape',
|
|
174
144
|
];
|
|
175
145
|
|
|
176
|
-
// Properties that should be set directly on the DOM element (not as attributes)
|
|
177
146
|
export const DOM_PROPERTIES = ['value', 'checked', 'selected'];
|
|
178
147
|
|
|
179
|
-
|
|
180
|
-
// Regex for matching reactive bindings (@[expression])
|
|
181
|
-
// Supports nested brackets, single-quoted and double-quoted strings inside expressions:
|
|
182
|
-
// @[items[0]], @[obj[key]], @[x.replace('.png', '-mugshot.png')]
|
|
183
148
|
const BINDING_INNER = String.raw`(?:[^\[\]'"]|\[[^\]]*\]|'[^']*'|"[^"]*")+`;
|
|
184
149
|
export const BINDING_REGEX = new RegExp(String.raw`\@\[(${BINDING_INNER})\]`, 'g');
|
|
185
150
|
|
|
186
|
-
// Regex for detecting a pure binding (entire value is just @[expression])
|
|
187
151
|
export const PURE_BINDING_REGEX = new RegExp(String.raw`^\@\[(${BINDING_INNER})\]$`);
|
|
188
152
|
|
|
189
|
-
// Regex for parsing iteration comment syntax. Supported forms — the index and
|
|
190
|
-
// the key are each optional and may appear in EITHER order:
|
|
191
|
-
// <!-- each items as item -->
|
|
192
|
-
// <!-- each items as item, index -->
|
|
193
|
-
// <!-- each items as item (item.id) --> // explicit key
|
|
194
|
-
// <!-- each items as item (item.id), index --> // key, then index
|
|
195
|
-
// <!-- each items as item, index (item.id) --> // index, then key
|
|
196
|
-
// Capture groups: arrayPath, itemAlias, keyBeforeIndex (optional), indexAlias
|
|
197
|
-
// (optional), keyAfterIndex (optional). The key lands in group 3 when written
|
|
198
|
-
// before the index and in group 5 when written after; parseIterationHeader
|
|
199
|
-
// coalesces the two — prefer that helper over destructuring the raw match.
|
|
200
|
-
// The array expression can be any JS: a state path, a window global, a method
|
|
201
|
-
// call, or an inline literal. The optional key expression is evaluated per
|
|
202
|
-
// item against scoped state to produce a stable identity for diffing — this
|
|
203
|
-
// keeps survivors stable when earlier items are removed (otherwise the
|
|
204
|
-
// fallback hash key embeds the index and triggers bulk re-render).
|
|
205
153
|
export const ITERATION_REGEX = /^each\s+(.+)\s+as\s+(\w+)(?:\s*\(\s*([^)]+?)\s*\))?(?:\s*,\s*(\w+))?(?:\s*\(\s*([^)]+?)\s*\))?\s*$/;
|
|
206
154
|
|
|
207
|
-
// Regex for detecting start of iteration comment
|
|
208
155
|
export const ITERATION_START_REGEX = /^each\s+/;
|
|
209
156
|
|
|
210
|
-
// Regex for parsing conditional comment syntax (<!-- if expression -->)
|
|
211
157
|
export const CONDITIONAL_REGEX = /^if\s+(.+)$/;
|
|
212
158
|
|
|
213
|
-
// Regex for detecting start of conditional comment
|
|
214
159
|
export const CONDITIONAL_START_REGEX = /^if\s+/;
|
|
215
160
|
|
|
216
|
-
// Regex for rewriting component-local `this.X` references to the component's
|
|
217
|
-
// state path. Captures the leading identifier only — any trailing `.Y.Z`
|
|
218
|
-
// chain stays attached after replacement, so `this.user.name` becomes
|
|
219
|
-
// `<componentId>.user.name`. Used in expression bodies (bindings, event
|
|
220
|
-
// handlers, conditional/iteration directives).
|
|
221
161
|
export const THIS_PROP_REGEX = /\bthis\.(\w+)/g;
|
|
222
162
|
|
|
223
|
-
// Regex for rewriting `$.this.X` writes (proxy assignment from event handlers)
|
|
224
|
-
// to the component's write path. Same prefix-only semantics as THIS_PROP_REGEX
|
|
225
|
-
// — `$.this.user.name = x` becomes `$.<componentId>.user.name = x`.
|
|
226
163
|
export const STATE_THIS_PROP_REGEX = /\$\.this\.(\w+)/g;
|