@ape-egg/vibe 1.8.0 → 1.9.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/CHANGELOG.md +107 -0
- package/README.md +72 -2
- package/boot.js +5 -1
- package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
- package/compiler/native/vibe-compiler-linux-x64 +0 -0
- package/compiler/src/parser/html.rs +15 -15
- package/index.js +15 -0
- package/llms.txt +151 -84
- package/package.json +3 -2
- package/runtime/_vibe-compiled-iteration-batch.js +18 -9
- package/runtime/affected.js +36 -16
- package/runtime/component.js +237 -86
- package/runtime/conditionals.js +12 -1
- package/runtime/constants.js +17 -3
- package/runtime/hydrate.js +27 -10
- package/runtime/index.js +91 -34
- package/runtime/iterate.js +323 -108
- package/runtime/manifest.js +2 -1
- package/runtime/parse.js +67 -80
- package/runtime/pre-compiled-manifest.js +38 -13
- package/runtime/reconcile.js +621 -0
- package/runtime/state.js +5 -2
- package/runtime/utils.js +77 -2
- package/vibe.css +3 -1
package/runtime/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import createManifest from './manifest.js';
|
|
|
4
4
|
import hydrate from './hydrate.js';
|
|
5
5
|
import affected from './affected.js';
|
|
6
6
|
import { deepMerge, hash } from './utils.js';
|
|
7
|
-
import { renderAllIterations, setRenderAllConditionals } from './iterate.js';
|
|
7
|
+
import { renderAllIterations, setRenderAllConditionals, releaseOrphanedIterationProps } from './iterate.js';
|
|
8
8
|
import { renderAllConditionals, branchNodeRegistry, managedNodes } from './conditionals.js';
|
|
9
9
|
import {
|
|
10
10
|
NON_REACTIVE_ELEMENTS,
|
|
@@ -21,9 +21,10 @@ import {
|
|
|
21
21
|
PHASE_READY,
|
|
22
22
|
DEHYDRATE_CLASS_OR_ATTR,
|
|
23
23
|
} from './constants.js';
|
|
24
|
-
import { processComponent, abortComponentFetch } from './component.js';
|
|
24
|
+
import { processComponent, abortComponentFetch, collectComponentIds, releaseOrphanedComponentState, renderComponentTemplate } from './component.js';
|
|
25
25
|
import { debugLog } from './debug.js';
|
|
26
26
|
import { shouldCleanup, cleanup } from './cleanup.js';
|
|
27
|
+
import { reconcile } from './reconcile.js';
|
|
27
28
|
import {
|
|
28
29
|
buildHyperspeedManifest,
|
|
29
30
|
hyperspeedManifest,
|
|
@@ -617,6 +618,32 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
617
618
|
enumerable: false,
|
|
618
619
|
});
|
|
619
620
|
|
|
621
|
+
// Promise that resolves after the ready hook fires and all ready callbacks
|
|
622
|
+
// have run. Lets late subscribers await readiness without missing the event:
|
|
623
|
+
// `await $.ready`. Non-enumerable so it won't leak into state snapshots.
|
|
624
|
+
let resolveReady;
|
|
625
|
+
Object.defineProperty($, 'ready', {
|
|
626
|
+
value: new Promise((resolve) => { resolveReady = resolve; }),
|
|
627
|
+
enumerable: false,
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// Reconcile a managed subtree against new source HTML. Opt-in entry point;
|
|
631
|
+
// dormant unless called (so hot paths and benchmarks are unaffected).
|
|
632
|
+
Object.defineProperty($, 'reconcile', {
|
|
633
|
+
value: reconcile,
|
|
634
|
+
enumerable: false,
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// Pure-render path for surgical component HMR. Given raw component template
|
|
638
|
+
// HTML, callsite props, slot HTML, and existing componentIds, returns the
|
|
639
|
+
// processed HTML string the plugin's HMR handler can hand to $.reconcile.
|
|
640
|
+
// Scripts are NOT executed — callers use this only when they've verified
|
|
641
|
+
// script contents haven't changed (so registered state is still valid).
|
|
642
|
+
Object.defineProperty($, 'renderComponent', {
|
|
643
|
+
value: renderComponentTemplate,
|
|
644
|
+
enumerable: false,
|
|
645
|
+
});
|
|
646
|
+
|
|
620
647
|
// Initial hydration - pass plain values so iteration can do reference comparison
|
|
621
648
|
const initialState = extractPlainValue($);
|
|
622
649
|
const affectedElements = affected(parsedTree, initialState, initialState);
|
|
@@ -696,6 +723,14 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
696
723
|
let evaluatedCount = 0;
|
|
697
724
|
let addedElementsList = []; // Track all added elements for verbose output
|
|
698
725
|
|
|
726
|
+
// Collect data-vibe-component-id values across ALL removed subtrees in this
|
|
727
|
+
// batch before doing any per-node work, so we can evict their state after
|
|
728
|
+
// the DOM mutations have been applied.
|
|
729
|
+
const removedComponentIds = new Set();
|
|
730
|
+
mutations.forEach(({ removedNodes: removedNodesList }) => {
|
|
731
|
+
removedNodesList.forEach((node) => collectComponentIds(node, removedComponentIds));
|
|
732
|
+
});
|
|
733
|
+
|
|
699
734
|
mutations.forEach(({ addedNodes: addedNodesList, removedNodes: removedNodesList, target }) => {
|
|
700
735
|
removedNodesList.forEach((node) => {
|
|
701
736
|
// If this is a component element with pending fetch, abort it
|
|
@@ -761,29 +796,20 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
761
796
|
return;
|
|
762
797
|
}
|
|
763
798
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
//
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
if (!picked) return;
|
|
774
|
-
|
|
775
|
-
// If parent has no element reference, re-parse from the actual DOM element
|
|
776
|
-
if (!picked.element) {
|
|
777
|
-
picked.element = target;
|
|
799
|
+
// Capture raw slot content of nested <component src> elements BEFORE parse runs.
|
|
800
|
+
// Parse creates conditional nodes from <!-- if --> comments, and renderConditional
|
|
801
|
+
// later removes the template nodes between the comments. Without capturing slot
|
|
802
|
+
// content first, conditionals inside a component's slot content lose their branch
|
|
803
|
+
// templates, breaking reactive updates.
|
|
804
|
+
if (node.nodeType === 1) {
|
|
805
|
+
node.querySelectorAll('component[src], div.component[src]').forEach((el) => {
|
|
806
|
+
if (el._vibeSlotContent === undefined) el._vibeSlotContent = el.innerHTML;
|
|
807
|
+
});
|
|
778
808
|
}
|
|
779
809
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
picked._nextChildIndex = Object.keys(picked.children).length;
|
|
784
|
-
}
|
|
785
|
-
const name = `${node.nodeName.toLowerCase()}_${picked._nextChildIndex}`;
|
|
786
|
-
picked._nextChildIndex++; // Always increment, never decrement
|
|
810
|
+
const entry = Object.entries(manifest).find(([_, element]) => element === target);
|
|
811
|
+
|
|
812
|
+
// Parse the newly added node
|
|
787
813
|
const parsedNode = parse(node);
|
|
788
814
|
|
|
789
815
|
// Accumulate skipped stats
|
|
@@ -791,19 +817,42 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
791
817
|
totalSkipped += parsedNode.stats.skipped;
|
|
792
818
|
}
|
|
793
819
|
|
|
794
|
-
//
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
820
|
+
// If parent is tracked in the manifest, register this new node in the parsed tree.
|
|
821
|
+
// (If not — e.g. mutations inside an iteration instance whose rows aren't in the
|
|
822
|
+
// global manifest — we still hydrate the node below; we just skip tree/manifest
|
|
823
|
+
// registration since there's no tree branch to attach to.)
|
|
824
|
+
if (entry) {
|
|
825
|
+
const [dotAnnotation] = entry;
|
|
826
|
+
const picked = navigateTree(parsedTree, dotAnnotation);
|
|
827
|
+
|
|
828
|
+
if (picked) {
|
|
829
|
+
if (!picked.element) {
|
|
830
|
+
picked.element = target;
|
|
831
|
+
}
|
|
801
832
|
|
|
802
|
-
|
|
803
|
-
|
|
833
|
+
// Parse the newly added node (use monotonically increasing counter for deterministic key)
|
|
834
|
+
// Initialize counter if it doesn't exist
|
|
835
|
+
if (!picked._nextChildIndex) {
|
|
836
|
+
picked._nextChildIndex = Object.keys(picked.children).length;
|
|
837
|
+
}
|
|
838
|
+
const name = `${node.nodeName.toLowerCase()}_${picked._nextChildIndex}`;
|
|
839
|
+
picked._nextChildIndex++; // Always increment, never decrement
|
|
840
|
+
|
|
841
|
+
// Update parent's parsed HTML (only once per parent)
|
|
842
|
+
if (!parsedParents) parsedParents = new Set();
|
|
843
|
+
if (!parsedParents.has(picked)) {
|
|
844
|
+
const { parsed } = parse(picked.element);
|
|
845
|
+
picked.parsed = parsed;
|
|
846
|
+
parsedParents.add(picked);
|
|
847
|
+
}
|
|
804
848
|
|
|
805
|
-
|
|
806
|
-
|
|
849
|
+
// Add the parsed node to parent's children
|
|
850
|
+
picked.children[name] = parsedNode;
|
|
851
|
+
|
|
852
|
+
// Recursively add node and all descendants to manifest
|
|
853
|
+
addToManifest(parsedNode, manifest, `${dotAnnotation}.${name}`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
807
856
|
|
|
808
857
|
// Run core loop for the new node (parse already done, hydrate → conditionals → iterate)
|
|
809
858
|
const counts = processCoreLoop(node, parsedNode, $, manifest, true, debug);
|
|
@@ -823,6 +872,12 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
823
872
|
});
|
|
824
873
|
});
|
|
825
874
|
|
|
875
|
+
// CLEANUP OF CURRENT STATE
|
|
876
|
+
releaseOrphanedComponentState(removedComponentIds);
|
|
877
|
+
mutations.forEach(({ removedNodes: removedNodesList }) => {
|
|
878
|
+
releaseOrphanedIterationProps(removedNodesList);
|
|
879
|
+
});
|
|
880
|
+
|
|
826
881
|
// Fire hooks once after all mutations are processed (not per-node)
|
|
827
882
|
if (hadChanges) {
|
|
828
883
|
if (addedElements > 0 || addedNodes > 0 || removedElements > 0 || removedNodes > 0) {
|
|
@@ -1019,6 +1074,8 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
1019
1074
|
console.error('[vibe] Error in ready hook:', error);
|
|
1020
1075
|
}
|
|
1021
1076
|
});
|
|
1077
|
+
// Resolve $.ready promise after all ready callbacks have run
|
|
1078
|
+
resolveReady();
|
|
1022
1079
|
}
|
|
1023
1080
|
};
|
|
1024
1081
|
|