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:
2026-04-10 11:46:41 +00:00
parent 89ffb02b20
commit a2a4d17d53
7 changed files with 2855 additions and 2534 deletions

View File

@@ -1,16 +1,69 @@
(import (sx dom)) (import (sx dom))
(import (sx render)) (import (sx render))
(define-library (web adapter-dom) (define-library
(export SVG_NS MATH_NS island-scope? contains-deref? dom-on render-to-dom render-dom-list render-dom-element render-dom-component render-dom-fragment render-dom-raw render-dom-unknown-component RENDER_DOM_FORMS render-dom-form? dispatch-render-form render-lambda-dom render-dom-island render-dom-lake render-dom-marsh reactive-text reactive-attr reactive-spread reactive-fragment render-list-item extract-key reactive-list bind-input *use-cek-reactive* enable-cek-reactive! cek-reactive-text cek-reactive-attr render-dom-portal render-dom-error-boundary) (web adapter-dom)
(export
SVG_NS
MATH_NS
island-scope?
hydrating?
contains-deref?
dom-on
render-to-dom
render-dom-list
render-dom-element
render-dom-component
render-dom-fragment
render-dom-raw
render-dom-unknown-component
RENDER_DOM_FORMS
render-dom-form?
dispatch-render-form
render-lambda-dom
render-dom-island
render-dom-lake
render-dom-marsh
reactive-text
reactive-attr
reactive-spread
reactive-fragment
render-list-item
extract-key
reactive-list
bind-input
*use-cek-reactive*
enable-cek-reactive!
cek-reactive-text
cek-reactive-attr
render-dom-portal
render-dom-error-boundary)
(begin (begin
(define SVG_NS "http://www.w3.org/2000/svg") (define SVG_NS "http://www.w3.org/2000/svg")
(define MATH_NS "http://www.w3.org/1998/Math/MathML") (define MATH_NS "http://www.w3.org/1998/Math/MathML")
(define
(define island-scope? (fn () (not (nil? (scope-peek "sx-island-scope"))))) island-scope?
(fn () (not (nil? (scope-peek "sx-island-scope")))))
(define hydrating? (fn () (not (nil? (scope-peek "sx-hydrating")))))
(define
hydrate-next-node
(fn
()
(let
((cursor (scope-peek "sx-hydrating")))
(when
cursor
(let
((parent (dict-get cursor "parent"))
(idx (dict-get cursor "index")))
(let
((child (host-call (host-get parent "childNodes") "item" idx)))
(dict-set! cursor "index" (+ idx 1))
child))))))
(define
hydrate-enter-element
(fn (el) (scope-push! "sx-hydrating" (dict "parent" el "index" 0))))
(define hydrate-exit-element (fn () (scope-pop! "sx-hydrating")))
(begin (begin
(define *memo-cache* (dict)) (define *memo-cache* (dict))
(define *cyst-counter* 0) (define *cyst-counter* 0)
@@ -20,7 +73,6 @@
() ()
(set! *cyst-counter* (+ *cyst-counter* 1)) (set! *cyst-counter* (+ *cyst-counter* 1))
(str "sx-cyst-" *cyst-counter*)))) (str "sx-cyst-" *cyst-counter*))))
(define (define
contains-deref? contains-deref?
(fn (fn
@@ -37,7 +89,6 @@
(= (symbol-name (first expr)) "deref")) (= (symbol-name (first expr)) "deref"))
true true
(some contains-deref? expr)))))) (some contains-deref? expr))))))
(define (define
dom-on dom-on
:effects (io) :effects (io)
@@ -59,7 +110,6 @@
(trampoline (call-lambda handler (list e))) (trampoline (call-lambda handler (list e)))
(run-post-render-hooks))) (run-post-render-hooks)))
handler)))) handler))))
(define (define
render-to-dom render-to-dom
:effects (render) :effects (render)
@@ -75,13 +125,22 @@
"raw-html" "raw-html"
(dom-parse-html (raw-html-content expr)) (dom-parse-html (raw-html-content expr))
"string" "string"
(create-text-node expr) (if
(hydrating?)
(or (hydrate-next-node) (create-text-node expr))
(create-text-node expr))
"number" "number"
(create-text-node (str expr)) (if
(hydrating?)
(or (hydrate-next-node) (create-text-node (str expr)))
(create-text-node (str expr)))
"symbol" "symbol"
(render-to-dom (trampoline (eval-expr expr env)) env ns) (render-to-dom (trampoline (eval-expr expr env)) env ns)
"keyword" "keyword"
(create-text-node (keyword-name expr)) (if
(hydrating?)
(or (hydrate-next-node) (create-text-node (keyword-name expr)))
(create-text-node (keyword-name expr)))
"dom-node" "dom-node"
expr expr
"spread" "spread"
@@ -101,7 +160,6 @@
(reactive-text expr) (reactive-text expr)
(create-text-node (str (deref expr)))) (create-text-node (str (deref expr))))
(create-text-node (str expr)))))) (create-text-node (str expr))))))
(define (define
render-dom-list render-dom-list
:effects (render) :effects (render)
@@ -136,7 +194,10 @@
(render-dom-element name args env ns) (render-dom-element name args env ns)
(dispatch-render-form name expr env ns)) (dispatch-render-form name expr env ns))
(and (env-has? env name) (macro? (env-get env name))) (and (env-has? env name) (macro? (env-get env name)))
(render-to-dom (expand-macro (env-get env name) args env) env ns) (render-to-dom
(expand-macro (env-get env name) args env)
env
ns)
(contains? HTML_TAGS name) (contains? HTML_TAGS name)
(render-dom-element name args env ns) (render-dom-element name args env ns)
(and (and
@@ -156,7 +217,12 @@
((skip (get state "skip"))) ((skip (get state "skip")))
(if (if
skip skip
(assoc state "skip" false "i" (inc (get state "i"))) (assoc
state
"skip"
false
"i"
(inc (get state "i")))
(if (if
(and (and
(= (type-of arg) "keyword") (= (type-of arg) "keyword")
@@ -178,7 +244,10 @@
(assoc state "i" (inc (get state "i"))))))) (assoc state "i" (inc (get state "i")))))))
(dict "i" 0 "skip" false) (dict "i" 0 "skip" false)
args) args)
(dom-set-attr marker "data-sx-island" (component-name island)) (dom-set-attr
marker
"data-sx-island"
(component-name island))
(when (when
(not (empty-dict? kw-state)) (not (empty-dict? kw-state))
(dom-set-attr (dom-set-attr
@@ -224,7 +293,6 @@
(when (not (spread? result)) (dom-append frag result)))) (when (not (spread? result)) (dom-append frag result))))
expr) expr)
frag))))) frag)))))
(define (define
render-dom-element render-dom-element
:effects (render) :effects (render)
@@ -232,7 +300,18 @@
((tag :as string) (args :as list) (env :as dict) (ns :as string)) ((tag :as string) (args :as list) (env :as dict) (ns :as string))
(let (let
((new-ns (cond (= tag "svg") SVG_NS (= tag "math") MATH_NS :else ns)) ((new-ns (cond (= tag "svg") SVG_NS (= tag "math") MATH_NS :else ns))
(el (dom-create-element tag new-ns))) (el
(if
(hydrating?)
(let
((existing (hydrate-next-node)))
(if
(and
existing
(= (lower (or (host-get existing "tagName") "")) tag))
(do (hydrate-enter-element existing) existing)
(error (str "hydrate-mismatch:" tag))))
(dom-create-element tag new-ns))))
(scope-push! "element-attrs" nil) (scope-push! "element-attrs" nil)
(reduce (reduce
(fn (fn
@@ -296,7 +375,7 @@
(fn () (render-to-dom arg env new-ns))) (fn () (render-to-dom arg env new-ns)))
(spread? child) (spread? child)
nil nil
:else (dom-append el child)))) :else (when (not (hydrating?)) (dom-append el child)))))
(assoc state "i" (inc (get state "i")))))))) (assoc state "i" (inc (get state "i"))))))))
(dict "i" 0 "skip" false) (dict "i" 0 "skip" false)
args) args)
@@ -334,13 +413,15 @@
(keys spread-dict))) (keys spread-dict)))
(scope-emitted "element-attrs")) (scope-emitted "element-attrs"))
(scope-pop! "element-attrs") (scope-pop! "element-attrs")
el))) (do (when (hydrating?) (hydrate-exit-element)) el))))
(define (define
render-dom-component render-dom-component
:effects (render) :effects (render)
(fn (fn
((comp :as component) (args :as list) (env :as dict) (ns :as string)) ((comp :as component)
(args :as list)
(env :as dict)
(ns :as string))
(let (let
((kwargs (dict)) (children (list))) ((kwargs (dict)) (children (list)))
(reduce (reduce
@@ -389,12 +470,16 @@
children) children)
(env-bind! local "children" child-frag))) (env-bind! local "children" child-frag)))
(render-to-dom (component-body comp) local ns))))) (render-to-dom (component-body comp) local ns)))))
(define (define
render-dom-fragment render-dom-fragment
:effects (render) :effects (render)
(fn (fn
((args :as list) (env :as dict) (ns :as string)) ((args :as list) (env :as dict) (ns :as string))
(if
(hydrating?)
(do
(for-each (fn (x) (render-to-dom x env ns)) args)
(create-fragment))
(let (let
((frag (create-fragment))) ((frag (create-fragment)))
(for-each (for-each
@@ -404,8 +489,7 @@
((result (render-to-dom x env ns))) ((result (render-to-dom x env ns)))
(when (not (spread? result)) (dom-append frag result)))) (when (not (spread? result)) (dom-append frag result))))
args) args)
frag))) frag))))
(define (define
render-dom-raw render-dom-raw
:effects (render) :effects (render)
@@ -427,12 +511,10 @@
(dom-append frag (create-text-node (str val)))))) (dom-append frag (create-text-node (str val))))))
args) args)
frag))) frag)))
(define (define
render-dom-unknown-component render-dom-unknown-component
:effects (render) :effects (render)
(fn ((name :as string)) (error (str "Unknown component: " name)))) (fn ((name :as string)) (error (str "Unknown component: " name))))
(define (define
RENDER_DOM_FORMS RENDER_DOM_FORMS
(list (list
@@ -459,12 +541,10 @@
"scope" "scope"
"provide" "provide"
"cyst")) "cyst"))
(define (define
render-dom-form? render-dom-form?
:effects () :effects ()
(fn ((name :as string)) (contains? RENDER_DOM_FORMS name))) (fn ((name :as string)) (contains? RENDER_DOM_FORMS name)))
(define (define
dispatch-render-form dispatch-render-form
:effects (render) :effects (render)
@@ -495,6 +575,21 @@
(list result))) (list result)))
(dom-insert-after marker result)) (dom-insert-after marker result))
(set! initial-result result))))) (set! initial-result result)))))
(if
(hydrating?)
(do
(when
(and
initial-result
(not (nil? initial-result))
(not (spread? initial-result)))
(let
((parent (dom-parent initial-result)))
(when
parent
(host-call parent "insertBefore" marker initial-result)
(set! current-nodes (list initial-result)))))
marker)
(if (if
(spread? initial-result) (spread? initial-result)
initial-result initial-result
@@ -510,7 +605,7 @@
(dom-child-nodes initial-result) (dom-child-nodes initial-result)
(list initial-result))) (list initial-result)))
(dom-append frag initial-result)) (dom-append frag initial-result))
frag))) frag))))
(let (let
((cond-val (trampoline (eval-expr (nth expr 1) env)))) ((cond-val (trampoline (eval-expr (nth expr 1) env))))
(if (if
@@ -550,15 +645,41 @@
(dom-insert-after marker frag)))) (dom-insert-after marker frag))))
(when (when
(trampoline (eval-expr (nth expr 1) env)) (trampoline (eval-expr (nth expr 1) env))
(if
(hydrating?)
(let
((nodes (list)))
(for-each
(fn
(i)
(let
((result (render-to-dom (nth expr i) env ns)))
(append! nodes result)))
(range 2 (len expr)))
(set! current-nodes nodes)
(set! initial-result (first nodes)))
(let (let
((frag (create-fragment))) ((frag (create-fragment)))
(for-each (for-each
(fn (fn
(i) (i)
(dom-append frag (render-to-dom (nth expr i) env ns))) (dom-append
frag
(render-to-dom (nth expr i) env ns)))
(range 2 (len expr))) (range 2 (len expr)))
(set! current-nodes (dom-child-nodes frag)) (set! current-nodes (dom-child-nodes frag))
(set! initial-result frag)))))) (set! initial-result frag)))))))
(if
(hydrating?)
(do
(when
(and initial-result (not (nil? initial-result)))
(let
((parent (dom-parent initial-result)))
(when
parent
(host-call parent "insertBefore" marker initial-result))))
marker)
(if (if
(spread? initial-result) (spread? initial-result)
initial-result initial-result
@@ -566,7 +687,7 @@
((frag (create-fragment))) ((frag (create-fragment)))
(dom-append frag marker) (dom-append frag marker)
(when initial-result (dom-append frag initial-result)) (when initial-result (dom-append frag initial-result))
frag))) frag))))
(if (if
(not (trampoline (eval-expr (nth expr 1) env))) (not (trampoline (eval-expr (nth expr 1) env)))
(create-fragment) (create-fragment)
@@ -617,6 +738,21 @@
(dom-child-nodes result) (dom-child-nodes result)
(list result))) (list result)))
(set! initial-result result))))))) (set! initial-result result)))))))
(if
(hydrating?)
(do
(when
(and
initial-result
(not (nil? initial-result))
(not (spread? initial-result)))
(let
((parent (dom-parent initial-result)))
(when
parent
(host-call parent "insertBefore" marker initial-result)
(set! current-nodes (list initial-result)))))
marker)
(if (if
(spread? initial-result) (spread? initial-result)
initial-result initial-result
@@ -624,7 +760,7 @@
((frag (create-fragment))) ((frag (create-fragment)))
(dom-append frag marker) (dom-append frag marker)
(when initial-result (dom-append frag initial-result)) (when initial-result (dom-append frag initial-result))
frag))) frag))))
(let (let
((branch (eval-cond (rest expr) env))) ((branch (eval-cond (rest expr) env)))
(if branch (render-to-dom branch env ns) (create-fragment)))) (if branch (render-to-dom branch env ns) (create-fragment))))
@@ -670,7 +806,9 @@
bindings) bindings)
(when (when
(> (len body) 1) (> (len body) 1)
(for-each (fn (e) (trampoline (eval-expr e local))) (init body))) (for-each
(fn (e) (trampoline (eval-expr e local)))
(init body)))
(render-to-dom (last body) local ns)) (render-to-dom (last body) local ns))
(or (= name "begin") (= name "do")) (or (= name "begin") (= name "do"))
(if (if
@@ -771,7 +909,9 @@
(= (type-of (first rest-args)) "keyword") (= (type-of (first rest-args)) "keyword")
(= (keyword-name (first rest-args)) "value")) (= (keyword-name (first rest-args)) "value"))
(do (do
(set! scope-val (trampoline (eval-expr (nth rest-args 1) env))) (set!
scope-val
(trampoline (eval-expr (nth rest-args 1) env)))
(set! body-exprs (slice rest-args 2))) (set! body-exprs (slice rest-args 2)))
(set! body-exprs rest-args)) (set! body-exprs rest-args))
(scope-push! scope-name scope-val) (scope-push! scope-name scope-val)
@@ -787,7 +927,9 @@
(frag (create-fragment))) (frag (create-fragment)))
(scope-push! prov-name prov-val) (scope-push! prov-name prov-val)
(for-each (for-each
(fn (i) (dom-append frag (render-to-dom (nth expr i) env ns))) (fn
(i)
(dom-append frag (render-to-dom (nth expr i) env ns)))
(range 3 (len expr))) (range 3 (len expr)))
(scope-pop! prov-name) (scope-pop! prov-name)
frag) frag)
@@ -817,7 +959,6 @@
(dict-set! *memo-cache* cyst-key container) (dict-set! *memo-cache* cyst-key container)
container)))) container))))
:else (render-to-dom (trampoline (eval-expr expr env)) env ns)))) :else (render-to-dom (trampoline (eval-expr expr env)) env ns))))
(define (define
render-lambda-dom render-lambda-dom
:effects (render) :effects (render)
@@ -829,12 +970,14 @@
(fn (i p) (env-bind! local p (nth args i))) (fn (i p) (env-bind! local p (nth args i)))
(lambda-params f)) (lambda-params f))
(render-to-dom (lambda-body f) local ns)))) (render-to-dom (lambda-body f) local ns))))
(define (define
render-dom-island render-dom-island
:effects (render mutation) :effects (render mutation)
(fn (fn
((island :as island) (args :as list) (env :as dict) (ns :as string)) ((island :as island)
(args :as list)
(env :as dict)
(ns :as string))
(let (let
((kwargs (dict)) (children (list))) ((kwargs (dict)) (children (list)))
(reduce (reduce
@@ -878,19 +1021,22 @@
children) children)
(env-bind! local "children" child-frag))) (env-bind! local "children" child-frag)))
(let (let
((container (dom-create-element "span" nil)) (disposers (list))) ((container (dom-create-element "span" nil))
(disposers (list)))
(begin (begin
(dom-set-attr container "data-sx-island" island-name) (dom-set-attr container "data-sx-island" island-name)
(when (when
(not (empty-dict? kwargs)) (not (empty-dict? kwargs))
(dom-set-attr container "data-sx-state" (sx-serialize kwargs)))) (dom-set-attr
container
"data-sx-state"
(sx-serialize kwargs))))
(mark-processed! container "island-hydrated") (mark-processed! container "island-hydrated")
(let (let
((body-dom (with-island-scope (fn (disposable) (append! disposers disposable)) (fn () (render-to-dom (component-body island) local ns))))) ((body-dom (with-island-scope (fn (disposable) (append! disposers disposable)) (fn () (render-to-dom (component-body island) local ns)))))
(dom-append container body-dom) (dom-append container body-dom)
(dom-set-data container "sx-disposers" disposers) (dom-set-data container "sx-disposers" disposers)
container)))))) container))))))
(define (define
render-dom-lake render-dom-lake
:effects (render) :effects (render)
@@ -927,13 +1073,16 @@
(dict "i" 0 "skip" false) (dict "i" 0 "skip" false)
args) args)
(let (let
((el (dom-create-element lake-tag nil))) ((el (if (hydrating?) (let ((existing (hydrate-next-node))) (if existing (do (hydrate-enter-element existing) existing) (dom-create-element lake-tag nil))) (dom-create-element lake-tag nil))))
(dom-set-attr el "data-sx-lake" (or lake-id "")) (dom-set-attr el "data-sx-lake" (or lake-id ""))
(for-each (for-each
(fn (c) (dom-append el (render-to-dom c env ns))) (fn
(c)
(let
((result (render-to-dom c env ns)))
(when (not (hydrating?)) (dom-append el result))))
children) children)
el)))) (do (when (hydrating?) (hydrate-exit-element)) el)))))
(define (define
render-dom-marsh render-dom-marsh
:effects (render) :effects (render)
@@ -975,27 +1124,29 @@
(dict "i" 0 "skip" false) (dict "i" 0 "skip" false)
args) args)
(let (let
((el (dom-create-element marsh-tag nil))) ((el (if (hydrating?) (let ((existing (hydrate-next-node))) (if existing (do (hydrate-enter-element existing) existing) (dom-create-element marsh-tag nil))) (dom-create-element marsh-tag nil))))
(dom-set-attr el "data-sx-marsh" (or marsh-id "")) (dom-set-attr el "data-sx-marsh" (or marsh-id ""))
(when (when
marsh-transform marsh-transform
(dom-set-data el "sx-marsh-transform" marsh-transform)) (dom-set-data el "sx-marsh-transform" marsh-transform))
(dom-set-data el "sx-marsh-env" env) (dom-set-data el "sx-marsh-env" env)
(for-each (for-each
(fn (c) (dom-append el (render-to-dom c env ns))) (fn
(c)
(let
((result (render-to-dom c env ns)))
(when (not (hydrating?)) (dom-append el result))))
children) children)
el)))) (do (when (hydrating?) (hydrate-exit-element)) el)))))
(define (define
reactive-text reactive-text
:effects (render mutation) :effects (render mutation)
(fn (fn
(sig) (sig)
(let (let
((node (create-text-node (str (deref sig))))) ((node (if (hydrating?) (or (hydrate-next-node) (create-text-node (str (deref sig)))) (create-text-node (str (deref sig))))))
(effect (fn () (dom-set-text-content node (str (deref sig))))) (effect (fn () (dom-set-text-content node (str (deref sig)))))
node))) node)))
(define (define
reactive-attr reactive-attr
:effects (render mutation) :effects (render mutation)
@@ -1019,7 +1170,6 @@
(= val true) (= val true)
(dom-set-attr el attr-name "") (dom-set-attr el attr-name "")
:else (dom-set-attr el attr-name (str val))))))))) :else (dom-set-attr el attr-name (str val)))))))))
(define (define
reactive-spread reactive-spread
:effects (render mutation) :effects (render mutation)
@@ -1044,7 +1194,9 @@
(filter (fn (c) (not (= c ""))) (split current " "))) (filter (fn (c) (not (= c ""))) (split current " ")))
(kept (kept
(filter (filter
(fn (c) (not (some (fn (pc) (= pc c)) prev-classes))) (fn
(c)
(not (some (fn (pc) (= pc c)) prev-classes)))
tokens))) tokens)))
(if (if
(empty? kept) (empty? kept)
@@ -1059,7 +1211,9 @@
((attrs (spread-attrs result)) ((attrs (spread-attrs result))
(cls-str (or (dict-get attrs "class") "")) (cls-str (or (dict-get attrs "class") ""))
(new-classes (new-classes
(filter (fn (c) (not (= c ""))) (split cls-str " "))) (filter
(fn (c) (not (= c "")))
(split cls-str " ")))
(extra-keys (extra-keys
(filter (fn (k) (not (= k "class"))) (keys attrs)))) (filter (fn (k) (not (= k "class"))) (keys attrs))))
(set! prev-classes new-classes) (set! prev-classes new-classes)
@@ -1079,8 +1233,9 @@
(fn (k) (dom-set-attr el k (str (dict-get attrs k)))) (fn (k) (dom-set-attr el k (str (dict-get attrs k))))
extra-keys) extra-keys)
(run-post-render-hooks)) (run-post-render-hooks))
(do (set! prev-classes (list)) (set! prev-extra-keys (list)))))))))) (do
(set! prev-classes (list))
(set! prev-extra-keys (list))))))))))
(define (define
reactive-fragment reactive-fragment
:effects (render mutation) :effects (render mutation)
@@ -1090,7 +1245,8 @@
(env :as dict) (env :as dict)
(ns :as string)) (ns :as string))
(let (let
((marker (create-comment "island-fragment")) (current-nodes (list))) ((marker (create-comment "island-fragment"))
(current-nodes (list)))
(effect (effect
(fn (fn
() ()
@@ -1103,7 +1259,6 @@
(set! current-nodes (dom-child-nodes frag)) (set! current-nodes (dom-child-nodes frag))
(dom-insert-after marker frag))))) (dom-insert-after marker frag)))))
marker))) marker)))
(define (define
render-list-item render-list-item
:effects (render) :effects (render)
@@ -1113,7 +1268,6 @@
(lambda? map-fn) (lambda? map-fn)
(render-lambda-dom map-fn (list item) env ns) (render-lambda-dom map-fn (list item) env ns)
(render-to-dom (apply map-fn (list item)) env ns)))) (render-to-dom (apply map-fn (list item)) env ns))))
(define (define
extract-key extract-key
:effects (render) :effects (render)
@@ -1127,7 +1281,6 @@
(let (let
((dk (dom-get-data node "key"))) ((dk (dom-get-data node "key")))
(if dk (str dk) (str "__idx_" index))))))) (if dk (str dk) (str "__idx_" index)))))))
(define (define
reactive-list reactive-list
:effects (render mutation) :effects (render mutation)
@@ -1212,7 +1365,6 @@
(dom-append container rendered))) (dom-append container rendered)))
items))))) items)))))
container))) container)))
(define (define
bind-input bind-input
:effects (render mutation) :effects (render mutation)
@@ -1220,7 +1372,8 @@
(el (sig :as signal)) (el (sig :as signal))
(let (let
((input-type (lower (or (dom-get-attr el "type") ""))) ((input-type (lower (or (dom-get-attr el "type") "")))
(is-checkbox (or (= input-type "checkbox") (= input-type "radio")))) (is-checkbox
(or (= input-type "checkbox") (= input-type "radio"))))
(if (if
is-checkbox is-checkbox
(dom-set-prop el "checked" (deref sig)) (dom-set-prop el "checked" (deref sig))
@@ -1245,24 +1398,20 @@
is-checkbox is-checkbox
(reset! sig (dom-get-prop el "checked")) (reset! sig (dom-get-prop el "checked"))
(reset! sig (dom-get-prop el "value")))))))) (reset! sig (dom-get-prop el "value"))))))))
(define *use-cek-reactive* true) (define *use-cek-reactive* true)
(define enable-cek-reactive! (fn () (set! *use-cek-reactive* true))) (define enable-cek-reactive! (fn () (set! *use-cek-reactive* true)))
(define (define
cek-reactive-text cek-reactive-text
:effects (render mutation) :effects (render mutation)
(fn (fn
(expr env) (expr env)
(let (let
((node (create-text-node "")) ((node (if (hydrating?) (or (hydrate-next-node) (create-text-node "")) (create-text-node "")))
(update-fn (fn (val) (dom-set-text-content node (str val))))) (update-fn (fn (val) (dom-set-text-content node (str val)))))
(let (let
((initial (cek-run (make-cek-state expr env (list (make-reactive-reset-frame env update-fn true)))))) ((initial (cek-run (make-cek-state expr env (list (make-reactive-reset-frame env update-fn true))))))
(dom-set-text-content node (str initial)) (dom-set-text-content node (str initial))
node)))) node))))
(define (define
cek-reactive-attr cek-reactive-attr
:effects (render mutation) :effects (render mutation)
@@ -1278,7 +1427,6 @@
(let (let
((initial (cek-run (make-cek-state expr env (list (make-reactive-reset-frame env update-fn true)))))) ((initial (cek-run (make-cek-state expr env (list (make-reactive-reset-frame env update-fn true))))))
(cek-call update-fn (list initial)))))) (cek-call update-fn (list initial))))))
(define (define
render-dom-portal render-dom-portal
:effects (render) :effects (render)
@@ -1302,7 +1450,6 @@
(register-in-scope (register-in-scope
(fn () (for-each (fn (n) (dom-remove n)) portal-nodes)))) (fn () (for-each (fn (n) (dom-remove n)) portal-nodes))))
marker))))) marker)))))
(define (define
render-dom-error-boundary render-dom-error-boundary
:effects (render) :effects (render)
@@ -1342,10 +1489,7 @@
(let (let
((fallback-dom (if (nil? fallback-fn) (let ((el (dom-create-element "div" nil))) (dom-set-attr el "class" "sx-render-error") (dom-set-attr el "style" "color:red;font-size:0.875rem;padding:0.5rem;border:1px solid red;border-radius:0.25rem;margin:0.5rem 0;") (dom-set-text-content el (str "Render error: " err)) el) (if (lambda? fallback-fn) (render-lambda-dom fallback-fn (list err retry-fn) env ns) (render-to-dom (apply fallback-fn (list err retry-fn)) env ns))))) ((fallback-dom (if (nil? fallback-fn) (let ((el (dom-create-element "div" nil))) (dom-set-attr el "class" "sx-render-error") (dom-set-attr el "style" "color:red;font-size:0.875rem;padding:0.5rem;border:1px solid red;border-radius:0.25rem;margin:0.5rem 0;") (dom-set-text-content el (str "Render error: " err)) el) (if (lambda? fallback-fn) (render-lambda-dom fallback-fn (list err retry-fn) env ns) (render-to-dom (apply fallback-fn (list err retry-fn)) env ns)))))
(dom-append container fallback-dom))))))) (dom-append container fallback-dom)))))))
container))) container)))))
))
;; Re-export to global env ;; Re-export to global env
(import (web adapter-dom)) (import (web adapter-dom))

File diff suppressed because one or more lines are too long

View File

@@ -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

View File

@@ -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",

View File

@@ -1,16 +1,69 @@
(import (sx dom)) (import (sx dom))
(import (sx render)) (import (sx render))
(define-library (web adapter-dom) (define-library
(export SVG_NS MATH_NS island-scope? contains-deref? dom-on render-to-dom render-dom-list render-dom-element render-dom-component render-dom-fragment render-dom-raw render-dom-unknown-component RENDER_DOM_FORMS render-dom-form? dispatch-render-form render-lambda-dom render-dom-island render-dom-lake render-dom-marsh reactive-text reactive-attr reactive-spread reactive-fragment render-list-item extract-key reactive-list bind-input *use-cek-reactive* enable-cek-reactive! cek-reactive-text cek-reactive-attr render-dom-portal render-dom-error-boundary) (web adapter-dom)
(export
SVG_NS
MATH_NS
island-scope?
hydrating?
contains-deref?
dom-on
render-to-dom
render-dom-list
render-dom-element
render-dom-component
render-dom-fragment
render-dom-raw
render-dom-unknown-component
RENDER_DOM_FORMS
render-dom-form?
dispatch-render-form
render-lambda-dom
render-dom-island
render-dom-lake
render-dom-marsh
reactive-text
reactive-attr
reactive-spread
reactive-fragment
render-list-item
extract-key
reactive-list
bind-input
*use-cek-reactive*
enable-cek-reactive!
cek-reactive-text
cek-reactive-attr
render-dom-portal
render-dom-error-boundary)
(begin (begin
(define SVG_NS "http://www.w3.org/2000/svg") (define SVG_NS "http://www.w3.org/2000/svg")
(define MATH_NS "http://www.w3.org/1998/Math/MathML") (define MATH_NS "http://www.w3.org/1998/Math/MathML")
(define
(define island-scope? (fn () (not (nil? (scope-peek "sx-island-scope"))))) island-scope?
(fn () (not (nil? (scope-peek "sx-island-scope")))))
(define hydrating? (fn () (not (nil? (scope-peek "sx-hydrating")))))
(define
hydrate-next-node
(fn
()
(let
((cursor (scope-peek "sx-hydrating")))
(when
cursor
(let
((parent (dict-get cursor "parent"))
(idx (dict-get cursor "index")))
(let
((child (host-call (host-get parent "childNodes") "item" idx)))
(dict-set! cursor "index" (+ idx 1))
child))))))
(define
hydrate-enter-element
(fn (el) (scope-push! "sx-hydrating" (dict "parent" el "index" 0))))
(define hydrate-exit-element (fn () (scope-pop! "sx-hydrating")))
(begin (begin
(define *memo-cache* (dict)) (define *memo-cache* (dict))
(define *cyst-counter* 0) (define *cyst-counter* 0)
@@ -20,7 +73,6 @@
() ()
(set! *cyst-counter* (+ *cyst-counter* 1)) (set! *cyst-counter* (+ *cyst-counter* 1))
(str "sx-cyst-" *cyst-counter*)))) (str "sx-cyst-" *cyst-counter*))))
(define (define
contains-deref? contains-deref?
(fn (fn
@@ -37,7 +89,6 @@
(= (symbol-name (first expr)) "deref")) (= (symbol-name (first expr)) "deref"))
true true
(some contains-deref? expr)))))) (some contains-deref? expr))))))
(define (define
dom-on dom-on
:effects (io) :effects (io)
@@ -59,7 +110,6 @@
(trampoline (call-lambda handler (list e))) (trampoline (call-lambda handler (list e)))
(run-post-render-hooks))) (run-post-render-hooks)))
handler)))) handler))))
(define (define
render-to-dom render-to-dom
:effects (render) :effects (render)
@@ -75,13 +125,22 @@
"raw-html" "raw-html"
(dom-parse-html (raw-html-content expr)) (dom-parse-html (raw-html-content expr))
"string" "string"
(create-text-node expr) (if
(hydrating?)
(or (hydrate-next-node) (create-text-node expr))
(create-text-node expr))
"number" "number"
(create-text-node (str expr)) (if
(hydrating?)
(or (hydrate-next-node) (create-text-node (str expr)))
(create-text-node (str expr)))
"symbol" "symbol"
(render-to-dom (trampoline (eval-expr expr env)) env ns) (render-to-dom (trampoline (eval-expr expr env)) env ns)
"keyword" "keyword"
(create-text-node (keyword-name expr)) (if
(hydrating?)
(or (hydrate-next-node) (create-text-node (keyword-name expr)))
(create-text-node (keyword-name expr)))
"dom-node" "dom-node"
expr expr
"spread" "spread"
@@ -101,7 +160,6 @@
(reactive-text expr) (reactive-text expr)
(create-text-node (str (deref expr)))) (create-text-node (str (deref expr))))
(create-text-node (str expr)))))) (create-text-node (str expr))))))
(define (define
render-dom-list render-dom-list
:effects (render) :effects (render)
@@ -136,7 +194,10 @@
(render-dom-element name args env ns) (render-dom-element name args env ns)
(dispatch-render-form name expr env ns)) (dispatch-render-form name expr env ns))
(and (env-has? env name) (macro? (env-get env name))) (and (env-has? env name) (macro? (env-get env name)))
(render-to-dom (expand-macro (env-get env name) args env) env ns) (render-to-dom
(expand-macro (env-get env name) args env)
env
ns)
(contains? HTML_TAGS name) (contains? HTML_TAGS name)
(render-dom-element name args env ns) (render-dom-element name args env ns)
(and (and
@@ -156,7 +217,12 @@
((skip (get state "skip"))) ((skip (get state "skip")))
(if (if
skip skip
(assoc state "skip" false "i" (inc (get state "i"))) (assoc
state
"skip"
false
"i"
(inc (get state "i")))
(if (if
(and (and
(= (type-of arg) "keyword") (= (type-of arg) "keyword")
@@ -178,7 +244,10 @@
(assoc state "i" (inc (get state "i"))))))) (assoc state "i" (inc (get state "i")))))))
(dict "i" 0 "skip" false) (dict "i" 0 "skip" false)
args) args)
(dom-set-attr marker "data-sx-island" (component-name island)) (dom-set-attr
marker
"data-sx-island"
(component-name island))
(when (when
(not (empty-dict? kw-state)) (not (empty-dict? kw-state))
(dom-set-attr (dom-set-attr
@@ -224,7 +293,6 @@
(when (not (spread? result)) (dom-append frag result)))) (when (not (spread? result)) (dom-append frag result))))
expr) expr)
frag))))) frag)))))
(define (define
render-dom-element render-dom-element
:effects (render) :effects (render)
@@ -232,7 +300,18 @@
((tag :as string) (args :as list) (env :as dict) (ns :as string)) ((tag :as string) (args :as list) (env :as dict) (ns :as string))
(let (let
((new-ns (cond (= tag "svg") SVG_NS (= tag "math") MATH_NS :else ns)) ((new-ns (cond (= tag "svg") SVG_NS (= tag "math") MATH_NS :else ns))
(el (dom-create-element tag new-ns))) (el
(if
(hydrating?)
(let
((existing (hydrate-next-node)))
(if
(and
existing
(= (lower (or (host-get existing "tagName") "")) tag))
(do (hydrate-enter-element existing) existing)
(error (str "hydrate-mismatch:" tag))))
(dom-create-element tag new-ns))))
(scope-push! "element-attrs" nil) (scope-push! "element-attrs" nil)
(reduce (reduce
(fn (fn
@@ -296,7 +375,7 @@
(fn () (render-to-dom arg env new-ns))) (fn () (render-to-dom arg env new-ns)))
(spread? child) (spread? child)
nil nil
:else (dom-append el child)))) :else (when (not (hydrating?)) (dom-append el child)))))
(assoc state "i" (inc (get state "i")))))))) (assoc state "i" (inc (get state "i"))))))))
(dict "i" 0 "skip" false) (dict "i" 0 "skip" false)
args) args)
@@ -334,13 +413,15 @@
(keys spread-dict))) (keys spread-dict)))
(scope-emitted "element-attrs")) (scope-emitted "element-attrs"))
(scope-pop! "element-attrs") (scope-pop! "element-attrs")
el))) (do (when (hydrating?) (hydrate-exit-element)) el))))
(define (define
render-dom-component render-dom-component
:effects (render) :effects (render)
(fn (fn
((comp :as component) (args :as list) (env :as dict) (ns :as string)) ((comp :as component)
(args :as list)
(env :as dict)
(ns :as string))
(let (let
((kwargs (dict)) (children (list))) ((kwargs (dict)) (children (list)))
(reduce (reduce
@@ -389,12 +470,16 @@
children) children)
(env-bind! local "children" child-frag))) (env-bind! local "children" child-frag)))
(render-to-dom (component-body comp) local ns))))) (render-to-dom (component-body comp) local ns)))))
(define (define
render-dom-fragment render-dom-fragment
:effects (render) :effects (render)
(fn (fn
((args :as list) (env :as dict) (ns :as string)) ((args :as list) (env :as dict) (ns :as string))
(if
(hydrating?)
(do
(for-each (fn (x) (render-to-dom x env ns)) args)
(create-fragment))
(let (let
((frag (create-fragment))) ((frag (create-fragment)))
(for-each (for-each
@@ -404,8 +489,7 @@
((result (render-to-dom x env ns))) ((result (render-to-dom x env ns)))
(when (not (spread? result)) (dom-append frag result)))) (when (not (spread? result)) (dom-append frag result))))
args) args)
frag))) frag))))
(define (define
render-dom-raw render-dom-raw
:effects (render) :effects (render)
@@ -427,12 +511,10 @@
(dom-append frag (create-text-node (str val)))))) (dom-append frag (create-text-node (str val))))))
args) args)
frag))) frag)))
(define (define
render-dom-unknown-component render-dom-unknown-component
:effects (render) :effects (render)
(fn ((name :as string)) (error (str "Unknown component: " name)))) (fn ((name :as string)) (error (str "Unknown component: " name))))
(define (define
RENDER_DOM_FORMS RENDER_DOM_FORMS
(list (list
@@ -459,12 +541,10 @@
"scope" "scope"
"provide" "provide"
"cyst")) "cyst"))
(define (define
render-dom-form? render-dom-form?
:effects () :effects ()
(fn ((name :as string)) (contains? RENDER_DOM_FORMS name))) (fn ((name :as string)) (contains? RENDER_DOM_FORMS name)))
(define (define
dispatch-render-form dispatch-render-form
:effects (render) :effects (render)
@@ -495,6 +575,21 @@
(list result))) (list result)))
(dom-insert-after marker result)) (dom-insert-after marker result))
(set! initial-result result))))) (set! initial-result result)))))
(if
(hydrating?)
(do
(when
(and
initial-result
(not (nil? initial-result))
(not (spread? initial-result)))
(let
((parent (dom-parent initial-result)))
(when
parent
(host-call parent "insertBefore" marker initial-result)
(set! current-nodes (list initial-result)))))
marker)
(if (if
(spread? initial-result) (spread? initial-result)
initial-result initial-result
@@ -510,7 +605,7 @@
(dom-child-nodes initial-result) (dom-child-nodes initial-result)
(list initial-result))) (list initial-result)))
(dom-append frag initial-result)) (dom-append frag initial-result))
frag))) frag))))
(let (let
((cond-val (trampoline (eval-expr (nth expr 1) env)))) ((cond-val (trampoline (eval-expr (nth expr 1) env))))
(if (if
@@ -550,15 +645,41 @@
(dom-insert-after marker frag)))) (dom-insert-after marker frag))))
(when (when
(trampoline (eval-expr (nth expr 1) env)) (trampoline (eval-expr (nth expr 1) env))
(if
(hydrating?)
(let
((nodes (list)))
(for-each
(fn
(i)
(let
((result (render-to-dom (nth expr i) env ns)))
(append! nodes result)))
(range 2 (len expr)))
(set! current-nodes nodes)
(set! initial-result (first nodes)))
(let (let
((frag (create-fragment))) ((frag (create-fragment)))
(for-each (for-each
(fn (fn
(i) (i)
(dom-append frag (render-to-dom (nth expr i) env ns))) (dom-append
frag
(render-to-dom (nth expr i) env ns)))
(range 2 (len expr))) (range 2 (len expr)))
(set! current-nodes (dom-child-nodes frag)) (set! current-nodes (dom-child-nodes frag))
(set! initial-result frag)))))) (set! initial-result frag)))))))
(if
(hydrating?)
(do
(when
(and initial-result (not (nil? initial-result)))
(let
((parent (dom-parent initial-result)))
(when
parent
(host-call parent "insertBefore" marker initial-result))))
marker)
(if (if
(spread? initial-result) (spread? initial-result)
initial-result initial-result
@@ -566,7 +687,7 @@
((frag (create-fragment))) ((frag (create-fragment)))
(dom-append frag marker) (dom-append frag marker)
(when initial-result (dom-append frag initial-result)) (when initial-result (dom-append frag initial-result))
frag))) frag))))
(if (if
(not (trampoline (eval-expr (nth expr 1) env))) (not (trampoline (eval-expr (nth expr 1) env)))
(create-fragment) (create-fragment)
@@ -617,6 +738,21 @@
(dom-child-nodes result) (dom-child-nodes result)
(list result))) (list result)))
(set! initial-result result))))))) (set! initial-result result)))))))
(if
(hydrating?)
(do
(when
(and
initial-result
(not (nil? initial-result))
(not (spread? initial-result)))
(let
((parent (dom-parent initial-result)))
(when
parent
(host-call parent "insertBefore" marker initial-result)
(set! current-nodes (list initial-result)))))
marker)
(if (if
(spread? initial-result) (spread? initial-result)
initial-result initial-result
@@ -624,7 +760,7 @@
((frag (create-fragment))) ((frag (create-fragment)))
(dom-append frag marker) (dom-append frag marker)
(when initial-result (dom-append frag initial-result)) (when initial-result (dom-append frag initial-result))
frag))) frag))))
(let (let
((branch (eval-cond (rest expr) env))) ((branch (eval-cond (rest expr) env)))
(if branch (render-to-dom branch env ns) (create-fragment)))) (if branch (render-to-dom branch env ns) (create-fragment))))
@@ -670,7 +806,9 @@
bindings) bindings)
(when (when
(> (len body) 1) (> (len body) 1)
(for-each (fn (e) (trampoline (eval-expr e local))) (init body))) (for-each
(fn (e) (trampoline (eval-expr e local)))
(init body)))
(render-to-dom (last body) local ns)) (render-to-dom (last body) local ns))
(or (= name "begin") (= name "do")) (or (= name "begin") (= name "do"))
(if (if
@@ -771,7 +909,9 @@
(= (type-of (first rest-args)) "keyword") (= (type-of (first rest-args)) "keyword")
(= (keyword-name (first rest-args)) "value")) (= (keyword-name (first rest-args)) "value"))
(do (do
(set! scope-val (trampoline (eval-expr (nth rest-args 1) env))) (set!
scope-val
(trampoline (eval-expr (nth rest-args 1) env)))
(set! body-exprs (slice rest-args 2))) (set! body-exprs (slice rest-args 2)))
(set! body-exprs rest-args)) (set! body-exprs rest-args))
(scope-push! scope-name scope-val) (scope-push! scope-name scope-val)
@@ -787,7 +927,9 @@
(frag (create-fragment))) (frag (create-fragment)))
(scope-push! prov-name prov-val) (scope-push! prov-name prov-val)
(for-each (for-each
(fn (i) (dom-append frag (render-to-dom (nth expr i) env ns))) (fn
(i)
(dom-append frag (render-to-dom (nth expr i) env ns)))
(range 3 (len expr))) (range 3 (len expr)))
(scope-pop! prov-name) (scope-pop! prov-name)
frag) frag)
@@ -817,7 +959,6 @@
(dict-set! *memo-cache* cyst-key container) (dict-set! *memo-cache* cyst-key container)
container)))) container))))
:else (render-to-dom (trampoline (eval-expr expr env)) env ns)))) :else (render-to-dom (trampoline (eval-expr expr env)) env ns))))
(define (define
render-lambda-dom render-lambda-dom
:effects (render) :effects (render)
@@ -829,12 +970,14 @@
(fn (i p) (env-bind! local p (nth args i))) (fn (i p) (env-bind! local p (nth args i)))
(lambda-params f)) (lambda-params f))
(render-to-dom (lambda-body f) local ns)))) (render-to-dom (lambda-body f) local ns))))
(define (define
render-dom-island render-dom-island
:effects (render mutation) :effects (render mutation)
(fn (fn
((island :as island) (args :as list) (env :as dict) (ns :as string)) ((island :as island)
(args :as list)
(env :as dict)
(ns :as string))
(let (let
((kwargs (dict)) (children (list))) ((kwargs (dict)) (children (list)))
(reduce (reduce
@@ -878,19 +1021,22 @@
children) children)
(env-bind! local "children" child-frag))) (env-bind! local "children" child-frag)))
(let (let
((container (dom-create-element "span" nil)) (disposers (list))) ((container (dom-create-element "span" nil))
(disposers (list)))
(begin (begin
(dom-set-attr container "data-sx-island" island-name) (dom-set-attr container "data-sx-island" island-name)
(when (when
(not (empty-dict? kwargs)) (not (empty-dict? kwargs))
(dom-set-attr container "data-sx-state" (sx-serialize kwargs)))) (dom-set-attr
container
"data-sx-state"
(sx-serialize kwargs))))
(mark-processed! container "island-hydrated") (mark-processed! container "island-hydrated")
(let (let
((body-dom (with-island-scope (fn (disposable) (append! disposers disposable)) (fn () (render-to-dom (component-body island) local ns))))) ((body-dom (with-island-scope (fn (disposable) (append! disposers disposable)) (fn () (render-to-dom (component-body island) local ns)))))
(dom-append container body-dom) (dom-append container body-dom)
(dom-set-data container "sx-disposers" disposers) (dom-set-data container "sx-disposers" disposers)
container)))))) container))))))
(define (define
render-dom-lake render-dom-lake
:effects (render) :effects (render)
@@ -927,13 +1073,16 @@
(dict "i" 0 "skip" false) (dict "i" 0 "skip" false)
args) args)
(let (let
((el (dom-create-element lake-tag nil))) ((el (if (hydrating?) (let ((existing (hydrate-next-node))) (if existing (do (hydrate-enter-element existing) existing) (dom-create-element lake-tag nil))) (dom-create-element lake-tag nil))))
(dom-set-attr el "data-sx-lake" (or lake-id "")) (dom-set-attr el "data-sx-lake" (or lake-id ""))
(for-each (for-each
(fn (c) (dom-append el (render-to-dom c env ns))) (fn
(c)
(let
((result (render-to-dom c env ns)))
(when (not (hydrating?)) (dom-append el result))))
children) children)
el)))) (do (when (hydrating?) (hydrate-exit-element)) el)))))
(define (define
render-dom-marsh render-dom-marsh
:effects (render) :effects (render)
@@ -975,27 +1124,29 @@
(dict "i" 0 "skip" false) (dict "i" 0 "skip" false)
args) args)
(let (let
((el (dom-create-element marsh-tag nil))) ((el (if (hydrating?) (let ((existing (hydrate-next-node))) (if existing (do (hydrate-enter-element existing) existing) (dom-create-element marsh-tag nil))) (dom-create-element marsh-tag nil))))
(dom-set-attr el "data-sx-marsh" (or marsh-id "")) (dom-set-attr el "data-sx-marsh" (or marsh-id ""))
(when (when
marsh-transform marsh-transform
(dom-set-data el "sx-marsh-transform" marsh-transform)) (dom-set-data el "sx-marsh-transform" marsh-transform))
(dom-set-data el "sx-marsh-env" env) (dom-set-data el "sx-marsh-env" env)
(for-each (for-each
(fn (c) (dom-append el (render-to-dom c env ns))) (fn
(c)
(let
((result (render-to-dom c env ns)))
(when (not (hydrating?)) (dom-append el result))))
children) children)
el)))) (do (when (hydrating?) (hydrate-exit-element)) el)))))
(define (define
reactive-text reactive-text
:effects (render mutation) :effects (render mutation)
(fn (fn
(sig) (sig)
(let (let
((node (create-text-node (str (deref sig))))) ((node (if (hydrating?) (or (hydrate-next-node) (create-text-node (str (deref sig)))) (create-text-node (str (deref sig))))))
(effect (fn () (dom-set-text-content node (str (deref sig))))) (effect (fn () (dom-set-text-content node (str (deref sig)))))
node))) node)))
(define (define
reactive-attr reactive-attr
:effects (render mutation) :effects (render mutation)
@@ -1019,7 +1170,6 @@
(= val true) (= val true)
(dom-set-attr el attr-name "") (dom-set-attr el attr-name "")
:else (dom-set-attr el attr-name (str val))))))))) :else (dom-set-attr el attr-name (str val)))))))))
(define (define
reactive-spread reactive-spread
:effects (render mutation) :effects (render mutation)
@@ -1044,7 +1194,9 @@
(filter (fn (c) (not (= c ""))) (split current " "))) (filter (fn (c) (not (= c ""))) (split current " ")))
(kept (kept
(filter (filter
(fn (c) (not (some (fn (pc) (= pc c)) prev-classes))) (fn
(c)
(not (some (fn (pc) (= pc c)) prev-classes)))
tokens))) tokens)))
(if (if
(empty? kept) (empty? kept)
@@ -1059,7 +1211,9 @@
((attrs (spread-attrs result)) ((attrs (spread-attrs result))
(cls-str (or (dict-get attrs "class") "")) (cls-str (or (dict-get attrs "class") ""))
(new-classes (new-classes
(filter (fn (c) (not (= c ""))) (split cls-str " "))) (filter
(fn (c) (not (= c "")))
(split cls-str " ")))
(extra-keys (extra-keys
(filter (fn (k) (not (= k "class"))) (keys attrs)))) (filter (fn (k) (not (= k "class"))) (keys attrs))))
(set! prev-classes new-classes) (set! prev-classes new-classes)
@@ -1079,8 +1233,9 @@
(fn (k) (dom-set-attr el k (str (dict-get attrs k)))) (fn (k) (dom-set-attr el k (str (dict-get attrs k))))
extra-keys) extra-keys)
(run-post-render-hooks)) (run-post-render-hooks))
(do (set! prev-classes (list)) (set! prev-extra-keys (list)))))))))) (do
(set! prev-classes (list))
(set! prev-extra-keys (list))))))))))
(define (define
reactive-fragment reactive-fragment
:effects (render mutation) :effects (render mutation)
@@ -1090,7 +1245,8 @@
(env :as dict) (env :as dict)
(ns :as string)) (ns :as string))
(let (let
((marker (create-comment "island-fragment")) (current-nodes (list))) ((marker (create-comment "island-fragment"))
(current-nodes (list)))
(effect (effect
(fn (fn
() ()
@@ -1103,7 +1259,6 @@
(set! current-nodes (dom-child-nodes frag)) (set! current-nodes (dom-child-nodes frag))
(dom-insert-after marker frag))))) (dom-insert-after marker frag)))))
marker))) marker)))
(define (define
render-list-item render-list-item
:effects (render) :effects (render)
@@ -1113,7 +1268,6 @@
(lambda? map-fn) (lambda? map-fn)
(render-lambda-dom map-fn (list item) env ns) (render-lambda-dom map-fn (list item) env ns)
(render-to-dom (apply map-fn (list item)) env ns)))) (render-to-dom (apply map-fn (list item)) env ns))))
(define (define
extract-key extract-key
:effects (render) :effects (render)
@@ -1127,7 +1281,6 @@
(let (let
((dk (dom-get-data node "key"))) ((dk (dom-get-data node "key")))
(if dk (str dk) (str "__idx_" index))))))) (if dk (str dk) (str "__idx_" index)))))))
(define (define
reactive-list reactive-list
:effects (render mutation) :effects (render mutation)
@@ -1212,7 +1365,6 @@
(dom-append container rendered))) (dom-append container rendered)))
items))))) items)))))
container))) container)))
(define (define
bind-input bind-input
:effects (render mutation) :effects (render mutation)
@@ -1220,7 +1372,8 @@
(el (sig :as signal)) (el (sig :as signal))
(let (let
((input-type (lower (or (dom-get-attr el "type") ""))) ((input-type (lower (or (dom-get-attr el "type") "")))
(is-checkbox (or (= input-type "checkbox") (= input-type "radio")))) (is-checkbox
(or (= input-type "checkbox") (= input-type "radio"))))
(if (if
is-checkbox is-checkbox
(dom-set-prop el "checked" (deref sig)) (dom-set-prop el "checked" (deref sig))
@@ -1245,24 +1398,20 @@
is-checkbox is-checkbox
(reset! sig (dom-get-prop el "checked")) (reset! sig (dom-get-prop el "checked"))
(reset! sig (dom-get-prop el "value")))))))) (reset! sig (dom-get-prop el "value"))))))))
(define *use-cek-reactive* true) (define *use-cek-reactive* true)
(define enable-cek-reactive! (fn () (set! *use-cek-reactive* true))) (define enable-cek-reactive! (fn () (set! *use-cek-reactive* true)))
(define (define
cek-reactive-text cek-reactive-text
:effects (render mutation) :effects (render mutation)
(fn (fn
(expr env) (expr env)
(let (let
((node (create-text-node "")) ((node (if (hydrating?) (or (hydrate-next-node) (create-text-node "")) (create-text-node "")))
(update-fn (fn (val) (dom-set-text-content node (str val))))) (update-fn (fn (val) (dom-set-text-content node (str val)))))
(let (let
((initial (cek-run (make-cek-state expr env (list (make-reactive-reset-frame env update-fn true)))))) ((initial (cek-run (make-cek-state expr env (list (make-reactive-reset-frame env update-fn true))))))
(dom-set-text-content node (str initial)) (dom-set-text-content node (str initial))
node)))) node))))
(define (define
cek-reactive-attr cek-reactive-attr
:effects (render mutation) :effects (render mutation)
@@ -1278,7 +1427,6 @@
(let (let
((initial (cek-run (make-cek-state expr env (list (make-reactive-reset-frame env update-fn true)))))) ((initial (cek-run (make-cek-state expr env (list (make-reactive-reset-frame env update-fn true))))))
(cek-call update-fn (list initial)))))) (cek-call update-fn (list initial))))))
(define (define
render-dom-portal render-dom-portal
:effects (render) :effects (render)
@@ -1302,7 +1450,6 @@
(register-in-scope (register-in-scope
(fn () (for-each (fn (n) (dom-remove n)) portal-nodes)))) (fn () (for-each (fn (n) (dom-remove n)) portal-nodes))))
marker))))) marker)))))
(define (define
render-dom-error-boundary render-dom-error-boundary
:effects (render) :effects (render)
@@ -1342,10 +1489,7 @@
(let (let
((fallback-dom (if (nil? fallback-fn) (let ((el (dom-create-element "div" nil))) (dom-set-attr el "class" "sx-render-error") (dom-set-attr el "style" "color:red;font-size:0.875rem;padding:0.5rem;border:1px solid red;border-radius:0.25rem;margin:0.5rem 0;") (dom-set-text-content el (str "Render error: " err)) el) (if (lambda? fallback-fn) (render-lambda-dom fallback-fn (list err retry-fn) env ns) (render-to-dom (apply fallback-fn (list err retry-fn)) env ns))))) ((fallback-dom (if (nil? fallback-fn) (let ((el (dom-create-element "div" nil))) (dom-set-attr el "class" "sx-render-error") (dom-set-attr el "style" "color:red;font-size:0.875rem;padding:0.5rem;border:1px solid red;border-radius:0.25rem;margin:0.5rem 0;") (dom-set-text-content el (str "Render error: " err)) el) (if (lambda? fallback-fn) (render-lambda-dom fallback-fn (list err retry-fn) env ns) (render-to-dom (apply fallback-fn (list err retry-fn)) env ns)))))
(dom-append container fallback-dom))))))) (dom-append container fallback-dom)))))))
container))) container)))))
))
;; Re-export to global env ;; Re-export to global env
(import (web adapter-dom)) (import (web adapter-dom))

View File

@@ -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)