ClojureScript

1.12.116 Release

24 November 2025
ClojureScript Team

We’re happy to announce a new release of ClojureScript. If you’re an existing user of ClojureScript please read over the following release notes carefully.

This is a major feature release with a significant number of enhancements. Before diving in, note that Google Closure Compiler has been updated to v20250820.

For a complete list of fixes, changes, and enhancements to ClojureScript see here

ECMAScript 2016 Language Specification

ClojureScript, outside of a few small exceptions, has generated ECMAScript 3rd edition (1999) compatible code. We avoided any newer constructs because historically they offered undesirable outcomes: increased code size due to polyfilling and decreased performance due to yet unoptimized paths in JavaScript virtual machines.

Nine years have passed since the ECMAScript 2016 was released and the major JavaScript virtual machines now offer great performance across the specification. While language constructs like let surprisingly fail to deliver much value for ClojureScript, features like Proxy and Reflect solve real problems at low prices.

ClojureScript will use ES2016 moving forward where it delivers value and performance benefits.

cljs.proxy

ClojureScript’s interop story, while strong, was never as complete as Clojure on the JVM due to the lack of common interfaces, i.e. java.util.Map. ClojureScript values had to be marshalled to JavaScript values via clj→js, generating a significant amount of computational waste.

Enter cljs.proxy. This new experimental namespace uses ES2016 Proxy to lazily bridge ClojureScript maps and vectors to JavaScript. JavaScript code can now see ClojureScript maps as objects and vectors as array-likes. cljs.proxy was carefully written to add very little overhead for object access patterns over a direct -lookup call.

(require '[cljs.proxy :refer [builder]]
         '[goog.object :as gobj])

(def proxy (builder))
(def proxied-map (proxy {:foo 1 :bar 2}))

(gobj/get proxied-map "foo") ;; => 1

This feature needs commmunity tire kicking, but we believe this approach offers sizeable benefits over existing practice.

Clojure Method Values

ClojureScript now supports Clojure 1.12 method value syntax as well as static field syntax. PersistentVector/EMPTY works, but also String/.toUpperCase and Object/new. Thanks to ES2016 Reflect we do not need manual :param-tags for disambiguation, and it covers the many cases where type information will simply not be available to the ClojureScript compiler.

(refer-global :only '[String])
(map String/.toUpperCase ["foo" "bar" "baz"]) ;; => ("FOO" "BAR" "BAZ")

:refer-global and :require-global

:refer-global lets a namespace declare what definitions from the global environment should be available in the current namespace without js prefixing. It can be combined with :rename.

(refer-global :only '[Date] :rename '{Date my-date})
(my-date/new)

:require-global lets you use JavaScript librares that you included as script tags on the page without any further build configuration. JavaScript build tooling brings a considerable amount of additional complexity and now risk and there is a growing population of developers moving to technologies that eliminate it. Hypermedia frameworks in particular have returned to more innocent times where at most you needed exactly one JavaScript dependency to be productive.

ClojureScript now supports hypermedia-centric development approaches where you might have only one dependency and you are using ClojureScript / Google Closure Library primarily to build Web Components and want to sidestep the JavaScript dependency churn and tooling burden.

(require-global '[Idiomorph :as idio])
(idio/morph ...)

:lite-mode and :elide-to-string

Not all programs we might want to write require ambition. There are light scripting use cases, say for a blog, that are not currently well served by ClojureScript.

How to break the 20K compressed wall? After some time in the hammock, we decided to travel back to 2011 and resurface the original data structures that Rich Hickey and co. included in the standard library. While not as efficient, they are decoupled and smaller. Setting the new :lite-mode compiler flag to true makes the ClojureScript compiler emit calls to the older constructors and tree-shaking can eliminate the heavier persistent implementations.

Printing is another blocker for very compact artifacts. Many simpler programs will never recursively print EDN. The :elide-to-string compiler flag removes the toString implementations leading to improved tree-shaking.

Combining these two experimental flags cuts the initial artifact size by two thirds. It’s important to understand these flags cannot be used to make large ClojureScript programs smaller - once you have enough dependencies or rely on enough features, the savings are a wash.

But for people who know that they want to build something very compact, yet not give up on useful bits of cljs.core and Google Closure Library, these two new flags provide more control.

The following program is 6K Brotli compressed with :lite-mode and :elide-to-string.

(->> (map inc (range 10))
  (filter even?)
  (partition 2)
  (drop 1)
  (mapcat identity)
  into-array)

We’re excited to hear feedback about all these new features!

Contributors

Thanks to all of the community members who contributed to ClojureScript 1.12.116

  • Michel Borkent

  • Paula Gearon

  • Roman Liutikov