Skip to content

Instantly share code, notes, and snippets.

@borkdude
Created October 26, 2025 12:45
Show Gist options
  • Save borkdude/568945514e5061273ee1eb26f2c699ed to your computer and use it in GitHub Desktop.
Save borkdude/568945514e5061273ee1eb26f2c699ed to your computer and use it in GitHub Desktop.
Reagami ported to CLJS
(ns reagami.core
{:clj-kondo/config '{:linters {:unresolved-symbol {:exclude [update!]}}}})
;; array-seq didn't exist in scittle
(defn array-seq [x]
(into [] x))
(def svg-ns "http://www.w3.org/2000/svg")
(defn- parse-tag
"From hiccup, thanks @weavejester"
[^String tag]
(let [id-index (let [index (.indexOf tag "#")] (when (pos? index) index))
class-index (let [index (.indexOf tag ".")] (when (pos? index) index))]
[(cond
id-index (.substring tag 0 id-index)
class-index (.substring tag 0 class-index)
:else tag)
(when id-index
(if class-index
(.substring tag (inc id-index) class-index)
(.substring tag (inc id-index))))
(when class-index
(.substring tag (inc class-index)))]))
(defn boolean-attr? [x]
(or (= "disabled" x)
(= "checked" x)))
#?(:squint (defn name [k]
k))
#?(:squint (defn array-seq [s]
s))
(defn- create-node*
[hiccup in-svg?]
(cond
(or (nil? hiccup)
(string? hiccup)
(number? hiccup)
(boolean? hiccup))
(js/document.createTextNode (str hiccup))
(vector? hiccup)
(let [[tag & children] hiccup
#?@(:squint []
:cljs [tag (if (keyword? tag)
(subs (str tag) 1)
tag)])
[tag id class] (if (string? tag) (parse-tag tag) [tag])
classes (when class (.split class "."))
[attrs children] (if (map? (first children))
[(first children) (rest children)]
[nil children])
in-svg? (or in-svg? (= :svg tag))
node (if (fn? tag)
(let [res (apply tag (if attrs
(cons attrs children)
children))]
(create-node* res in-svg?))
(let [node (if in-svg?
(js/document.createElementNS svg-ns tag)
(js/document.createElement tag))]
(doseq [child children]
(let [child-nodes (if (and (seq? child)
(not (vector? child)))
(mapv #(create-node* % in-svg?) child)
[(create-node* child in-svg?)])]
(doseq [child-node child-nodes]
(.appendChild node child-node))))
(let [modified-attrs (js/Set.)]
(doseq [[k v] attrs]
(let [k (name k)]
(if (.startsWith k "on")
(let [event (-> k
(.replaceAll "-" "")
(.toLowerCase))]
(aset node event v)
(.add modified-attrs event))
(do
(.add modified-attrs k)
(cond
(and (= "style" k) (map? v))
(doseq [[k v] v]
(aset (.-style node) (name k) (name v)))
(.startsWith k "on")
(let [event (-> k
(.replaceAll "-" "")
(.toLowerCase))]
(aset node event v))
(boolean-attr? k) (aset node k v)
:else (when v (.setAttribute node k (name v))))))))
(when (seq classes)
(.setAttribute node "class"
(str (when-let [c (.getAttribute node "class")]
(str c " "))
(.join classes " ")))
(.add modified-attrs "class"))
(when id
(.setAttribute node "id" id)
(.add modified-attrs "id"))
(aset node ::attrs #?(:squint modified-attrs
:cljs (into #{} modified-attrs))))
node))]
node)
:else
(throw (do
(js/console.error "Invalid hiccup:" hiccup)
(js/Error. (str "Invalid hiccup: " hiccup))))))
(defn- create-node [hiccup]
(create-node* hiccup false))
(defn- patch [^js parent new-children]
(let [old-children (.-childNodes parent)]
(if (not= (alength old-children) (alength new-children))
(.apply parent.replaceChildren parent new-children)
(doseq [[^js old ^js new] (mapv vector (array-seq old-children) (array-seq new-children))]
(cond
(and old new (= (.-nodeName old) (.-nodeName new)))
(if (= 3 (.-nodeType old))
(let [txt (.-textContent new)]
(set! (.-textContent old) txt))
(do
(let [old-attrs (aget old ::attrs)
new-attrs (aget new ::attrs)]
(doseq [o old-attrs]
(if (or (.startsWith o "on")
(boolean-attr? o))
(aset old o nil)
(.removeAttribute old o)))
(doseq [n new-attrs]
(if (or (.startsWith n "on")
(boolean-attr? n))
(aset old n (aget new n))
(if
(= "style" n)
(set! (.-style.cssText old) (.-style.cssText new))
(.setAttribute old n (.getAttribute new n))))))
(when-let [new-children (.-childNodes new)]
(patch old new-children))))
:else (.replaceChild parent new old))))))
(defn reagami:render [root hiccup]
(let [new-node (create-node hiccup)]
(patch root #js [new-node])))
(def state (atom {:counter 0}))
(defn my-component []
[:div
[:div "Counted: " (:counter @state)]
[:button {:on-click #(swap! state update :counter inc)}
"Click me!"]])
(defn render []
(reagami:render (js/document.querySelector "#app") [my-component]))
(add-watch state ::render (fn [_ _ _ _]
(render)))
(render)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment