I’m reworking the “Launcher” for shadow-cljs and would be interested to hear what people think about my plans. I’m undecided on which way to go since there are several options each with their own pros/cons.

The launcher is used to download maven :dependencies and starting the JVM with the proper classpath. shadow-cljs basically consists of 3 separate parts:

Clojure Library doing all the work The Standalone Launcher The npm package providing the shadow-cljs CLI command

shadow-cljs supports 3 different launchers currently: lein via :lein+project.clj , tools.deps clojure via :deps+deps.edn and its standalone version. lein and tools.deps are not affected by this work and will stay exactly the same as they are now. You can even skip 2,3 entirely and just use the Clojure Library directly.

Issue #1

Previously the launcher was distributed via the shadow-cljs-jar npm package which the shadow-cljs npm package just depended on. This meant that the standalone launcher was always downloaded even when actually using the other launchers.

Issue #2

Due to the way npm operates the launcher would be downloaded for each project separately although it rarely changes and each project could use a shared version instead. 5 projects currently means 5 copies which is not the end of the world but could be improved.

Issue #3

The launcher was very limited: download dependencies and build a classpath. This is exactly the same way tools.deps and lein operate. A standalone “uberjar” is used to complete that job and then a secondary JVM is launched to run the actual application. This works well but means that sometimes 2 JVMs are started. I also want to get to the point where :dependencies can be added dynamically without restart which would require duplicated work in the Clojure Library + Launcher.

Plan A

Keep everything the way it is.

Plan B

Use the shadow-cljs JS script to download the launcher independently from npm . The launcher could be distributed via Github or so and shared between projects. It would only be downloaded when required, meaning lein or tools.deps users wouldn’t. This is also how tools.deps and lein work basically.

Plan C

Plan B + make the launcher a bit smarter to enable dynamic classpath additions and such.

Plan D

Actually bundle shadow-cljs with the launcher. Saves the first initial download step, can do more optimizations for startup. Could investigate GraalVM native-image for nearly instant startup. Downside is that shadow-cljs has far more frequent releases than the launcher. So “upgrading” would mean a larger download.

Bonus Level

Actually create a completely standalone executable. One that you could double-click to launch and even get an actual GUI application at some point. Terminal is so 20 years ago. I think we can do better and I personally want that. CLI version will stay for CI and such but developer experience would be much improved if you could click a thing instead of running 3 separate terminals or so. IMHO,YMMV. For those that remember there used to be cuttle for CLJS and the Vue world has a pretty badass UI too. There is also guppy which is fairly new but looks interesting too.

The final Question

I’m currently undecided whether to go with C or D and want the Bonus Level

Which would you prefer: A more frequent larger download (~40MB) or a less frequent ~20MB download plus a separate more frequent ~20MB download? Am I overthinking this?

If you use lein to deps nothing changes for you but you’ll miss out on some of the AOT optimizations (4sec startup vs 9sec startup). shadow-cljs will stay a Clojure Library primarily and this is just about finding the most optimal way to launch it.