@ape-egg/vibe 1.3.1 → 1.6.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 +193 -0
- package/README.md +97 -0
- package/ROADMAP.md +289 -0
- package/boot.js +45 -0
- package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
- package/compiler/src/Cargo.lock +719 -40
- package/compiler/src/Cargo.toml +11 -2
- package/compiler/src/compiler/PRE-RENDERING-IMPLEMENTATION.md +241 -0
- package/compiler/src/compiler/compile.rs +552 -175
- package/compiler/src/compiler/component_tagger.rs +234 -0
- package/compiler/src/compiler/iteration_optimizer.rs +351 -0
- package/compiler/src/compiler/js_analyzer.rs +572 -0
- package/compiler/src/compiler/manifest_builder.rs +251 -26
- package/compiler/src/compiler/mod.rs +5 -1
- package/compiler/src/compiler/state_extractor.rs +140 -25
- package/compiler/src/compiler/value_stamper.rs +579 -88
- package/compiler/src/compiler/watcher.rs +579 -0
- package/compiler/src/config.rs +51 -8
- package/compiler/src/main.rs +41 -28
- package/compiler/src/parser/html.rs +229 -118
- package/component.js +23 -11
- package/llms.txt +304 -0
- package/package.json +1 -17
- package/runtime/cleanup.js +4 -4
- package/runtime/component.js +98 -21
- package/runtime/conditionals.js +2 -2
- package/runtime/constants.js +2 -1
- package/runtime/index.js +152 -30
- package/runtime/iterate.js +27 -5
- package/runtime/parse.js +2 -1
- package/runtime/pre-compiled-iterations.js +153 -0
- package/runtime/{hyperspeed.js → pre-compiled-manifest.js} +204 -132
- package/runtime/utils.js +2 -1
- package/test-results/.last-run.json +4 -0
- package/vibe.css +19 -0
- package/runtime/component-state.js +0 -63
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Pre-compiled Manifest - Manifest support for pre-compiled Vibe pages
|
|
3
3
|
*
|
|
4
|
-
* Handles detection and restoration of pre-compiled
|
|
4
|
+
* Handles detection, loading, and restoration of pre-compiled page manifests.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
// Build
|
|
7
|
+
// Build manifest with restoration data (captures markers before hydration)
|
|
8
8
|
export const buildHyperspeedManifest = (parsedTree) => {
|
|
9
9
|
// Helper to split text content by @[...] markers into array
|
|
10
10
|
const splitByMarkers = (text) => {
|
|
@@ -121,16 +121,17 @@ export const buildHyperspeedManifest = (parsedTree) => {
|
|
|
121
121
|
};
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
-
//
|
|
124
|
+
// Manifest detection - per-page manifest
|
|
125
125
|
// Each compiled page has its own manifest: /vibe-hyperspeed/{page-path}.manifest.js
|
|
126
|
-
let
|
|
126
|
+
let hyperspeedData = null;
|
|
127
127
|
let hyperspeedDetectionAttempted = false;
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
130
|
* Detect page-specific manifest (async, cached after first call)
|
|
131
|
+
* Returns { manifest, path } or null
|
|
131
132
|
*/
|
|
132
133
|
const detectHyperspeed = async () => {
|
|
133
|
-
if (hyperspeedDetectionAttempted) return
|
|
134
|
+
if (hyperspeedDetectionAttempted) return hyperspeedData;
|
|
134
135
|
hyperspeedDetectionAttempted = true;
|
|
135
136
|
|
|
136
137
|
try {
|
|
@@ -170,9 +171,12 @@ const detectHyperspeed = async () => {
|
|
|
170
171
|
for (const manifestPath of possiblePaths) {
|
|
171
172
|
try {
|
|
172
173
|
const module = await import(manifestPath);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
hyperspeedData = {
|
|
175
|
+
manifest: module.default,
|
|
176
|
+
path: manifestPath
|
|
177
|
+
};
|
|
178
|
+
return hyperspeedData;
|
|
179
|
+
} catch (e) {
|
|
176
180
|
// Try next path
|
|
177
181
|
continue;
|
|
178
182
|
}
|
|
@@ -187,7 +191,7 @@ const detectHyperspeed = async () => {
|
|
|
187
191
|
};
|
|
188
192
|
|
|
189
193
|
/**
|
|
190
|
-
* Restore DOM from pre-rendered values to @[...] markers using
|
|
194
|
+
* Restore DOM from pre-rendered values to @[...] markers using pre-compiled manifest
|
|
191
195
|
* This enables FOUC-free loading while maintaining runtime reactivity
|
|
192
196
|
*
|
|
193
197
|
* Flow:
|
|
@@ -203,8 +207,8 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
203
207
|
const walkTree = (tree, element) => {
|
|
204
208
|
if (!tree || !element) return;
|
|
205
209
|
|
|
206
|
-
// Check for
|
|
207
|
-
const restoration = tree.
|
|
210
|
+
// Check for compiled restoration data
|
|
211
|
+
const restoration = tree.compiled?.restoration;
|
|
208
212
|
|
|
209
213
|
if (restoration) {
|
|
210
214
|
// Restore text content with markers if parsed contains bindings
|
|
@@ -230,95 +234,94 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
230
234
|
}
|
|
231
235
|
}
|
|
232
236
|
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
let endComment = null;
|
|
244
|
-
|
|
245
|
-
while (walker.nextNode()) {
|
|
246
|
-
const comment = walker.currentNode;
|
|
247
|
-
const text = comment.textContent.trim();
|
|
248
|
-
if (text.startsWith('if')) {
|
|
249
|
-
startComment = comment;
|
|
250
|
-
} else if (text === 'else') {
|
|
251
|
-
elseComment = comment;
|
|
252
|
-
} else if (text === '/if') {
|
|
253
|
-
endComment = comment;
|
|
254
|
-
break;
|
|
255
|
-
}
|
|
237
|
+
// Restore attribute bindings for reactivity
|
|
238
|
+
// Two cases:
|
|
239
|
+
// 1. Boolean-like attributes with falsy values were removed - restore them
|
|
240
|
+
// 2. Value attributes were stamped - replace stamped values with markers
|
|
241
|
+
if (restoration.attributes) {
|
|
242
|
+
for (const [attrName, attrValue] of Object.entries(restoration.attributes)) {
|
|
243
|
+
// Always set the attribute to restore the marker
|
|
244
|
+
// - If missing (boolean-like, falsy): adds it back
|
|
245
|
+
// - If present (value attr, stamped): replaces stamped value with marker
|
|
246
|
+
element.setAttribute(attrName, attrValue);
|
|
256
247
|
}
|
|
248
|
+
}
|
|
257
249
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
while (current && current !== (elseComment || endComment)) {
|
|
262
|
-
const next = current.nextSibling;
|
|
263
|
-
if (current.nodeType === Node.ELEMENT_NODE) {
|
|
264
|
-
current.remove();
|
|
265
|
-
}
|
|
266
|
-
current = next;
|
|
267
|
-
}
|
|
250
|
+
// Restore name bindings (sparse array indexed by attribute position)
|
|
251
|
+
if (restoration.nameBindings && Array.isArray(restoration.nameBindings)) {
|
|
252
|
+
const attrs = Array.from(element.attributes);
|
|
268
253
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
254
|
+
for (let i = 0; i < restoration.nameBindings.length; i++) {
|
|
255
|
+
const marker = restoration.nameBindings[i];
|
|
256
|
+
if (marker && attrs[i]) {
|
|
257
|
+
// Replace the stamped attribute name with the marker
|
|
258
|
+
const stampedName = attrs[i].name;
|
|
259
|
+
const attrValue = attrs[i].value;
|
|
260
|
+
|
|
261
|
+
element.removeAttribute(stampedName);
|
|
262
|
+
element.setAttribute(marker, attrValue);
|
|
279
263
|
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// After restoration, delete compiled.restoration (runtime will parse DOM fresh)
|
|
268
|
+
if (tree.compiled) {
|
|
269
|
+
delete tree.compiled.restoration;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
280
272
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
273
|
+
// IMPORTANT: Restore text nodes BEFORE processing conditionals/iterations
|
|
274
|
+
// Text nodes use childNodes indices which become invalid after DOM modifications
|
|
275
|
+
if (tree.children) {
|
|
276
|
+
for (const key in tree.children) {
|
|
277
|
+
if (key.startsWith('text_')) {
|
|
278
|
+
const childTree = tree.children[key];
|
|
279
|
+
const restoration = childTree.compiled?.restoration;
|
|
280
|
+
|
|
281
|
+
if (restoration?.parsed && Array.isArray(restoration.parsed)) {
|
|
282
|
+
const hasBindings = restoration.parsed.some(item =>
|
|
283
|
+
typeof item === 'string' && item.includes('@[')
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
if (hasBindings) {
|
|
287
|
+
const originalContent = restoration.parsed.join('');
|
|
288
|
+
|
|
289
|
+
// Extract index from key (e.g., text_0 -> 0)
|
|
290
|
+
const match = key.match(/_(\d+)$/);
|
|
291
|
+
if (match) {
|
|
292
|
+
const index = parseInt(match[1], 10);
|
|
293
|
+
|
|
294
|
+
if (index < element.childNodes.length) {
|
|
295
|
+
const textNode = element.childNodes[index];
|
|
296
|
+
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
|
297
|
+
textNode.textContent = originalContent;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
291
301
|
}
|
|
292
302
|
}
|
|
293
303
|
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
template.innerHTML = restoration.falseTemplate;
|
|
297
|
-
const templateNode = template.firstElementChild; // Use firstElementChild, not firstChild
|
|
298
|
-
if (templateNode) {
|
|
299
|
-
endComment.parentNode.insertBefore(templateNode, endComment);
|
|
300
|
-
}
|
|
304
|
+
if (childTree.compiled) {
|
|
305
|
+
delete childTree.compiled.restoration;
|
|
301
306
|
}
|
|
302
307
|
}
|
|
303
308
|
}
|
|
304
|
-
|
|
305
|
-
// After restoration, delete hyperspeedRestoration (runtime will parse DOM fresh)
|
|
306
|
-
delete tree.hyperspeedRestoration;
|
|
307
309
|
}
|
|
308
310
|
|
|
309
|
-
//
|
|
311
|
+
// Handle conditional and iteration children - process them at THIS level before recursing
|
|
312
|
+
// These are comment nodes in the DOM, not elements, so we need to find them here
|
|
310
313
|
if (tree.children) {
|
|
311
314
|
for (const key in tree.children) {
|
|
312
315
|
const childTree = tree.children[key];
|
|
313
316
|
|
|
314
|
-
// Handle iteration
|
|
315
|
-
if (childTree.type === 'iteration' && childTree.
|
|
316
|
-
const restoration = childTree.
|
|
317
|
+
// Handle iteration restoration
|
|
318
|
+
if (childTree.type === 'iteration' && childTree.compiled?.restoration) {
|
|
319
|
+
const restoration = childTree.compiled.restoration;
|
|
317
320
|
|
|
318
|
-
// Find iteration comment by
|
|
319
|
-
// This avoids index shifting issues after restoration changes DOM
|
|
321
|
+
// Find iteration comment by matching the expression
|
|
320
322
|
let startComment = null;
|
|
321
323
|
let endComment = null;
|
|
324
|
+
let depth = 0;
|
|
322
325
|
|
|
323
326
|
const walker = document.createTreeWalker(element, NodeFilter.SHOW_COMMENT);
|
|
324
327
|
while (walker.nextNode()) {
|
|
@@ -328,11 +331,21 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
328
331
|
// Skip already processed iterations
|
|
329
332
|
if (comment._vibeProcessed) continue;
|
|
330
333
|
|
|
331
|
-
|
|
334
|
+
// Match by expression: "each items as item, i"
|
|
335
|
+
const expectedComment = `each ${restoration.expression}`;
|
|
336
|
+
if (trimmed === expectedComment && !startComment) {
|
|
332
337
|
startComment = comment;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
338
|
+
depth = 1;
|
|
339
|
+
} else if (startComment) {
|
|
340
|
+
if (trimmed.startsWith('each ')) {
|
|
341
|
+
depth++;
|
|
342
|
+
} else if (trimmed === '/each') {
|
|
343
|
+
depth--;
|
|
344
|
+
if (depth === 0) {
|
|
345
|
+
endComment = comment;
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
336
349
|
}
|
|
337
350
|
}
|
|
338
351
|
|
|
@@ -341,76 +354,134 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
341
354
|
startComment._vibeProcessed = true;
|
|
342
355
|
endComment._vibeProcessed = true;
|
|
343
356
|
|
|
344
|
-
|
|
357
|
+
const parent = startComment.parentNode;
|
|
358
|
+
const insertionPoint = endComment.nextSibling;
|
|
359
|
+
|
|
360
|
+
// Remove all pre-rendered content between comments
|
|
345
361
|
let current = startComment.nextSibling;
|
|
346
362
|
while (current && current !== endComment) {
|
|
347
363
|
const next = current.nextSibling;
|
|
348
|
-
if (current.nodeType
|
|
364
|
+
if (current.nodeType !== Node.COMMENT_NODE) {
|
|
349
365
|
current.remove();
|
|
350
366
|
}
|
|
351
367
|
current = next;
|
|
352
368
|
}
|
|
353
369
|
|
|
354
|
-
// Insert template
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
370
|
+
// Insert the template (single item)
|
|
371
|
+
const tempContainer = document.createElement('div');
|
|
372
|
+
tempContainer.innerHTML = restoration.template;
|
|
373
|
+
|
|
374
|
+
const fragment = document.createDocumentFragment();
|
|
375
|
+
while (tempContainer.firstChild) {
|
|
376
|
+
fragment.appendChild(tempContainer.firstChild);
|
|
360
377
|
}
|
|
378
|
+
parent.insertBefore(fragment, endComment);
|
|
361
379
|
}
|
|
362
380
|
|
|
363
|
-
|
|
381
|
+
// Delete the iteration node from tree - runtime will re-create it
|
|
382
|
+
delete tree.children[key];
|
|
364
383
|
continue;
|
|
365
384
|
}
|
|
366
385
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
// TODO: Implement conditional restoration when needed
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
386
|
+
if (childTree.type === 'conditional' && childTree.compiled?.restoration) {
|
|
387
|
+
const restoration = childTree.compiled.restoration;
|
|
372
388
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
);
|
|
389
|
+
// Find conditional comment markers in the current element
|
|
390
|
+
// But SKIP conditionals that are inside iteration blocks
|
|
391
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_COMMENT);
|
|
392
|
+
let startComment = null;
|
|
393
|
+
let endComment = null;
|
|
394
|
+
let insideIteration = false;
|
|
380
395
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
396
|
+
while (walker.nextNode()) {
|
|
397
|
+
const comment = walker.currentNode;
|
|
398
|
+
const text = comment.textContent.trim();
|
|
399
|
+
|
|
400
|
+
// Track if we're inside an iteration block
|
|
401
|
+
if (text.startsWith('each ')) {
|
|
402
|
+
insideIteration = true;
|
|
403
|
+
continue;
|
|
404
|
+
} else if (text === '/each') {
|
|
405
|
+
insideIteration = false;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Skip conditionals inside iterations - runtime will handle them
|
|
410
|
+
if (insideIteration) continue;
|
|
411
|
+
|
|
412
|
+
// Skip already processed conditionals
|
|
413
|
+
if (comment._vibeProcessed) continue;
|
|
414
|
+
|
|
415
|
+
if (text.startsWith('if') && !startComment) {
|
|
416
|
+
startComment = comment;
|
|
417
|
+
} else if (text === '/if' && startComment && !endComment) {
|
|
418
|
+
endComment = comment;
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (startComment && endComment) {
|
|
424
|
+
// Mark as processed
|
|
425
|
+
startComment._vibeProcessed = true;
|
|
426
|
+
endComment._vibeProcessed = true;
|
|
427
|
+
|
|
428
|
+
// Remove ALL pre-rendered content between start and end comments
|
|
429
|
+
// This includes the <!-- else --> marker from compiled HTML
|
|
430
|
+
let current = startComment.nextSibling;
|
|
431
|
+
while (current && current !== endComment) {
|
|
432
|
+
const next = current.nextSibling;
|
|
433
|
+
current.remove(); // Remove ALL nodes, including comment nodes
|
|
434
|
+
current = next;
|
|
388
435
|
}
|
|
436
|
+
|
|
437
|
+
// Insert the template content BEFORE endComment
|
|
438
|
+
const tempContainer = document.createElement('div');
|
|
439
|
+
tempContainer.innerHTML = restoration.template;
|
|
440
|
+
|
|
441
|
+
const fragment = document.createDocumentFragment();
|
|
442
|
+
while (tempContainer.firstChild) {
|
|
443
|
+
fragment.appendChild(tempContainer.firstChild);
|
|
444
|
+
}
|
|
445
|
+
endComment.parentNode.insertBefore(fragment, endComment);
|
|
389
446
|
}
|
|
390
|
-
|
|
447
|
+
|
|
448
|
+
// Delete restoration data AND remove the conditional node from tree
|
|
449
|
+
// Runtime will re-create it from the restored DOM
|
|
450
|
+
delete tree.children[key];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Recursively restore children (skip conditional/iteration children as they were already handled)
|
|
456
|
+
if (tree.children) {
|
|
457
|
+
for (const key in tree.children) {
|
|
458
|
+
const childTree = tree.children[key];
|
|
459
|
+
|
|
460
|
+
// Skip conditionals and iterations (already processed/deleted above)
|
|
461
|
+
if (childTree.type === 'conditional' || childTree.type === 'iteration') {
|
|
391
462
|
continue;
|
|
392
463
|
}
|
|
393
464
|
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
465
|
+
// Skip text nodes - already handled before conditional/iteration processing
|
|
466
|
+
if (key.startsWith('text_')) {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Find corresponding child element by tag name and index
|
|
471
|
+
// Keys like "layout_1" mean the node at childNodes index 1 (includes text nodes)
|
|
472
|
+
// NOT the second layout element
|
|
473
|
+
// Tag names can contain digits (h1, h2, etc.) so use [a-z0-9-]+
|
|
474
|
+
const match = key.match(/^([a-z0-9-]+)_(\d+)$/);
|
|
397
475
|
if (match) {
|
|
398
|
-
const
|
|
399
|
-
const
|
|
476
|
+
const tagName = match[1].toUpperCase();
|
|
477
|
+
const nodeIndex = parseInt(match[2], 10);
|
|
400
478
|
|
|
401
|
-
//
|
|
402
|
-
|
|
479
|
+
// Get the node at this childNodes index
|
|
480
|
+
const childNode = element.childNodes[nodeIndex];
|
|
481
|
+
|
|
482
|
+
if (childNode && childNode.nodeType === Node.ELEMENT_NODE && childNode.nodeName === tagName) {
|
|
403
483
|
walkTree(childTree, childNode);
|
|
404
484
|
}
|
|
405
|
-
} else {
|
|
406
|
-
// Try to find by tag name for non-indexed keys
|
|
407
|
-
const tagName = key.split('_')[0].toUpperCase();
|
|
408
|
-
const childElement = Array.from(element.children).find(
|
|
409
|
-
child => child.nodeName === tagName
|
|
410
|
-
);
|
|
411
|
-
if (childElement) {
|
|
412
|
-
walkTree(childTree, childElement);
|
|
413
|
-
}
|
|
414
485
|
}
|
|
415
486
|
}
|
|
416
487
|
}
|
|
@@ -420,6 +491,7 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
|
|
|
420
491
|
};
|
|
421
492
|
|
|
422
493
|
// Top-level await to detect before module exports
|
|
423
|
-
|
|
494
|
+
hyperspeedData = await detectHyperspeed();
|
|
424
495
|
|
|
425
|
-
export
|
|
496
|
+
export const hyperspeedManifest = hyperspeedData?.manifest || null;
|
|
497
|
+
export const hyperspeedPath = hyperspeedData?.path || null;
|
package/runtime/utils.js
CHANGED
|
@@ -67,7 +67,8 @@ export const resolveThisPath = (path, element) => {
|
|
|
67
67
|
|
|
68
68
|
const componentId = findComponentIdForElement(element);
|
|
69
69
|
if (componentId) {
|
|
70
|
-
|
|
70
|
+
const resolved = path.replace(/^this\./, `${componentId}.`);
|
|
71
|
+
return resolved;
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
return path;
|
package/vibe.css
CHANGED
|
@@ -11,3 +11,22 @@
|
|
|
11
11
|
.vibe-fouc * {
|
|
12
12
|
transition: none !important;
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
/* Vibe Dehydrate - Skip Reactive Processing
|
|
16
|
+
* Elements with [vibe-dehydrate] attribute or .vibe-dehydrate class are skipped during
|
|
17
|
+
* Vibe's parsing and hydration. Useful for displaying literal @[variable] syntax in
|
|
18
|
+
* documentation, examples, or code snippets without triggering reactivity.
|
|
19
|
+
*/
|
|
20
|
+
[vibe-dehydrate],
|
|
21
|
+
.vibe-dehydrate {
|
|
22
|
+
/* No styles applied - marker only */
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Component Wrappers - Layout Transparent
|
|
26
|
+
* Component wrappers (<component> or <div class="component">) use display: contents
|
|
27
|
+
* to make the wrapper invisible in the layout. Children render as if the wrapper doesn't exist.
|
|
28
|
+
*/
|
|
29
|
+
component,
|
|
30
|
+
div.component {
|
|
31
|
+
display: contents;
|
|
32
|
+
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// Shared utility for processing component state
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Generate unique component ID
|
|
5
|
-
*/
|
|
6
|
-
export const generateComponentId = () => {
|
|
7
|
-
return `_c${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Transform variable declarations to property assignments
|
|
12
|
-
* let count = 0; → this.count = 0;
|
|
13
|
-
*/
|
|
14
|
-
const transformDeclarations = (code) => {
|
|
15
|
-
return code
|
|
16
|
-
// Transform: let varName = value;
|
|
17
|
-
.replace(/\b(let|const|var)\s+(\w+)\s*=\s*([^;]+);/g, 'this.$2 = $3;')
|
|
18
|
-
// Transform: let varName;
|
|
19
|
-
.replace(/\b(let|const|var)\s+(\w+)\s*;/g, 'this.$2 = undefined;');
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Execute component script and return state object
|
|
24
|
-
* @param {string} scriptContent - JavaScript code to execute
|
|
25
|
-
* @returns {object} - Component state object
|
|
26
|
-
*/
|
|
27
|
-
export const executeComponentScript = (scriptContent) => {
|
|
28
|
-
const componentState = {};
|
|
29
|
-
|
|
30
|
-
if (!scriptContent || !scriptContent.trim()) {
|
|
31
|
-
return componentState;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
// Transform declarations to property assignments
|
|
36
|
-
const transformed = transformDeclarations(scriptContent);
|
|
37
|
-
|
|
38
|
-
// Execute script with 'this' = componentState
|
|
39
|
-
const fn = new Function(transformed);
|
|
40
|
-
fn.call(componentState);
|
|
41
|
-
|
|
42
|
-
} catch (error) {
|
|
43
|
-
console.error('[vibe] Error executing component script:', error);
|
|
44
|
-
console.error('[vibe] Script content:', scriptContent);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return componentState;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Register component state in global store
|
|
52
|
-
* @param {string} componentId - Unique component identifier
|
|
53
|
-
* @param {object} state - Component state object
|
|
54
|
-
* @param {object} globalState - Global $ object
|
|
55
|
-
*/
|
|
56
|
-
export const registerComponentState = (componentId, state, globalState) => {
|
|
57
|
-
if (!globalState[componentId]) {
|
|
58
|
-
globalState[componentId] = state;
|
|
59
|
-
} else {
|
|
60
|
-
// Component already registered, merge new state
|
|
61
|
-
Object.assign(globalState[componentId], state);
|
|
62
|
-
}
|
|
63
|
-
};
|