DOM-preserving hydration — SSR DOM stays, event listeners attach in place
Scope-based cursor walks the existing SSR DOM during island hydration instead of creating new elements and calling replaceChildren. The hydration scope (sx-hydrating) propagates through define-library via scope-push!/peek/pop!, solving the env isolation that broke the previous set!-based approach. Changes: - adapter-dom.sx: hydrating?, hydrate-next-node, hydrate-enter/exit-element helpers. render-to-dom reuses text nodes. render-dom-element reuses elements by tag match, skips dom-append. reactive-text/cek-reactive-text reuse existing text nodes. render-dom-fragment/lake/marsh skip append. dispatch-render-form (if/when/cond) injects markers into existing DOM. - boot.sx: hydrate-island pushes cursor scope, skips replaceChildren. On mismatch error, falls back to full re-render. Result: zero DOM destruction, zero visual flash, event listeners attached to original SSR elements. Stepper clicks verified working. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -330,13 +330,29 @@
|
|||||||
(if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
(if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
||||||
(component-params comp))
|
(component-params comp))
|
||||||
(let
|
(let
|
||||||
((body-dom (cek-try (fn () (with-island-scope (fn (disposable) (append! disposers disposable)) (fn () (render-to-dom (component-body comp) local nil)))) (fn (err) (log-warn (str "hydrate-island FAILED: " comp-name " — " err)) (let ((error-el (dom-create-element "div" nil))) (dom-set-attr error-el "class" "sx-island-error") (dom-set-attr error-el "style" "padding:8px;margin:4px 0;border:1px solid #ef4444;border-radius:4px;background:#fef2f2;color:#b91c1c;font-family:monospace;font-size:12px;white-space:pre-wrap") (dom-set-text-content error-el (str "Island error: " comp-name "\n" err)) error-el)))))
|
((cursor (dict "parent" el "index" 0)))
|
||||||
(host-call el "replaceChildren" body-dom)
|
(scope-push! "sx-hydrating" cursor)
|
||||||
|
(cek-try
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(with-island-scope
|
||||||
|
(fn (disposable) (append! disposers disposable))
|
||||||
|
(fn () (render-to-dom (component-body comp) local nil))))
|
||||||
|
(fn
|
||||||
|
(err)
|
||||||
|
(scope-pop! "sx-hydrating")
|
||||||
|
(log-warn
|
||||||
|
(str "hydrate fallback: " comp-name " — " err))
|
||||||
|
(let
|
||||||
|
((fallback (cek-try (fn () (with-island-scope (fn (d) (append! disposers d)) (fn () (render-to-dom (component-body comp) local nil)))) (fn (err2) (let ((e (dom-create-element "div" nil))) (dom-set-text-content e (str "Island error: " comp-name "\n" err2)) e)))))
|
||||||
|
(host-call el "replaceChildren" fallback)
|
||||||
|
nil)))
|
||||||
|
(scope-pop! "sx-hydrating")
|
||||||
(dom-set-data el "sx-disposers" disposers)
|
(dom-set-data el "sx-disposers" disposers)
|
||||||
(set-timeout (fn () (process-elements el)) 0)
|
(set-timeout (fn () (process-elements el)) 0)
|
||||||
(log-info
|
(log-info
|
||||||
(str
|
(str
|
||||||
"hydrated island: "
|
"hydrated island: ~"
|
||||||
comp-name
|
comp-name
|
||||||
" ("
|
" ("
|
||||||
(len disposers)
|
(len disposers)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -536,6 +536,7 @@
|
|||||||
"SVG_NS",
|
"SVG_NS",
|
||||||
"MATH_NS",
|
"MATH_NS",
|
||||||
"island-scope?",
|
"island-scope?",
|
||||||
|
"hydrating?",
|
||||||
"contains-deref?",
|
"contains-deref?",
|
||||||
"dom-on",
|
"dom-on",
|
||||||
"render-to-dom",
|
"render-to-dom",
|
||||||
|
|||||||
2668
web/adapter-dom.sx
2668
web/adapter-dom.sx
File diff suppressed because it is too large
Load Diff
22
web/boot.sx
22
web/boot.sx
@@ -330,13 +330,29 @@
|
|||||||
(if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
(if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
||||||
(component-params comp))
|
(component-params comp))
|
||||||
(let
|
(let
|
||||||
((body-dom (cek-try (fn () (with-island-scope (fn (disposable) (append! disposers disposable)) (fn () (render-to-dom (component-body comp) local nil)))) (fn (err) (log-warn (str "hydrate-island FAILED: " comp-name " — " err)) (let ((error-el (dom-create-element "div" nil))) (dom-set-attr error-el "class" "sx-island-error") (dom-set-attr error-el "style" "padding:8px;margin:4px 0;border:1px solid #ef4444;border-radius:4px;background:#fef2f2;color:#b91c1c;font-family:monospace;font-size:12px;white-space:pre-wrap") (dom-set-text-content error-el (str "Island error: " comp-name "\n" err)) error-el)))))
|
((cursor (dict "parent" el "index" 0)))
|
||||||
(host-call el "replaceChildren" body-dom)
|
(scope-push! "sx-hydrating" cursor)
|
||||||
|
(cek-try
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(with-island-scope
|
||||||
|
(fn (disposable) (append! disposers disposable))
|
||||||
|
(fn () (render-to-dom (component-body comp) local nil))))
|
||||||
|
(fn
|
||||||
|
(err)
|
||||||
|
(scope-pop! "sx-hydrating")
|
||||||
|
(log-warn
|
||||||
|
(str "hydrate fallback: " comp-name " — " err))
|
||||||
|
(let
|
||||||
|
((fallback (cek-try (fn () (with-island-scope (fn (d) (append! disposers d)) (fn () (render-to-dom (component-body comp) local nil)))) (fn (err2) (let ((e (dom-create-element "div" nil))) (dom-set-text-content e (str "Island error: " comp-name "\n" err2)) e)))))
|
||||||
|
(host-call el "replaceChildren" fallback)
|
||||||
|
nil)))
|
||||||
|
(scope-pop! "sx-hydrating")
|
||||||
(dom-set-data el "sx-disposers" disposers)
|
(dom-set-data el "sx-disposers" disposers)
|
||||||
(set-timeout (fn () (process-elements el)) 0)
|
(set-timeout (fn () (process-elements el)) 0)
|
||||||
(log-info
|
(log-info
|
||||||
(str
|
(str
|
||||||
"hydrated island: "
|
"hydrated island: ~"
|
||||||
comp-name
|
comp-name
|
||||||
" ("
|
" ("
|
||||||
(len disposers)
|
(len disposers)
|
||||||
|
|||||||
Reference in New Issue
Block a user