Fix duplicate sx-cssx-live style tags
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 21s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 21s
Cache the style element reference in _cssx-style-el so flush-cssx-to-dom never creates more than one. Previous code called dom-query on every flush, which could miss the element during rapid successive calls, creating duplicates. 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
@@ -952,7 +952,7 @@
|
||||
(dom-set-attr el k (str (dict-get attrs k))))
|
||||
extra-keys)
|
||||
;; Flush any newly collected CSS rules to live stylesheet
|
||||
(flush-cssx-to-dom))
|
||||
(run-post-render-hooks))
|
||||
;; No longer a spread — clear tracked state
|
||||
(do
|
||||
(set! prev-classes (list))
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
(process-elements el)
|
||||
(sx-hydrate-elements el)
|
||||
(sx-hydrate-islands el)
|
||||
(flush-cssx-to-dom))))))
|
||||
(run-post-render-hooks))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
@@ -120,7 +120,7 @@
|
||||
(process-elements el)
|
||||
(sx-hydrate-elements el)
|
||||
(sx-hydrate-islands el)
|
||||
(flush-cssx-to-dom)
|
||||
(run-post-render-hooks)
|
||||
(dom-dispatch el "sx:resolved" {:id id})))
|
||||
(log-warn (str "resolveSuspense: no element for id=" id))))))
|
||||
|
||||
@@ -418,29 +418,34 @@
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; CSSX live flush — inject collected CSS rules into the DOM
|
||||
;; Render hooks — generic pre/post callbacks for hydration, swap, mount.
|
||||
;; The spec calls these at render boundaries; the app decides what to do.
|
||||
;; Pre-render: setup before DOM changes (e.g. prepare state).
|
||||
;; Post-render: cleanup after DOM changes (e.g. flush collected CSS).
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; ~cssx/tw collects CSS rules via collect!("cssx" ...) during rendering.
|
||||
;; On the server, ~cssx/flush emits a batch <style> tag. On the client,
|
||||
;; islands render independently and no batch flush runs. This function
|
||||
;; injects any unflushed rules into a persistent <style> element in <head>.
|
||||
;; Called after hydration (boot + post-swap) to cover all render paths.
|
||||
|
||||
(define flush-cssx-to-dom :effects [mutation io]
|
||||
(define *pre-render-hooks* (list))
|
||||
(define *post-render-hooks* (list))
|
||||
|
||||
(define register-pre-render-hook :effects [mutation]
|
||||
(fn ((hook-fn :as lambda))
|
||||
(append! *pre-render-hooks* hook-fn)))
|
||||
|
||||
(define register-post-render-hook :effects [mutation]
|
||||
(fn ((hook-fn :as lambda))
|
||||
(append! *post-render-hooks* hook-fn)))
|
||||
|
||||
(define run-pre-render-hooks :effects [mutation io]
|
||||
(fn ()
|
||||
(let ((rules (collected "cssx")))
|
||||
(when (not (empty? rules))
|
||||
(let ((style (or (dom-query "#sx-cssx-live")
|
||||
(let ((s (dom-create-element "style" nil)))
|
||||
(dom-set-attr s "id" "sx-cssx-live")
|
||||
(dom-set-attr s "data-cssx" "")
|
||||
(dom-append-to-head s)
|
||||
s))))
|
||||
(dom-set-prop style "textContent"
|
||||
(str (or (dom-get-prop style "textContent") "")
|
||||
(join "" rules))))
|
||||
(clear-collected! "cssx")))))
|
||||
(for-each (fn (hook) (cek-call hook nil)) *pre-render-hooks*)))
|
||||
|
||||
(define run-post-render-hooks :effects [mutation io]
|
||||
(fn ()
|
||||
(log-info "run-post-render-hooks:" (len *post-render-hooks*) "hooks")
|
||||
(for-each (fn (hook)
|
||||
(log-info " hook type:" (type-of hook) "callable:" (callable? hook) "lambda:" (lambda? hook))
|
||||
(cek-call hook nil))
|
||||
*post-render-hooks*)))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
@@ -464,7 +469,7 @@
|
||||
(process-sx-scripts nil)
|
||||
(sx-hydrate-elements nil)
|
||||
(sx-hydrate-islands nil)
|
||||
(flush-cssx-to-dom)
|
||||
(run-post-render-hooks)
|
||||
(process-elements nil))))
|
||||
|
||||
|
||||
|
||||
@@ -618,7 +618,7 @@
|
||||
(if (not (nil? renamed))
|
||||
renamed
|
||||
;; General mangling rules
|
||||
(let ((result name))
|
||||
(let ((result (replace name "*" "_")))
|
||||
;; Handle trailing ? and !
|
||||
(let ((result (cond
|
||||
(ends-with? result "?")
|
||||
@@ -1422,23 +1422,27 @@
|
||||
(= (keyword-name (nth expr 2)) "effects"))
|
||||
(nth expr 4)
|
||||
(nth expr 2))))
|
||||
(if (nil? val-expr)
|
||||
(str "var " (js-mangle name) " = NIL;")
|
||||
;; Detect zero-arg self-tail-recursive functions → while loops
|
||||
(if (and (list? val-expr)
|
||||
(not (empty? val-expr))
|
||||
(= (type-of (first val-expr)) "symbol")
|
||||
(or (= (symbol-name (first val-expr)) "fn")
|
||||
(= (symbol-name (first val-expr)) "lambda"))
|
||||
(list? (nth val-expr 1))
|
||||
(= (len (nth val-expr 1)) 0)
|
||||
(js-is-self-tail-recursive? name (rest (rest val-expr))))
|
||||
;; While loop optimization
|
||||
(let ((body (rest (rest val-expr)))
|
||||
(loop-body (js-emit-loop-body name body)))
|
||||
(str "var " (js-mangle name) " = function() { while(true) { " loop-body " } };"))
|
||||
;; Normal define
|
||||
(str "var " (js-mangle name) " = " (js-expr val-expr) ";"))))))
|
||||
(let ((mangled (js-mangle name))
|
||||
(var-decl
|
||||
(if (nil? val-expr)
|
||||
(str "var " (js-mangle name) " = NIL;")
|
||||
;; Detect zero-arg self-tail-recursive functions → while loops
|
||||
(if (and (list? val-expr)
|
||||
(not (empty? val-expr))
|
||||
(= (type-of (first val-expr)) "symbol")
|
||||
(or (= (symbol-name (first val-expr)) "fn")
|
||||
(= (symbol-name (first val-expr)) "lambda"))
|
||||
(list? (nth val-expr 1))
|
||||
(= (len (nth val-expr 1)) 0)
|
||||
(js-is-self-tail-recursive? name (rest (rest val-expr))))
|
||||
;; While loop optimization
|
||||
(let ((body (rest (rest val-expr)))
|
||||
(loop-body (js-emit-loop-body name body)))
|
||||
(str "var " mangled " = function() { while(true) { " loop-body " } };"))
|
||||
;; Normal define
|
||||
(str "var " mangled " = " (js-expr val-expr) ";")))))
|
||||
;; Self-register: every spec define is available to evaluated SX code
|
||||
(str var-decl "\nPRIMITIVES[\"" name "\"] = " mangled ";")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
@@ -460,7 +460,7 @@
|
||||
(sx-process-scripts root)
|
||||
(sx-hydrate root)
|
||||
(sx-hydrate-islands root)
|
||||
(flush-cssx-to-dom)
|
||||
(run-post-render-hooks)
|
||||
(process-elements root)))
|
||||
|
||||
|
||||
@@ -871,7 +871,7 @@
|
||||
(hoist-head-elements-full target)
|
||||
(process-elements target)
|
||||
(sx-hydrate-elements target)
|
||||
(flush-cssx-to-dom)
|
||||
(run-post-render-hooks)
|
||||
(dom-dispatch target "sx:clientRoute"
|
||||
(dict "pathname" pathname))
|
||||
(log-info (str "sx:route client " pathname)))))
|
||||
|
||||
@@ -1509,8 +1509,8 @@ CEK_FIXUPS_JS = '''
|
||||
return cekValue(state);
|
||||
};
|
||||
|
||||
// Expose spec functions so evaluated SX code can use them.
|
||||
// Type inspection (platform interface from boundary.sx)
|
||||
// Platform functions — defined in platform_js.py, not in .sx spec files.
|
||||
// Spec defines self-register via js-emit-define; these are the platform interface.
|
||||
PRIMITIVES["type-of"] = typeOf;
|
||||
PRIMITIVES["symbol-name"] = symbolName;
|
||||
PRIMITIVES["keyword-name"] = keywordName;
|
||||
@@ -1520,22 +1520,7 @@ CEK_FIXUPS_JS = '''
|
||||
PRIMITIVES["component?"] = isComponent;
|
||||
PRIMITIVES["island?"] = isIsland;
|
||||
PRIMITIVES["make-symbol"] = function(n) { return new Symbol(n); };
|
||||
|
||||
// Parser (from parser.sx)
|
||||
PRIMITIVES["sx-serialize"] = sxSerialize;
|
||||
|
||||
// CEK machine (from cek.sx/frames.sx)
|
||||
PRIMITIVES["make-cek-state"] = makeCekState;
|
||||
PRIMITIVES["cek-step"] = cekStep;
|
||||
PRIMITIVES["cek-run"] = cekRun;
|
||||
PRIMITIVES["cek-terminal?"] = cekTerminal_p;
|
||||
PRIMITIVES["cek-value"] = cekValue;
|
||||
PRIMITIVES["eval-expr-cek"] = evalExprCek;
|
||||
|
||||
// Render (from adapter-html.sx / render.sx)
|
||||
PRIMITIVES["is-html-tag?"] = function(n) { return HTML_TAGS.indexOf(n) >= 0; };
|
||||
|
||||
// Environment (for creating eval contexts)
|
||||
PRIMITIVES["make-env"] = function() { return merge(componentEnv, PRIMITIVES); };
|
||||
'''
|
||||
|
||||
@@ -1827,8 +1812,8 @@ PLATFORM_DOM_JS = """
|
||||
// If lambda takes 0 params, call without event arg (convenience for on-click handlers)
|
||||
var wrapped = isLambda(handler)
|
||||
? (lambdaParams(handler).length === 0
|
||||
? function(e) { try { cekCall(handler, NIL); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } }
|
||||
: function(e) { try { cekCall(handler, [e]); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } })
|
||||
? function(e) { try { cekCall(handler, NIL); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } finally { runPostRenderHooks(); } }
|
||||
: function(e) { try { cekCall(handler, [e]); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } finally { runPostRenderHooks(); } })
|
||||
: handler;
|
||||
if (name === "click") logInfo("domListen: click on <" + (el.tagName||"?").toLowerCase() + "> text=" + (el.textContent||"").substring(0,20) + " isLambda=" + isLambda(handler));
|
||||
el.addEventListener(name, wrapped);
|
||||
@@ -3064,6 +3049,7 @@ def fixups_js(has_html, has_sx, has_dom, has_signals=False, has_deps=False, has_
|
||||
if (typeof domTextContent === "function") PRIMITIVES["dom-text-content"] = domTextContent;
|
||||
if (typeof domCreateElement === "function") PRIMITIVES["dom-create-element"] = domCreateElement;
|
||||
if (typeof domAppend === "function") PRIMITIVES["dom-append"] = domAppend;
|
||||
if (typeof domAppendToHead === "function") PRIMITIVES["dom-append-to-head"] = domAppendToHead;
|
||||
if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse;
|
||||
if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs;
|
||||
PRIMITIVES["sx-parse"] = sxParse;
|
||||
|
||||
Reference in New Issue
Block a user