Dear sponsors,
As we approach Thanksgiving once again, I’m reminded that sustained open source software development, supported by long term sponsors, is not something to take for granted.
I’m genuinely grateful for your ongoing support through GitHub Sponsors. Your contributions make a real difference: my Clojure projects wouldn’t be nearly as polished, maintained, or ambitious without your help.
If you’d like to look back on what happened in open source this past year, you can find an overview here: https://blog.michielborkent.nl/tags/oss-updates.html. The core projects remain clj-kondo, babashka, SCI, scittle, and squint/cherry. Each of them continues to grow in capability and adoption.
I’ve also applied for Clojurists Together again for 2026. If you’re a CT sponsor, a vote in the next long-term funding round would be appreciated.
Here are the main ideas I want to explore in 2026:
I can't make any promises on hard deadlines, but I definitely intend to work toward realizing the above goals.
Aside from software development, I'm also organizing Babashka Conf 2026 the day before Dutch Clojure Days.
As always, feel free to reach out anytime, whether here, on Clojurians Slack, or by email at michielborkent@gmail.com. I love hearing about what you are doing with my projects. Also if you are struggling with something, let me know. Your feedback and use cases continue to shape the direction of my work.
Here’s to another strong year of Clojure OSS!
Thank you for making this journey possible.
With appreciation,
Michiel Borkent / @borkdude

PS: if you aren't sponsoring, but are interested, here are the main ways to do so:
Published: 2025-11-26
Tagged: sponsors
In this post I'll give updates about open source I worked on during September and October 2025.
To see previous OSS updates, go here.
I'd like to thank all the sponsors and contributors that make this work possible. Without you, the below projects would not be as mature or wouldn't exist or be maintained at all! So a sincere thank you to everyone who contributes to the sustainability of these projects.

Current top tier sponsors:
Open the details section for more info about sponsoring.
If you want to ensure that the projects I work on are sustainably maintained, you can sponsor this work in the following ways. Thank you!
The summer heat has faded, and autumn is upon us. One big focus for me is preparing my talk for Clojure Conj 2025, titled "Making tools developers actually use". I did a test run of the talk at the Dutch Clojure Meetup. It went a bit too long at 45 minutes, so I have to shrink it almost by half for the Conj. The more I work on the talk the more ideas come up, so it's challenging!

Of course I spent a ton of time on OSS the past two months as well. Some special mentions:
sci.impl.Reflector code, based on clojure.lang.Reflector was ported to Clojure with the purpose that it would then be easier to translate to Clojure CLR.str in ClojureScript (worst case 4x, best case 200x)Here are updates about the projects/libraries I've worked on in the last two months in detail.
babashka: native, fast starting Clojure interpreter for scripting.
.addMethod to clojure.lang.MultiFnclojure.lang.ITransientCollection for instance? checksreify + equals + hashCode on Objectjava.nio.charset.CharsetDecoder, java.nio.charset.CodingErrorAction, java.nio.charset.CharacterCodingException in support of the sfv librarynrepl-server completions and lookup op to be compatible with rebel-readlineclojure.lang.Ref for instance? checksNO_SOURCE_PATH instead of <expr> since this can cause issues on Windows when checking for absolute file pathsjava.security and allowing setting deprecated Cipher suites at runtime. See this commit.SCI: Configurable Clojure/Script interpreter suitable for scripting
edamame: configurable EDN and Clojure parser with location metadata and more
clj-kondo: static analyzer and linter for Clojure code that sparks joy.
duplicate-key-in-assoc, defaults to :warning:equals-nil linter to detect (= nil x) or (= x nil) patterns and suggest (nil? x) instead (@conao3)defparkingop macro in core.async alpha:interface flag to :flags set in :java-class-definitions analysis output to distinguish Java interfaces from classes (@hugoduncan)requiring-resolve etc.def body, no results due to laziness bug:not-empty? to only warn on objects that are already seqs:ns-groups (thanks @severeoverfl0w):self-requiring-namespace level from :off to :warningdbg from data_readers.clj since this breaks when using together with CIDERdestruct syntaxget and get-in (especially to catch swapped arguments to get in threading macros) (@borkdude, @Uthar):inline-def with nested deftestsquint: CLJS syntax to JS compiler
mapvidentical? callsnat-int?, neg-int?, pos-int? (@eNotchy)randnull and undefined in #html#html escape fixassoc calls, e.g. produced with ->auto-transient)=, and, and not= even morenot= on undefined and false should return trueassoc, assoc! and get when object argument can be inferred or is type hinted with ^objectstr using macro that compiles into template strings + ?? '' for null/undefinedtake-last should return nil or empty seq for negative numberskeys and vals should work on js/Mapmap-indexed and keep-indexed lazy= when using it on numbers, strings or keyword literals= to a deep-equals implementation that works on primitives, objects, Arrays, Maps and Setsparse-doubleassoc-in on nil or undefineddissoc on nil or undefined:import-maps support in squint.edn (just literal replacements, prefixes not supported yet)reagami: A minimal zero-deps Reagent-like for Squint and CLJS
clerk: Moldable Live Programming for Clojure
unused-deps: Find unused deps in a clojure project
scittle: Execute Clojure(Script) directly from browser script tags via SCI
(.catch ...) was accidentally munched:start attribute to ordered lists not starting with 1 (@spicyfalafel)cherry: Experimental ClojureScript to ES6 module compiler
=, str, not=:macros option + :refer so you can use unqualified macros using compiler state (see macro-state-test)deps.clj: A faithful port of the clojure CLI bash script to Clojure
pod-babashka-go-sqlite3: A babashka pod for interacting with sqlite3
close-connectionget-connection to cache connectionquickdoc: Quick and minimal API doc generation for Clojure
Contributions to third party projects:
str (4x worst case, 200x best case)munge-str public:ns-groups handling by caching regex creation and usageThese are (some of the) other projects I'm involved with but little to no activity happened in the past month.
Published: 2025-11-02
Tagged: clojure oss updates
Recently I've been excited about Eucalypt, a Reagent-clone in pure Squint. For those who don't know Squint, it is a CLJS dialect that re-uses JS features as much as possible. Maps and vectors compile directly to objects and arrays for example. The standard library mimics that of Clojure(Script). Collections, although they are mutable in JS, are treated immutably via shallow-copying. You can build small apps in Eucalypt starting at about 9kb gzip. When looking at the code, I wondered if we could make an ultra-simple Reagent-like library with less features for even smaller apps. If I understand Eucalypt correctly, it produces DOM nodes from hiccup and then patches the already mounted DOM nodes using a diffing algorithm. I tried to do this in under 100 lines, in a more basic fashion. I call this library Reagami, a library you can use to fold your state into the DOM!
Let's explain how it works.
Below you see the create-node function, which takes hiccup, e.g. [:div
"Hello"] and transforms it into a DOM node. I placed comments in the code that help you understand it, hopefully.
(defn- create-node
([hiccup] (create-node hiccup false))
([hiccup in-svg?]
(cond
;; we compile primitives directly into TexNodes
(or (nil? hiccup)
(string? hiccup)
(number? hiccup)
(boolean? hiccup))
(js/document.createTextNode (str hiccup))
;; vectors indicate a DOM node literal or a function call
(vector? hiccup)
(let [[tag & children] hiccup
;; here we pull apart id and class shortcuts like: `:div#my-id.my-class`
[tag id class] (if (string? tag) (parse-tag tag) [tag])
classes (when class (.split class "."))
;; hiccup vectors can have an optional map to set attributes of the DOM node
[attrs children] (if (map? (first children))
[(first children) (rest children)]
[nil children])
;; SVG needs special handling, SVG nodes need to be created with `createElementNS`, see below
in-svg? (or in-svg? (= :svg tag))
;; if we encounter a function as the first element, e.g. `[my-component]`, we call it
node (if (fn? tag)
(let [res (apply tag (if attrs
(cons attrs children)
children))]
;; and then we feed the result back into create-node
(create-node res in-svg?))
;; so here we create the DOM element, either a normal or an SVG one
(let [node (if in-svg?
(js/document.createElementNS svg-ns tag)
(js/document.createElement tag))]
;; here we iterate over all the children in the hiccup vector
(doseq [child children]
;; in squint, vectors are also seqs, but here we want to make sure the result is list-like,
;; e.g. the result from `(for [i xs] [my-component])`
;; if that is the case, then we map create-node over those results
(let [child-nodes (if (and (seq? child)
(not (vector? child)))
(mapv #(create-node % in-svg?) child)
;; else, we have only one node as the result of calling create-node on the child
[(create-node child in-svg?)])]
;; let's add all the child-nodes to the DOM element created from the tag!
(doseq [child-node child-nodes]
(.appendChild node child-node))))
;; attribute handling: for each key and value in the attribute map:
(doseq [[k v] attrs]
(let [key-name k]
(cond
;; Let's create some CSS if the style attribute is a msp!
(and (= "style" key-name) (map? v))
(doseq [[k v] v]
(aset (.-style node) k v))
;; if the attribute starts with "on", then we add an event handler
(.startsWith key-name "on")
;; we support :on-click, :on-mouse-down, :onMouseDown, etc.
(let [event (-> (subs key-name 2)
(.replaceAll "-" "")
(.toLowerCase))]
(.addEventListener node event v))
;; if the attribute false is false or nil, we ignore it
:else (when v
(.setAttribute node key-name (str v))))))
;; classes parsed from the tag, e.g. `:div.my-class1.my-class2`
(let [class-list (.-classList node)]
(doseq [clazz classes]
(.add class-list clazz)))
;; finally we handle the id parsed from the tag, e.g. `:div#my-id`
(when id
(set! node -id id))
node))]
node)
:else
(throw (do
(js/console.error "Invalid hiccup:" hiccup)
(js/Error. (str "Invalid hiccup: " hiccup)))))))
That's it! Pretty simple.
If we simply just re-insert the generated hiccup DOM nodes into the DOM every time something changes, then we have several problems. E.g. when we are typing in an input field and we insert a new input field, we lose focus. Also we lose event handlers, although this isn't really an issue if we re-generate them via the hiccup-to-DOM function above. A better approach is to cleverly patch the DOM and re-use existing DOM nodes when possible.
The patch function takes a parent node, some child nodes and tries to re-use or insert the children. There are several libraries that do this far more intelligently than the below function. E.g. Idiomorph is such a library. One problem I encountered with Idiomorph is that it doesn't preserve event listeners. There's also Snabbdom, a small but effective vDOM library that you could use. But where's the fun in that, if you can make your own naive patch function. Here we go! When we render a component for the first time, Reagami calls patch with the root node (usually something like (js/document.querySelector "#app")) and the rendered hiccup as the only child. Read along with the comments again.
(defn- patch [parent new-children]
;; First we take a look at the current children of the root node, already mounted in the DOM
(let [old-children (.-childNodes parent)]
;; If the amount of children isn't the same, we just give up and replace all the children with the new children.
;; This typically happens on first render or when elements disappear due to conditionals in the hiccup code.
;; More mature patching libraries do much more clever things here
;; but I've found this works well enough for the basic use cases I tested.
(if (not= (count old-children) (count new-children))
(parent.replaceChildren.apply parent new-children)
;; When the number of children is the same we compare them one by one.
(doseq [[old new] (mapv vector old-children new-children)]
(cond
(and old new (= (.-nodeName old) (.-nodeName new)))
;; Only when the node names are the same, we consider it the same node.
;; If it's Text node, we need to copy over the text content.
(if (= 3 (.-nodeType old))
(let [txt (.-textContent new)]
(set! (.-textContent old) txt))
(let [new-attributes (.-attributes new)
old-attributes (.-attributes old)]
;; Here we just set all the attribues from the new node to the old node.
(doseq [attr new-attributes]
(.setAttribute old (.-name attr) (.-value attr)))
(doseq [attr old-attributes]
;; When an existing attribute in the old node, doesn't occur in the new node, we remove it
(when-not (.hasAttribute new (.-name attr))
(.removeAttribute old (.-name attr))))
;; Then we recursively descend into the children of the new node.
(when-let [new-children (.-childNodes new)]
(patch old new-children))))
;; If the node names aren't the same, we replace the node completely with the new node.
:else (.replaceChild parent new old))))))
Reagami's only public function is called render which is just:
(defn render [root hiccup]
(let [new-node (create-node hiccup)]
(patch root [new-node])))
With this single function, you can write a small reactive app, if we combine it with atom and add-watch. Here's an example of this:
(ns my-app
(:require ["https://esm.sh/reagami@0.0.8" :as reagami]))
(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)
(Open this example on the Squint playground)
That's it: a Reagent-like library in less than 100 lines of code!
Further ideas:
Here are some examples you can play with on the Squint playground:
Published: 2025-10-24
Tagged: clojure reagent clojurescript squint