OSS updates September and October 2025

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.

Sponsors

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.

gratitude

Current top tier sponsors:

Open the details section for more info about sponsoring.

Sponsor info

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!

Updates

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!

presentation at Dutch Clojure meetup

Of course I spent a ton of time on OSS the past two months as well. Some special mentions:

  • I'm pretty excited by Eucalypt, a remake of Reagent for Squint without React by Chris McCormick. It lets you build UIs with the Reagent API in less than 10kb of gzip'ed JS. The code was initially generated by an LLM, but now work is going into making the code base thoroughly tested and simplified where possible.
  • After studying Eucalypt's code I figured that making an even more minimal Reagent-like by hand would be fun. This is where I came up with Reagami. The API looks like a hybrid between Reagent and Replicant. You can build apps with Reagami starting around 5kb gzip'ed.
  • Edamame got Clojure CLR support thanks to Ambrose Bonnaire-Sergeant.
  • SCI Clojure CLR support is underway. The 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.
  • Cljdoc chose squint for its small bundle sizes and easy migration off of TypeScript towards CLJS
  • Via work on Squint, I found a way to optimize 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.

    • Bump to clojure 1.12.3
    • #1870: add .addMethod to clojure.lang.MultiFn
    • #1869: add clojure.lang.ITransientCollection for instance? checks
    • #1865: support reify + equals + hashCode on Object
    • Add java.nio.charset.CharsetDecoder, java.nio.charset.CodingErrorAction, java.nio.charset.CharacterCodingException in support of the sfv library
    • Fix nrepl-server completions and lookup op to be compatible with rebel-readline
    • Add clojure.lang.Ref for instance? checks
    • Bump SCI: align unresolved symbol error message with Clojure
    • Use GraalVM 25
    • Bump deps.clj to 1.12.3.1557
    • Change unknown or REPL file path to NO_SOURCE_PATH instead of <expr> since this can cause issues on Windows when checking for absolute file paths
    • #1001: fix encoding issues on Windows in Powershell. Also see this GraalVM issue
    • Fixes around java.security and allowing setting deprecated Cipher suites at runtime. See this commit.
    • Support Windows Git Bash in bash install script
  • SCI: Configurable Clojure/Script interpreter suitable for scripting

    • ClojureCLR support in progress (with Ambrose Bonnaire Sergeant)
  • edamame: configurable EDN and Clojure parser with location metadata and more

    • 1.5.33 (2025-10-28)
    • Add ClojureCLR support (@frenchy64)
  • clj-kondo: static analyzer and linter for Clojure code that sparks joy.

    • Unreleased
    • #2651: resume linting after paren mismatches
    • 2025.10.23
    • #2590: NEW linter: duplicate-key-in-assoc, defaults to :warning
    • #2639: NEW :equals-nil linter to detect (= nil x) or (= x nil) patterns and suggest (nil? x) instead (@conao3)
    • #2633: support new defparkingop macro in core.async alpha
    • #2635: Add :interface flag to :flags set in :java-class-definitions analysis output to distinguish Java interfaces from classes (@hugoduncan)
    • #2636: set global SCI context so hooks can use requiring-resolve etc.
    • #2641: fix linting of def body, no results due to laziness bug
    • #1743: change :not-empty? to only warn on objects that are already seqs
    • Performance optimization for :ns-groups (thanks @severeoverfl0w)
    • Flip :self-requiring-namespace level from :off to :warning
    • 2025.09.22
    • Remove dbg from data_readers.clj since this breaks when using together with CIDER
    • 2025.09.19
    • #1894: support destruct syntax
    • #2624: lint argument types passed to get and get-in (especially to catch swapped arguments to get in threading macros) (@borkdude, @Uthar)
    • #2564: detect calling set with wrong number of arguments
    • #2603: warn on :inline-def with nested deftest
  • squint: CLJS syntax to JS compiler

    • Support passing keyword to mapv
    • Inline identical? calls
    • Clean up emission of paren wrapping
    • Add nat-int?, neg-int?, pos-int? (@eNotchy)
    • Add rand
    • Fix rendering of null and undefined in #html
    • #747: #html escape fix
    • Optimize nested assoc calls, e.g. produced with ->
    • Avoid object spread when object isn't shared (auto-transient)
    • Optimize =, and, and not= even more
    • not= on undefined and false should return true
    • Optimize code produced for assoc, assoc! and get when object argument can be inferred or is type hinted with ^object
    • Optimize str using macro that compiles into template strings + ?? '' for null/undefined
    • Fix #732: take-last should return nil or empty seq for negative numbers
    • #725: keys and vals should work on js/Map
    • Make map-indexed and keep-indexed lazy
    • Compile time optimization for = when using it on numbers, strings or keyword literals
    • Switch = to a deep-equals implementation that works on primitives, objects, Arrays, Maps and Sets
    • Fix #710: add parse-double
    • Fix #714: assoc-in on nil or undefined
    • Fix #714: dissoc on nil or undefined
    • Basic :import-maps support in squint.edn (just literal replacements, prefixes not supported yet)
  • reagami: A minimal zero-deps Reagent-like for Squint and CLJS

    • First releases
  • clerk: Moldable Live Programming for Clojure

    • Support evaluation of quoted regex
    • Support macros defined in notebooks
    • Bump cherry
  • cljs-str

    • More efficient drop-in replacement for CLJS str. This work was already upstreamed into CLJS, so coming near you in the next CLJS release.
  • unused-deps: Find unused deps in a clojure project

    • Support finding unused git deps
  • scittle: Execute Clojure(Script) directly from browser script tags via SCI

    • Fix SCI regression where interop on keywords like (.catch ...) was accidentally munched
  • Nextjournal Markdown

    • Add :start attribute to ordered lists not starting with 1 (@spicyfalafel)
  • cherry: Experimental ClojureScript to ES6 module compiler

    • Bump squint compiler common component and standard library
    • Bump other deps
    • Optimize =, str, not=
    • Support :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

    • Released several versions catching up with the clojure CLI
  • pod-babashka-go-sqlite3: A babashka pod for interacting with sqlite3

    • Add close-connection
    • Fix #38: add get-connection to cache connection
    • Fix potential memory leak
    • Better handling of parent process death by handling EOF of stdin
    • #25: use musl to compile linux binaries to avoid dependency on glibc
  • quickdoc: Quick and minimal API doc generation for Clojure

    • Fix extra newline in codeblock

Contributions to third party projects:

Other projects

These are (some of the) other projects I'm involved with but little to no activity happened in the past month.

Click for more details - [CLI](https://github.com/babashka/cli): Turn Clojure functions into CLIs! - [pod-babashka-fswatcher](https://github.com/babashka/pod-babashka-fswatcher): babashka filewatcher pod - [sci.nrepl](https://github.com/babashka/sci.nrepl): nREPL server for SCI projects that run in the browser - [babashka.nrepl-client](https://github.com/babashka/nrepl-client) - [fs](https://github.com/babashka/fs) - File system utility library for Clojure - [http-server](https://github.com/babashka/http-server): serve static assets - [nbb](https://github.com/babashka/nbb): Scripting in Clojure on Node.js using SCI - [sci.configs](https://github.com/babashka/sci.configs): A collection of ready to be used SCI configs. - [http-client](https://github.com/babashka/http-client): babashka's http-client - [quickblog](https://github.com/borkdude/quickblog): light-weight static blog engine for Clojure and babashka - [process](https://github.com/babashka/process): Clojure library for shelling out / spawning sub-processes - [html](https://github.com/borkdude/html): Html generation library inspired by squint's html tag - [instaparse-bb](https://github.com/babashka/instaparse-bb): Use instaparse from babashka - [sql pods](https://github.com/babashka/babashka-sql-pods): babashka pods for SQL databases - [rewrite-edn](https://github.com/borkdude/rewrite-edn): Utility lib on top of - [rewrite-clj](https://github.com/clj-commons/rewrite-clj): Rewrite Clojure code and edn - [tools-deps-native](https://github.com/babashka/tools-deps-native) and [tools.bbuild](https://github.com/babashka/tools.bbuild): use tools.deps directly from babashka - [bbin](https://github.com/babashka/bbin): Install any Babashka script or project with one command - [qualify-methods](https://github.com/borkdude/qualify-methods) - Initial release of experimental tool to rewrite instance calls to use fully qualified methods (Clojure 1.12 only) - [neil](https://github.com/babashka/neil): A CLI to add common aliases and features to deps.edn-based projects.
- [tools](https://github.com/borkdude/tools): a set of [bbin](https://github.com/babashka/bbin/) installable scripts - [babashka.json](https://github.com/babashka/json): babashka JSON library/adapter - [speculative](https://github.com/borkdude/speculative) - [squint-macros](https://github.com/squint-cljs/squint-macros): a couple of macros that stand-in for [applied-science/js-interop](https://github.com/applied-science/js-interop) and [promesa](https://github.com/funcool/promesa) to make CLJS projects compatible with squint and/or cherry. - [grasp](https://github.com/borkdude/grasp): Grep Clojure code using clojure.spec regexes - [lein-clj-kondo](https://github.com/clj-kondo/lein-clj-kondo): a leiningen plugin for clj-kondo - [http-kit](https://github.com/http-kit/http-kit): Simple, high-performance event-driven HTTP client+server for Clojure. - [babashka.nrepl](https://github.com/babashka/babashka.nrepl): The nREPL server from babashka as a library, so it can be used from other SCI-based CLIs - [jet](https://github.com/borkdude/jet): CLI to transform between JSON, EDN, YAML and Transit using Clojure - [lein2deps](https://github.com/borkdude/lein2deps): leiningen to deps.edn converter - [cljs-showcase](https://github.com/borkdude/cljs-showcase): Showcase CLJS libs using SCI - [babashka.book](https://github.com/babashka/book): Babashka manual - [pod-babashka-buddy](https://github.com/babashka/pod-babashka-buddy): A pod around buddy core (Cryptographic Api for Clojure). - [gh-release-artifact](https://github.com/borkdude/gh-release-artifact): Upload artifacts to Github releases idempotently - [carve](https://github.com/borkdude/carve) - Remove unused Clojure vars - [4ever-clojure](https://github.com/oxalorg/4ever-clojure) - Pure CLJS version of 4clojure, meant to run forever! - [pod-babashka-lanterna](https://github.com/babashka/pod-babashka-lanterna): Interact with clojure-lanterna from babashka - [joyride](https://github.com/BetterThanTomorrow/joyride): VSCode CLJS scripting and REPL (via [SCI](https://github.com/babashka/sci)) - [clj2el](https://borkdude.github.io/clj2el/): transpile Clojure to elisp - [deflet](https://github.com/borkdude/deflet): make let-expressions REPL-friendly! - [deps.add-lib](https://github.com/borkdude/deps.add-lib): Clojure 1.12's add-lib feature for leiningen and/or other environments without a specific version of the clojure CLI

Published: 2025-11-02

Tagged: clojure oss updates

Reagami: a Reagent-like library in less than 100 lines of Squint CLJS

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.

Rendering Hiccup to DOM nodes

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.

Patching the DOM

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

Putting it all together

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:

  • Blending hiccup and patching so we don't even need to create DOM nodes for stuff that hasn't changed
  • Or remember the hiccup we generated on the previous change, compare that since that's more efficient? (I believe Eucalypt does this).
  • Better patching by looking at IDs or keys

Examples

Here are some examples you can play with on the Squint playground:

Published: 2025-10-24

Tagged: clojure reagent clojurescript squint

OSS updates July and August 2025

In this post I'll give updates about open source I worked on during July and August 2025.

To see previous OSS updates, go here.

Sponsors

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.

gratitude

Current top tier sponsors:

Open the details section for more info about sponsoring.

Sponsor info

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!

Updates

Although summer hit Europe and I made a train trip to Switzerland for some hiking with my wife, OSS activity continued in the borkiverse. 20 projects saw updates. As usual, babashka, SCI and clj-kondo saw the most activity.

One of the big things I’m looking forward to is speaking at Clojure Conj 2025. At the risk of sounding a bit pretentious, the title of my talk is "Making Tools Developers Actually Use". Babashka started as a quirky interpreter "nobody had asked for" but now many Clojure developers don't want to live without it. Clj-kondo started out as a minimal proof-of-concept linter and now is widely used tool in Clojurian's every day toolset and available even in Cursive today. In the talk I want to reflect on what makes a tool something developers (like myself) actually want to use. I'm excited about this opportunity and about my first time visiting the Conj (don't ask me how I got the Clojure Conj cap on the photo above). Given the rest of the schedule, it's something I wouldn't want to miss.

For babashka, my main focus has been making it feel even more like regular Clojure. One example is the change in how non-daemon threads are handled. Previously, people had to sometimes add @(promise) to keep an httpkit server alive. Now babashka behaves like clojure -X in this regard: if you spawn non-daemon threads, the process waits for them. It’s looks like a small change, but it brings consistency with JVM Clojure, something I'm always aiming for more with babashka. If you want the old behavior, you can still use --force-exit. While implementing this I hit an interesting bug with GraalVM and also found out that clojure -X sometimes stalls when using agents. Maybe more on this next time.

Another change that was introduced is that when code is evaluated through load-string or Compiler/load (which is the same thing in bb), vars like *warn-on-reflection* are bound. This fixes a problem with loading code in non-main threads. E.g. @(future (load-string "(set! *warn-on-reflection* true)")) would fail in previous versions of babashka. You might wonder why you would ever want to do this. Well, a similar thing happens when you execute babashka tasks in parallel and that's where I ran into this problem.

SCI, the interpreter under the hood of babashka and several other projects, got some critical fixes as well. I detected one somewhat embarrasing bug when loading clojure+.hashp in babashka. It had code that looked like:

(def config {})
(let [config {}
      _ (alter-var-root #'config (constantly config))
     ]
  ...)

In the expression (alter-var-root #'config (constantly config)) the var #'config was mistaken for the local config since SCI's analyzer used a resolve-like function that also resolves locals. This fails horribly. In 6 years of SCI it's the first time I encountered this bug though. After fixing this problem, I noticed that babashka's CI acted up. On every commit, babashka CI tests dozens of Clojure libraries by running their test suites. I noticed that specter's tests were failing. It turned out that one test actually worked prior to fixing the above bug exactly because the SCI analyzer's resolve returned a node that evaluated to a local value. But there is no way I could just leave that bug in, so I had to make a pull request to specter as well to set this straight. A new specter version was released that works both with older version of babashka and the new version.

One other headscratcher in SCI was on the ClojureScript side of things and had to do with munging. In interop like (.-foo-bar #js {:foo-bar 1}) ClojureScript munges the field name in the interop form to foo_bar but in the object it stays "foo-bar". The munging of this name wasn't applied in SCI as an oversight. So in SCI (and thus in nbb, joyride, scittle, etc.) the above expression would return 1 whereas in ClojureScript it would return nil. In contrast, (.-foo-bar #js {:foo_bar 1}) would return nil in SCI but 1 in CLJS. Although fixing this could mean a breaking change in SCI-based scripting environments I decided to align it with CLJS anyway, as switching between SCI and CLJS should not introduce these kinds of surprises.

Other improvements in SCI were made in the area of better using type hints on instance method interop.

And then there’s clj-kondo, the linter that is supposed to spark joy ✨, as far as a linter is able to do that in a developer's life. Two new linters were added, including one that catches suspicious uses of locking. This linter was inspired by a similar rule in splint. Lots of smaller improvements were made like sorting findings and imported files such that they are consistent across multiple runs that use the --parallel option and across operating systems. And as usual bugfixes and preventing false positives.

One happy improvement to scittle is that referencing a library that was introduced by a <script> tag now was made a lot easier. You can find the docs about that here. The tl;dr of this is that when a library registers itself as a global, you can just use that global in :require now: (require '["JSConfetti" :as confetti]).

Of course, none of this happens in isolation. I’m deeply grateful to the community and the sponsors who make this work sustainable: Clojurists Together, Roam Research, Nextjournal, Nubank, and many other companies and individuals. Every bit of support means I can keep refining these tools, fixing edge cases, and thinking about the long-term direction.

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.

    • Bump clojure to 1.12.2
    • #1843: BREAKING (potententially): non-daemon thread handling change. Similar to JVM clojure, babashka now waits for non-daemon threads to finish. This means you don't have to append @(promise) anymore when you spawn an httpkit server, for example. For futures and agents, bb uses a thread pool that spawns daemon threads, so that pool isn't preventing an exit. This behavior is similar to clojure -X. You can get back the old behavior where bb always forced an exit and ignored running non-daemon threads with --force-exit.
    • #1690: bind clojure.test/*test-out* to same print-writer as *out* in nREPL server
    • Add Compiler/demunge
    • Add clojure.lang.TaggedLiteral/create
    • Add java.util.TimeZone/setDefault
    • Add println-str
    • SCI: Var literal or special form gets confused with local of same name
    • #1852: (.getContextClassLoader (Thread/currentThread)) should be able to return results from babashka classpath
    • Bump deps.clj to 1.12.2.1565
    • Bind more vars like *warn-on-reflection* during load{string,reader} (same as JVM Clojure) so can load code in other than than the main thread
    • #1845: expose cheshire.generate/{add-encoder,encode-str}
    • Bump timbre to 6.8.0
    • Bump clojure.tools.logging to 1.3.0
    • Improve interop using type hints on qualified instance methods
    • Bump Jsoup to 1.21.2
    • Bump fs to 0.5.7
    • Bump cheshire to 6.1.0
    • Pods: no exception on destroy when there's still calls in progress
  • SCI: Configurable Clojure/Script interpreter suitable for scripting

    • Add println-str
    • Fix #997: Var is mistaken for local when used under the same name in a let body
    • Fix regression introduced in #987
    • Fix #963: respect :param-tags on qualified instance method
    • Add *suppress-read*
    • Add load-reader
    • Fix #872: *loaded-libs* is now the single source of truth about loaded libs
    • Fix #981: respect type hint on instance method callee
    • Add core dynamic vars like *warn-on-reflection* and bind them during load-string etc. such that set!-ing then in a future works.
    • Fix #984: support alternative set! syntax in CLJS
    • Fix #987: method or property name in interop should be munged
    • Fix #986: preserve error location for js static method
    • Fix #990: fix merge-opts with :bindings + deprecate :bindings (replaced by :namespaces {'user ...})
  • clj-kondo: static analyzer and linter for Clojure code that sparks joy.

    • Unreleased
    • #2588: false positive type mismatch about symbol accepting var
    • Require clojure 1.10.3 is the minimum clojure version
    • #2564: detect calling set with wrong number of arguments
    • #2257: support ignore hint on invalid symbol
    • Sort findings on filename, row, column and now additionally on message too
    • #2602: Sort auto-imported configs to avoid differences based on OS or file system
    • #2603: warn on :inline-def with nested deftest
    • #2606: make it easy for users to know how inline-config files should be version controlled (@lread)
    • #2610: ignores may show up unordered due to macros
    • #2615: emit inline-configs config.edn in a git-diff-friendly way (@lread)
    • 2025.07.28
    • #2580: false positive type mismatch with quoted value
    • Fix some :locking-suspicious-lock false positives
    • #2582: :condition-always-true false positives
    • 2025.07.26
    • #2560: NEW linter: :locking-suspicious-lock: report when locking is used on a single arg, interned value or local object
    • #2519: NEW linter: :unresolved-protocol-method. See docs (@emerson-matos)
    • #2555: false positive with clojure.string/replace and partial as replacement fn
    • #2566: Expand :condition-always-true check. (@NoahTheDuke)
    • #2350: support schema.core/defprotocol (@emerson-matos)
    • #2571: false positive unresolved symbol when ignoring expression that goes through macroexpansion hook
    • #2575: false positive type mismatch with nested keyword call and str
    • Bump SCI to 0.10.47
    • Drop memoization for hook fns and configuration, solves memory issue with Cursive + big projects like metabase
    • Optimizations to compensate for dropping caching, performance should be similar (or better depending on the size of your project)
    • #2568: support :deprecated-namespace for .cljc namespaces
  • clerk: Moldable Live Programming for Clojure

    • Upgrade to Reagent and fix unsafe HTML rendering
    • Add viewers for HTML markdown nodes
    • Support file watching in babashka
    • Support server side rendering of formulas using KaTeX
  • squint: CLJS syntax to JS compiler

    • v0.8.153 (2025-08-31)
    • Fix #704: while didn't compile correctly
    • Add clojure.string/includes?
    • Emit less code for varargs functions
    • Fix solidJS example
    • Documentation improvements (@lread)
    • Fix #697: ClassCastException in statement function when passed Code records
    • v0.8.152 (2025-07-18)
    • Fix #680: support import attributes using :with option in require, e.g. :with {:type :json}
    • v0.8.151 (2025-07-15)
    • Implement not= as function
    • Fix #684: JSX output
    • v0.8.150 (2025-07-09)
    • #678: Implement random-uuid (@rafaeldelboni)
    • Fix #681: support unsafe HTML via [:$ ...] tag
  • scittle: Execute Clojure(Script) directly from browser script tags via SCI

    • v0.7.27 (2025-08-21)
    • #95: support string requires of globalThis js deps (@chr15m). See docs.
    • Potentially breaking: (.-foo-bar {}) now behaves as {}.foo_bar, i.e. the property or method name is munged.
    • v0.7.26 (2025-08-20)
    • #121: add cjohansen/dataspex plugin (@jeroenvandijk)
    • #118: add goog.string/format (@jeroenvandijk)
    • Support alternative (set! #js {} -a 1) CLJS syntax (by bumping SCI)
    • Add source maps to distribution
    • Add dev versions of all modules in the dev folder of the distribution + a dev/scitte.cljs-devtools.js module
  • edamame: configurable EDN and Clojure parser with location metadata and more

    • Fix #132: Add counterpart to Clojure's *suppress-read*: :suppress-read
  • sci.configs: A collection of ready to be used SCI configs.

    • Add config for dataspex
  • nbb: Scripting in Clojure on Node.js using SCI

    • nREPL improvement for vim-fireplace
  • Nextjournal Markdown

    • Drop KaTeX dependency by inlining TeXMath lib
  • babashka.nrepl-client

    • Add :responses key with raw responses
  • fs - File system utility library for Clojure

    • Documentation improvements
    • Fix wrong typehint
  • cherry: Experimental ClojureScript to ES6 module compiler

    • not= is now a function
  • CLI: Turn Clojure functions into CLIs!

    • #122: introduce new :repeated-opts option to enforce repeating the option for accepting multiple values (e.g. --foo 1 --foo 2 rather than --foo 1 2)
  • deps.clj: A faithful port of the clojure CLI bash script to Clojure

    • Fixed Java download program that respects CLJ_JVM_OPTS for downloading tools jar.
    • Released several versions catching up with the clojure CLI
  • pod-babashka-fswatcher: babashka filewatcher pod

    • Pod protocol fix: don't send done with async messages
    • Robustness improvements
    • Bump fsnotify
  • sci.nrepl: nREPL server for SCI projects that run in the browser

    • Send current working directory in describe message (for tools like clojure-mcp)
    • Add "session-closed" to close op reply
  • pod-babashka-go-sqlite3: A babashka pod for interacting with sqlite3

    • JSON1 support
  • http-server: serve static assets

    • 0.1.15
    • #22: fix off-by-one error in range requests (@jyn514)
    • 0.1.14
    • #21: Add :not-found option for handling unfound files. The option is a function of the request and should return a map with :status and :body.
    • #19: Add text/html MIME types for asp and aspx file extensions (@respatialized)
    • 0.1.13
    • #16: support range requests (jmglov)
    • #13: add an ending slash to the dir link, and don't encode the slashes (@KDr2)
    • #12: Add headers to index page (rather than just file responses)

Contributions to third party projects:

  • specter: Clojure(Script)'s missing piece

    • Fix babashka support by removing optimizations that only worked due to SCI bug
  • clojure-test-suite: Dialect-independent tests for clojure.core, and others, focused on characterizing how Clojure JVM behaves so that other dialects to reach parity.

    • Added babashka to the test suite

Other projects

These are (some of the) other projects I'm involved with but little to no activity happened in the past month.

Click for more details
  • quickdoc: Quick and minimal API doc generation for Clojure
  • unused-deps: Find unused deps in a clojure project
  • http-client: babashka's http-client
  • quickblog: light-weight static blog engine for Clojure and babashka
  • process: Clojure library for shelling out / spawning sub-processes
  • html: Html generation library inspired by squint's html tag
  • instaparse-bb: Use instaparse from babashka
  • sql pods: babashka pods for SQL databases
  • rewrite-edn: Utility lib on top of
  • rewrite-clj: Rewrite Clojure code and edn
  • tools-deps-native and tools.bbuild: use tools.deps directly from babashka
  • bbin: Install any Babashka script or project with one comman
  • qualify-methods
    • Initial release of experimental tool to rewrite instance calls to use fully qualified methods (Clojure 1.12 only0
  • neil: A CLI to add common aliases and features to deps.edn-based projects.
  • tools: a set of bbin installable scripts
  • babashka.json: babashka JSON library/adapter
  • speculative
  • squint-macros: a couple of macros that stand-in for applied-science/js-interop and promesa to make CLJS projects compatible with squint and/or cherry.
  • grasp: Grep Clojure code using clojure.spec regexes
  • lein-clj-kondo: a leiningen plugin for clj-kondo
  • http-kit: Simple, high-performance event-driven HTTP client+server for Clojure.
  • babashka.nrepl: The nREPL server from babashka as a library, so it can be used from other SCI-based CLIs
  • jet: CLI to transform between JSON, EDN, YAML and Transit using Clojure
  • lein2deps: leiningen to deps.edn converter
  • cljs-showcase: Showcase CLJS libs using SCI
  • babashka.book: Babashka manual
  • pod-babashka-buddy: A pod around buddy core (Cryptographic Api for Clojure).
  • gh-release-artifact: Upload artifacts to Github releases idempotently
  • carve - Remove unused Clojure vars
  • 4ever-clojure - Pure CLJS version of 4clojure, meant to run forever!
  • pod-babashka-lanterna: Interact with clojure-lanterna from babashka
  • joyride: VSCode CLJS scripting and REPL (via SCI)
  • clj2el: transpile Clojure to elisp
  • deflet: make let-expressions REPL-friendly!
  • deps.add-lib: Clojure 1.12's add-lib feature for leiningen and/or other environments without a specific version of the clojure CLI

Published: 2025-08-05

Tagged: clojure oss updates

Archive