shadow-cljs: compiling ClojureScript without the setup fight
ClojureScript compilation made easy
At a glance
- What is it?
- shadow-cljs is a ClojureScript build tool that treats npm as a first-class citizen and ships its own dev server. It fits teams already writing Clojure who need a browser or Node build, and it is the wrong pick if you want a plain JavaScript bundler.
- Who is it for?
- Adopt shadow-cljs if your codebase is ClojureScript and your dependencies live on npm, since the scaffold, watch build and REPL are all wired together by npx create-cljs-project and shadow-cljs watch. Do not adopt it if you are not writing ClojureScript at all, or if you need a documented upgrade and rollback procedure, because the README does not describe one.
- Can I use it commercially?
- Yes, with conditions. EPL-1.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Clojure, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What shadow-cljs is for, and who ends up using it
ClojureScript has always compiled to JavaScript, but the surrounding toolchain historically assumed a JVM-centric world of Leiningen and Clojars. shadow-cljs takes the other position: your JavaScript dependencies are npm packages, your build is described in an EDN file, and the compiler is launched through npx like any other Node tool. The README frames the goal plainly, saying shadow-cljs provides everything needed to compile ClojureScript "with a focus on simplicity and ease of use".
The audience follows from that. This is for developers who already write Clojure or ClojureScript and want to ship a browser bundle, a Node script, an npm package, a React Native app or a Chrome extension without hand-assembling a compiler pipeline. It is also for people arriving from JavaScript who want to try ClojureScript without first learning the JVM build ecosystem. If you are not writing ClojureScript, nothing here applies to you.
Targets, modules and the npm bridge
A shadow-cljs project is described by a shadow-cljs.edn file, which the README says is used to configure CLJS builds and CLJS dependencies, while package.json is left to npm for JS dependencies. That split is the core design decision: two package managers, two files, one build graph.
Builds live under a :builds key, and each build names a :target. The README lists :browser, :node-script, :npm-module, :react-native and :chrome-extension among the supported targets. Code splitting is handled by :modules, where each module entry can carry an :init-fn naming the function to call when the generated JavaScript loads. Source paths default to src/main, and the README recommends starting namespaces with a unique prefix rather than generic names like app.core, so that acme.frontend.app maps to src/main/acme/frontend/app.cljs.
The output side is conventional. With no :output-dir configured, the default is public/js, and a module named :main becomes main.js inside it. A :dev-http key maps a port to a directory, so :dev-http {8080 "public"} serves the public folder at http://localhost:8080. The README notes that saving the config starts the server automatically and that no restart is needed, which is a small detail that matters a lot during a first session.
Scaffolding a project and getting a first build running
The README's quick start uses npx create-cljs-project, which creates the scaffold and installs the latest shadow-cljs into the project. Running it produces package.json, package-lock.json, shadow-cljs.edn and src/main plus src/test. The npm output in the README also reminds you to commit the generated package-lock.json.
$ npx create-cljs-project acme-appBefore any of this, the README lists requirements: Node.js or Bun, one of npm, bun, pnpm or yarn, and a Java SDK at version 21 or newer, with the latest LTS recommended. That Java requirement is easy to miss and is the most common reason a first run fails.
If you only want to poke at the language, no build config is needed. The README gives two REPL entry points:
$ npx shadow-cljs node-repl
# or
$ npx shadow-cljs browser-replFor a real build, create a source file at the path implied by its namespace. For the namespace acme.frontend.app, the README expects src/main/acme/frontend/app.cljs, containing at least an init function:
(ns acme.frontend.app)
(defn init []
(println "Hello World"))Then add a build to shadow-cljs.edn. This config tells the compiler to call acme.frontend.app/init when the generated JavaScript loads, and writes the result to the default public/js directory:
{...
:builds
{:frontend
{:target :browser
:modules {:main {:init-fn acme.frontend.app/init}}
}}}Start the watch process with the build id from that config:
$ npx shadow-cljs watch frontendThe README shows the output line as a build summary with file counts, compiled counts, warnings and elapsed time, then notes that public/js/main.js is created. Add a public/index.html that loads /js/main.js in a script tag, switch the config to include :dev-http {8080 "public"}, and open http://localhost:8080. The browser console should print Hello World. The README recommends serving over HTTP rather than opening the file from disk, because browsers restrict file-loaded pages in ways that cause problems later.
Where shadow-cljs is the wrong tool
The clearest limitation is the one the README states about itself: the User Manual is linked as "Work in Progress", and the quick start ends with "To be continued ...". A reader looking for an authoritative answer about a specific target, a caching edge case or a migration path will often land on the guide and find it incomplete. That is a real cost, not a nitpick, because the compiler has many targets and the documentation does not cover them evenly.
Second, the Java SDK requirement at version 21 or newer is a hard floor. Teams with older JDKs, or CI images pinned to an older LTS, will have to change their environment before the compiler runs at all.
Third, this is a ClojureScript compiler. If your project is JavaScript or TypeScript, shadow-cljs solves nothing for you, and the two-file configuration split between shadow-cljs.edn and package.json is overhead you would be paying for no benefit. Someone searching for a general bundler should look elsewhere.
The README also does not document rollback or how to pin and upgrade the compiler version. Since create-cljs-project installs "the latest version", a project scaffolded today and one scaffolded six months ago may start on different compiler versions unless you pin the dependency yourself. Nothing in the README describes a downgrade procedure, so treat version pinning as something you decide deliberately rather than something the tool decides for you.
shadow-cljs compared with figwheel and plain bundlers
The most common comparison is with figwheel, which is the other well-known ClojureScript live-reload toolchain. The difference in approach is where the dependency management sits. Figwheel's traditional setup grows out of the Leiningen and Clojars world, with the JVM build tool as the center of gravity. shadow-cljs inverts that: npm is the dependency manager for JavaScript, package.json and package-lock.json sit next to shadow-cljs.edn, and the whole thing is invoked through npx. If your project already has a node_modules tree and a package.json that you care about, shadow-cljs meets you there instead of asking you to route everything through a JVM tool.
Against a plain JavaScript bundler, the difference is more fundamental. A bundler consumes JavaScript and produces JavaScript. shadow-cljs consumes ClojureScript and produces JavaScript, and the interesting work happens in the compiler: namespace resolution, the :init-fn call convention, module splitting via :modules, and a REPL that connects to a running build. It also ships its own dev HTTP server through :dev-http, so a first project does not need a separate static server. That bundling of concerns is the trade-off: fewer moving parts up front, but the dev server and the compiler are one dependency rather than two you can swap independently.
Maintenance, licence and the upgrade question
The repository is not archived, and the last push was on 2026-09-16, which is recent. The project is licensed under EPL-1.0, the Eclipse Public License 1.0. That is a permissive-ish copyleft licence with a weak reciprocity scope, and it is the same licence family used across much of the Clojure ecosystem. This is not legal advice; if you are redistributing the compiler inside a product, read the licence text and talk to whoever handles licensing at your organisation.
On upgrade cost, the material is thin. The repository keeps a CHANGELOG.md at the top level, which is where release notes would live, but no release entries were retrieved for this review, so there is no way here to characterise how disruptive upgrades tend to be. The README does not describe a rollback path or a pinned-version workflow. Practically, that means the upgrade decision is yours: pin the shadow-cljs version in package.json, and read CHANGELOG.md before moving that pin, because nothing in the quick start tells you what changes between versions.
Editorial conclusion
Adopt shadow-cljs if your codebase is ClojureScript and your dependencies live on npm, since the scaffold, watch build and REPL are all wired together by npx create-cljs-project and shadow-cljs watch. Do not adopt it if you are not writing ClojureScript at all, or if you need a documented upgrade and rollback procedure, because the README does not describe one. Before committing, verify that Java 21 or newer is available on every machine that will run the build, and confirm which target your project actually needs among :browser, :node-script, :npm-module, :react-native and :chrome-extension.
Frequently asked questions
What is shadow-cljs?
It is a ClojureScript compiler and build tool. The README describes it as providing everything needed to compile ClojureScript code, with support for targets such as :browser, :node-script, :npm-module, :react-native and :chrome-extension, plus a REPL and live reload.
How do I install shadow-cljs?
The README's quick start uses npx create-cljs-project, which creates the project scaffold and installs the latest shadow-cljs into the project. It requires Node.js or Bun, a package manager such as npm, bun, pnpm or yarn, and a Java SDK at version 21 or newer.
What does the shadow-cljs.edn file do?
According to the README, shadow-cljs.edn is used to configure your CLJS builds and CLJS dependencies, while package.json is used by npm to manage JS dependencies. Builds are declared under a :builds key, and each build names a :target.
How do I start a REPL with shadow-cljs?
The README gives two commands for this: npx shadow-cljs node-repl and npx shadow-cljs browser-repl. It notes that everything is ready to go for a REPL immediately after the project is created.
Does shadow-cljs serve files over HTTP during development?
Yes. Adding a :dev-http key to the config, for example :dev-http {8080 "public"}, starts a built-in web server that serves that directory. The README states the server starts automatically once the config is saved and that shadow-cljs does not need to be restarted.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/thheller-shadow-cljs)