@ape-egg/vibe 1.6.1 → 1.7.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/CHANGELOG.md +44 -1
- package/README.md +19 -8
- package/boot.js +11 -0
- package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
- package/compiler/src/Cargo.lock +1 -1
- package/compiler/src/Cargo.toml +1 -1
- package/compiler/src/compiler/compile.rs +38 -12
- package/compiler/src/compiler/component_tagger.rs +22 -3
- package/compiler/src/compiler/value_stamper.rs +75 -5
- package/compiler/src/compiler/watcher.rs +69 -2
- package/compiler/src/config.rs +10 -0
- package/compiler/src/main.rs +39 -15
- package/compiler/src/parser/html.rs +61 -9
- package/index.js +34 -5
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/runtime/cleanup.js +0 -9
- package/runtime/component.js +14 -18
- package/runtime/index.js +13 -2
- package/runtime/iterate.js +63 -15
- package/runtime/parse.js +3 -2
- package/runtime/pre-compiled-manifest.js +111 -58
|
@@ -43,7 +43,7 @@ export const buildHyperspeedManifest = (parsedTree) => {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// Check if parsed string contains bindings - split into array
|
|
46
|
-
if (typeof node.parsed ===
|
|
46
|
+
if (typeof node.parsed === "string" && node.parsed.includes("@[")) {
|
|
47
47
|
const parsedArray = splitByMarkers(node.parsed);
|
|
48
48
|
if (parsedArray) {
|
|
49
49
|
result.hyperspeedRestoration = {
|
|
@@ -53,10 +53,10 @@ export const buildHyperspeedManifest = (parsedTree) => {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// Check for attribute bindings
|
|
56
|
-
if (node.attributes && typeof node.attributes ===
|
|
56
|
+
if (node.attributes && typeof node.attributes === "object") {
|
|
57
57
|
const attrBindings = {};
|
|
58
58
|
for (const [key, value] of Object.entries(node.attributes)) {
|
|
59
|
-
if (typeof value ===
|
|
59
|
+
if (typeof value === "string" && value.includes("@[")) {
|
|
60
60
|
attrBindings[key] = value;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -69,7 +69,7 @@ export const buildHyperspeedManifest = (parsedTree) => {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// For iterations, extract template as HTML string
|
|
72
|
-
if (node.type ===
|
|
72
|
+
if (node.type === "iteration" && node.meta?.template?.element) {
|
|
73
73
|
const templateElement = node.meta.template.element;
|
|
74
74
|
if (templateElement && templateElement.innerHTML) {
|
|
75
75
|
if (!result.hyperspeedRestoration) {
|
|
@@ -80,7 +80,7 @@ export const buildHyperspeedManifest = (parsedTree) => {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
// For conditionals, extract branch templates as HTML strings
|
|
83
|
-
if (node.type ===
|
|
83
|
+
if (node.type === "conditional" && node.meta?.branches) {
|
|
84
84
|
const trueBranch = node.meta.branches.true?.element;
|
|
85
85
|
const falseBranch = node.meta.branches.false?.element;
|
|
86
86
|
|
|
@@ -98,7 +98,7 @@ export const buildHyperspeedManifest = (parsedTree) => {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// Recursively process children (skip runtime-only nodes)
|
|
101
|
-
if (node.children && typeof node.children ===
|
|
101
|
+
if (node.children && typeof node.children === "object") {
|
|
102
102
|
for (const [key, childNode] of Object.entries(node.children)) {
|
|
103
103
|
result.children[key] = walkNode(childNode);
|
|
104
104
|
}
|
|
@@ -116,8 +116,8 @@ export const buildHyperspeedManifest = (parsedTree) => {
|
|
|
116
116
|
element: null,
|
|
117
117
|
parsed: [],
|
|
118
118
|
children: walkNode(parsedTree).children, // Use children directly
|
|
119
|
-
}
|
|
120
|
-
}
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
121
|
};
|
|
122
122
|
};
|
|
123
123
|
|
|
@@ -138,19 +138,19 @@ const detectHyperspeed = async () => {
|
|
|
138
138
|
let pagePath = window.location.pathname;
|
|
139
139
|
|
|
140
140
|
// Normalize path: handle directory URLs and missing extensions
|
|
141
|
-
if (pagePath.endsWith(
|
|
141
|
+
if (pagePath.endsWith("/")) {
|
|
142
142
|
// /compiled/ -> /compiled/index.html
|
|
143
|
-
pagePath = pagePath +
|
|
144
|
-
} else if (!pagePath.includes(
|
|
143
|
+
pagePath = pagePath + "index.html";
|
|
144
|
+
} else if (!pagePath.includes(".")) {
|
|
145
145
|
// /compiled/mypage -> /compiled/mypage.html
|
|
146
|
-
const lastSlash = pagePath.lastIndexOf(
|
|
146
|
+
const lastSlash = pagePath.lastIndexOf("/");
|
|
147
147
|
const lastSegment = pagePath.substring(lastSlash + 1);
|
|
148
|
-
if (lastSegment && !lastSegment.includes(
|
|
149
|
-
pagePath = pagePath +
|
|
148
|
+
if (lastSegment && !lastSegment.includes(".")) {
|
|
149
|
+
pagePath = pagePath + ".html";
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
const pathSegments = pagePath.split(
|
|
153
|
+
const pathSegments = pagePath.split("/").filter((s) => s);
|
|
154
154
|
|
|
155
155
|
if (pathSegments.length === 0) return null;
|
|
156
156
|
|
|
@@ -165,9 +165,11 @@ const detectHyperspeed = async () => {
|
|
|
165
165
|
// Strategy 1: vibe-hyperspeed at the same level as parent directory
|
|
166
166
|
// /compiled/playground/test.html -> /compiled/vibe-hyperspeed/playground/test.html.manifest.js
|
|
167
167
|
if (dirSegments.length >= 1) {
|
|
168
|
-
const subPath = dirSegments.slice(1).join(
|
|
169
|
-
const baseDir =
|
|
170
|
-
possiblePaths.push(
|
|
168
|
+
const subPath = dirSegments.slice(1).join("/"); // Everything after first dir
|
|
169
|
+
const baseDir = "/" + dirSegments[0]; // First directory segment
|
|
170
|
+
possiblePaths.push(
|
|
171
|
+
`${baseDir}/vibe-hyperspeed/${subPath ? subPath + "/" : ""}${fileName}.manifest.js`,
|
|
172
|
+
);
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
// Strategy 2: vibe-hyperspeed at web root (original behavior)
|
|
@@ -177,8 +179,10 @@ const detectHyperspeed = async () => {
|
|
|
177
179
|
// Strategy 3: vibe-hyperspeed relative to immediate parent
|
|
178
180
|
// /playground/test.html -> /vibe-hyperspeed/playground/test.html.manifest.js
|
|
179
181
|
if (dirSegments.length > 0) {
|
|
180
|
-
const relativePath = dirSegments.join(
|
|
181
|
-
possiblePaths.push(
|
|
182
|
+
const relativePath = dirSegments.join("/");
|
|
183
|
+
possiblePaths.push(
|
|
184
|
+
`/vibe-hyperspeed/${relativePath}/${fileName}.manifest.js`,
|
|
185
|
+
);
|
|
182
186
|
}
|
|
183
187
|
|
|
184
188
|
// Try each possible path
|
|
@@ -187,7 +191,7 @@ const detectHyperspeed = async () => {
|
|
|
187
191
|
const module = await import(manifestPath);
|
|
188
192
|
hyperspeedData = {
|
|
189
193
|
manifest: module.default,
|
|
190
|
-
path: manifestPath
|
|
194
|
+
path: manifestPath,
|
|
191
195
|
};
|
|
192
196
|
return hyperspeedData;
|
|
193
197
|
} catch (e) {
|
|
@@ -217,7 +221,11 @@ const detectHyperspeed = async () => {
|
|
|
217
221
|
* @param {Object} subtree - The matching subtree from manifest (e.g., manifest.children.body)
|
|
218
222
|
* @param {Object} fullManifest - The full manifest (unused now, kept for compatibility)
|
|
219
223
|
*/
|
|
220
|
-
export const restoreMarkersFromManifest = (
|
|
224
|
+
export const restoreMarkersFromManifest = (
|
|
225
|
+
rootElement,
|
|
226
|
+
subtree,
|
|
227
|
+
fullManifest = null,
|
|
228
|
+
) => {
|
|
221
229
|
const walkTree = (tree, element) => {
|
|
222
230
|
if (!tree || !element) return;
|
|
223
231
|
|
|
@@ -227,17 +235,25 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
227
235
|
if (restoration) {
|
|
228
236
|
// Restore text content with markers if parsed contains bindings
|
|
229
237
|
if (restoration.parsed && Array.isArray(restoration.parsed)) {
|
|
230
|
-
const hasBindings = restoration.parsed.some(
|
|
231
|
-
typeof item ===
|
|
238
|
+
const hasBindings = restoration.parsed.some(
|
|
239
|
+
(item) => typeof item === "string" && item.includes("@["),
|
|
232
240
|
);
|
|
233
241
|
|
|
234
242
|
if (hasBindings) {
|
|
235
243
|
// Reconstruct original content with markers
|
|
236
|
-
|
|
244
|
+
let originalContent = restoration.parsed.join("");
|
|
245
|
+
|
|
246
|
+
// Transform component-scoped bindings back to this. format
|
|
247
|
+
// Compiler transforms @[this.count] → @[_c0.count] for stamping
|
|
248
|
+
// Runtime expects @[this.count], so transform back
|
|
249
|
+
originalContent = originalContent.replace(/@\[_c\d+\./g, "@[this.");
|
|
237
250
|
|
|
238
251
|
// For text nodes, update parent's innerHTML
|
|
239
252
|
// For elements with children, update only text nodes
|
|
240
|
-
if (
|
|
253
|
+
if (
|
|
254
|
+
element.childNodes.length === 1 &&
|
|
255
|
+
element.childNodes[0].nodeType === 3
|
|
256
|
+
) {
|
|
241
257
|
// Single text node - replace it
|
|
242
258
|
element.childNodes[0].textContent = originalContent;
|
|
243
259
|
} else if (element.childNodes.length === 0) {
|
|
@@ -253,7 +269,13 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
253
269
|
// 1. Boolean-like attributes with falsy values were removed - restore them
|
|
254
270
|
// 2. Value attributes were stamped - replace stamped values with markers
|
|
255
271
|
if (restoration.attributes) {
|
|
256
|
-
for (
|
|
272
|
+
for (let [attrName, attrValue] of Object.entries(
|
|
273
|
+
restoration.attributes,
|
|
274
|
+
)) {
|
|
275
|
+
// Transform component-scoped bindings back to this. format
|
|
276
|
+
// Compiler transforms @[this.count] → @[_c0.count], runtime expects @[this.count]
|
|
277
|
+
attrValue = attrValue.replace(/@\[_c\d+\./g, "@[this.");
|
|
278
|
+
|
|
257
279
|
// Always set the attribute to restore the marker
|
|
258
280
|
// - If missing (boolean-like, falsy): adds it back
|
|
259
281
|
// - If present (value attr, stamped): replaces stamped value with marker
|
|
@@ -288,17 +310,22 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
288
310
|
// Text nodes use childNodes indices which become invalid after DOM modifications
|
|
289
311
|
if (tree.children) {
|
|
290
312
|
for (const key in tree.children) {
|
|
291
|
-
if (key.startsWith(
|
|
313
|
+
if (key.startsWith("text_")) {
|
|
292
314
|
const childTree = tree.children[key];
|
|
293
315
|
const restoration = childTree.compiled?.restoration;
|
|
294
316
|
|
|
295
317
|
if (restoration?.parsed && Array.isArray(restoration.parsed)) {
|
|
296
|
-
const hasBindings = restoration.parsed.some(
|
|
297
|
-
typeof item ===
|
|
318
|
+
const hasBindings = restoration.parsed.some(
|
|
319
|
+
(item) => typeof item === "string" && item.includes("@["),
|
|
298
320
|
);
|
|
299
321
|
|
|
300
322
|
if (hasBindings) {
|
|
301
|
-
|
|
323
|
+
// Transform component-scoped bindings back to this. format
|
|
324
|
+
let originalContent = restoration.parsed.join("");
|
|
325
|
+
originalContent = originalContent.replace(
|
|
326
|
+
/@\[_c\d+\./g,
|
|
327
|
+
"@[this.",
|
|
328
|
+
);
|
|
302
329
|
|
|
303
330
|
// Extract index from key (e.g., text_0 -> 0)
|
|
304
331
|
const match = key.match(/_(\d+)$/);
|
|
@@ -329,7 +356,7 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
329
356
|
const childTree = tree.children[key];
|
|
330
357
|
|
|
331
358
|
// Handle iteration restoration
|
|
332
|
-
if (childTree.type ===
|
|
359
|
+
if (childTree.type === "iteration" && childTree.compiled?.restoration) {
|
|
333
360
|
const restoration = childTree.compiled.restoration;
|
|
334
361
|
|
|
335
362
|
// Find iteration comment by matching the expression
|
|
@@ -337,7 +364,10 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
337
364
|
let endComment = null;
|
|
338
365
|
let depth = 0;
|
|
339
366
|
|
|
340
|
-
const walker = document.createTreeWalker(
|
|
367
|
+
const walker = document.createTreeWalker(
|
|
368
|
+
element,
|
|
369
|
+
NodeFilter.SHOW_COMMENT,
|
|
370
|
+
);
|
|
341
371
|
while (walker.nextNode()) {
|
|
342
372
|
const comment = walker.currentNode;
|
|
343
373
|
const trimmed = comment.textContent.trim();
|
|
@@ -351,9 +381,9 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
351
381
|
startComment = comment;
|
|
352
382
|
depth = 1;
|
|
353
383
|
} else if (startComment) {
|
|
354
|
-
if (trimmed.startsWith(
|
|
384
|
+
if (trimmed.startsWith("each ")) {
|
|
355
385
|
depth++;
|
|
356
|
-
} else if (trimmed ===
|
|
386
|
+
} else if (trimmed === "/each") {
|
|
357
387
|
depth--;
|
|
358
388
|
if (depth === 0) {
|
|
359
389
|
endComment = comment;
|
|
@@ -363,6 +393,10 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
363
393
|
}
|
|
364
394
|
}
|
|
365
395
|
|
|
396
|
+
// Always delete the iteration node from tree - runtime will re-create it
|
|
397
|
+
// Do this even if comments weren't found (they might have been removed by parent restoration)
|
|
398
|
+
delete tree.children[key];
|
|
399
|
+
|
|
366
400
|
if (startComment && endComment) {
|
|
367
401
|
// Mark as processed
|
|
368
402
|
startComment._vibeProcessed = true;
|
|
@@ -382,7 +416,7 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
382
416
|
}
|
|
383
417
|
|
|
384
418
|
// Insert the template (single item)
|
|
385
|
-
const tempContainer = document.createElement(
|
|
419
|
+
const tempContainer = document.createElement("div");
|
|
386
420
|
tempContainer.innerHTML = restoration.template;
|
|
387
421
|
|
|
388
422
|
const fragment = document.createDocumentFragment();
|
|
@@ -392,30 +426,35 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
392
426
|
parent.insertBefore(fragment, endComment);
|
|
393
427
|
}
|
|
394
428
|
|
|
395
|
-
// Delete the iteration node from tree - runtime will re-create it
|
|
396
|
-
delete tree.children[key];
|
|
397
429
|
continue;
|
|
398
430
|
}
|
|
399
431
|
|
|
400
|
-
if (
|
|
432
|
+
if (
|
|
433
|
+
childTree.type === "conditional" &&
|
|
434
|
+
childTree.compiled?.restoration
|
|
435
|
+
) {
|
|
401
436
|
const restoration = childTree.compiled.restoration;
|
|
402
437
|
|
|
403
438
|
// Find conditional comment markers in the current element
|
|
404
439
|
// But SKIP conditionals that are inside iteration blocks
|
|
405
|
-
const walker = document.createTreeWalker(
|
|
440
|
+
const walker = document.createTreeWalker(
|
|
441
|
+
element,
|
|
442
|
+
NodeFilter.SHOW_COMMENT,
|
|
443
|
+
);
|
|
406
444
|
let startComment = null;
|
|
407
445
|
let endComment = null;
|
|
408
446
|
let insideIteration = false;
|
|
447
|
+
let conditionalDepth = 0;
|
|
409
448
|
|
|
410
449
|
while (walker.nextNode()) {
|
|
411
450
|
const comment = walker.currentNode;
|
|
412
451
|
const text = comment.textContent.trim();
|
|
413
452
|
|
|
414
453
|
// Track if we're inside an iteration block
|
|
415
|
-
if (text.startsWith(
|
|
454
|
+
if (text.startsWith("each ")) {
|
|
416
455
|
insideIteration = true;
|
|
417
456
|
continue;
|
|
418
|
-
} else if (text ===
|
|
457
|
+
} else if (text === "/each") {
|
|
419
458
|
insideIteration = false;
|
|
420
459
|
continue;
|
|
421
460
|
}
|
|
@@ -426,14 +465,27 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
426
465
|
// Skip already processed conditionals
|
|
427
466
|
if (comment._vibeProcessed) continue;
|
|
428
467
|
|
|
429
|
-
if (text.startsWith(
|
|
430
|
-
startComment
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
468
|
+
if (text.startsWith("if")) {
|
|
469
|
+
if (!startComment) {
|
|
470
|
+
startComment = comment;
|
|
471
|
+
conditionalDepth = 1;
|
|
472
|
+
} else {
|
|
473
|
+
// Track nested conditionals
|
|
474
|
+
conditionalDepth++;
|
|
475
|
+
}
|
|
476
|
+
} else if (text === "/if" && startComment) {
|
|
477
|
+
conditionalDepth--;
|
|
478
|
+
if (conditionalDepth === 0) {
|
|
479
|
+
endComment = comment;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
434
482
|
}
|
|
435
483
|
}
|
|
436
484
|
|
|
485
|
+
// Always delete the conditional node from tree - runtime will re-create it
|
|
486
|
+
// Do this even if comments weren't found (they might have been removed by parent restoration)
|
|
487
|
+
delete tree.children[key];
|
|
488
|
+
|
|
437
489
|
if (startComment && endComment) {
|
|
438
490
|
// Mark as processed
|
|
439
491
|
startComment._vibeProcessed = true;
|
|
@@ -444,12 +496,12 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
444
496
|
let current = startComment.nextSibling;
|
|
445
497
|
while (current && current !== endComment) {
|
|
446
498
|
const next = current.nextSibling;
|
|
447
|
-
current.remove();
|
|
499
|
+
current.remove(); // Remove ALL nodes, including comment nodes
|
|
448
500
|
current = next;
|
|
449
501
|
}
|
|
450
502
|
|
|
451
503
|
// Insert the template content BEFORE endComment
|
|
452
|
-
const tempContainer = document.createElement(
|
|
504
|
+
const tempContainer = document.createElement("div");
|
|
453
505
|
tempContainer.innerHTML = restoration.template;
|
|
454
506
|
|
|
455
507
|
const fragment = document.createDocumentFragment();
|
|
@@ -458,10 +510,6 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
458
510
|
}
|
|
459
511
|
endComment.parentNode.insertBefore(fragment, endComment);
|
|
460
512
|
}
|
|
461
|
-
|
|
462
|
-
// Delete restoration data AND remove the conditional node from tree
|
|
463
|
-
// Runtime will re-create it from the restored DOM
|
|
464
|
-
delete tree.children[key];
|
|
465
513
|
}
|
|
466
514
|
}
|
|
467
515
|
}
|
|
@@ -472,28 +520,33 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
472
520
|
const childTree = tree.children[key];
|
|
473
521
|
|
|
474
522
|
// Skip conditionals and iterations (already processed/deleted above)
|
|
475
|
-
if (
|
|
523
|
+
if (
|
|
524
|
+
childTree.type === "conditional" ||
|
|
525
|
+
childTree.type === "iteration"
|
|
526
|
+
) {
|
|
476
527
|
continue;
|
|
477
528
|
}
|
|
478
529
|
|
|
479
530
|
// Skip text nodes - already handled before conditional/iteration processing
|
|
480
|
-
if (key.startsWith(
|
|
531
|
+
if (key.startsWith("text_")) {
|
|
481
532
|
continue;
|
|
482
533
|
}
|
|
483
534
|
|
|
484
535
|
// Find corresponding child element by tag name and index
|
|
485
536
|
// Keys like "layout_1" mean the node at childNodes index 1 (includes text nodes)
|
|
486
|
-
// NOT the second layout element
|
|
487
537
|
// Tag names can contain digits (h1, h2, etc.) so use [a-z0-9-]+
|
|
488
538
|
const match = key.match(/^([a-z0-9-]+)_(\d+)$/);
|
|
489
539
|
if (match) {
|
|
490
540
|
const tagName = match[1].toUpperCase();
|
|
491
541
|
const nodeIndex = parseInt(match[2], 10);
|
|
492
542
|
|
|
493
|
-
// Get the node at this childNodes index
|
|
494
543
|
const childNode = element.childNodes[nodeIndex];
|
|
495
544
|
|
|
496
|
-
if (
|
|
545
|
+
if (
|
|
546
|
+
childNode &&
|
|
547
|
+
childNode.nodeType === Node.ELEMENT_NODE &&
|
|
548
|
+
childNode.nodeName === tagName
|
|
549
|
+
) {
|
|
497
550
|
walkTree(childTree, childNode);
|
|
498
551
|
}
|
|
499
552
|
}
|