Add shadow-cljs to existing Leiningen project

I have an existing clj/cljs project that is based on Leiningen and uses Figwheel. I would like to try out Shadow CLJS. So basically add it to my project.

I’m trying to follow the guide at https://shadow-cljs.github.io/docs/UsersGuide.html but I don’t think that I am quite getting it. It is recommended to use a “standalone” Shadow version and not integrate with Lein. How do I do that (follow the recommendation) when I have an existing Lein project? Or do I need to integrate with Lein?

Any pointers would be very appreciated!

Which part in particular do you struggle with?

You’ll need a shadow-cljs.edn and configure your build there. Once thats done you can use shadow-cljs.edn to manage your dependencies or continue doing so via project.clj. It is fine to use lein for server-side CLJ stuff and shadow-cljs.edn for CLJS related things. I do that in all my projects but some people prefer to have everything in project.clj.

You might get a better feeling for how shadow-cljs works by following the quickstart and setting up a little dummy project.

Thanks for your response @thheller! I think I just needed to process the user guide and your response was very helpful in that processing.

I have now managed to switch from figwheel to shadow-cljs and hot reloading, the cljs-repl, and re-frame-10x all work. Fantastic!

This is my shadow-cljs.edn

{:lein   true
 :builds {:app {:target           :browser
                :output-dir       "target/public/js"
                :asset-path       "/assets/public/js"
                :modules          {:main {:entries [app.admin.core]}}
                :devtools         {:devtools-url "http://localhost:9630"
                                   :preloads     [devtools.preload
                                                  day8.re-frame-10x.preload]}
                :compiler-options {:source-map      true
                                   :closure-defines {re-frame.trace.trace-enabled?        true
                                                     day8.re-frame.tracing.trace-enabled? true}}}}}
  1. I get hundreds of warnings that the Chrome devtools cannot load map-files, e.g., DevTools failed to load SourceMap: Could not load content for https://dev.bass4.com/assets/public/js/cljs-runtime/re_com.misc.js.map: HTTP error: status code 404, net::ERR_HTTP_RESPONSE_CODE_FAILURE.
    I’m running the app behind a reverse proxy, could that be it?

  2. The user guide says that “If the popular middleware cider-nrepl is found on the classpath (e.g. it’s included in :dependencies), it will be used automatically.” cider-nrepl was included as a lein plugin (from the figwheel config) so I kept it there, does that mean that it is used by shadow-cljs? How can I check that it is running? What does cider-nrepl do? (please excuse my ignorance!)

This is because your :asset-path in the config looks incorrect. That should match the path you use to access the .js files from the HTML. Dunno where the /assets comes from but the public is most definitely wrong. Maybe just :asset-path "/js"?

It is only relevant if you use emacs. The middleware provides stuff like code completion, documentation lookups, etc.

Yes, of course!

Ah yes. I removed all cider-nrepl and piggieback stuff and I am able to connect to the nREPL from Cursive.

Coming from Leiningen I’m used to using profiles to include different dependencies in dev and production builds and also to have an “env” folder where profile dependent namespaces are included. For instance, I only want to use Orchestra in development to instrument all speced functions. So orchestra is included as a dependency in the Leiningen dev profile and I call Orchestra’s instrument function in a namespace that is in the env/dev folder. The corresponding namespace in the env/prod folder does not require the orchestra namespace.

Is it possible to achieve the same effect using Shadow CLJS? Note that I am using Leiningen for my dependency management.

In shadow-cljs by default all builds only include what they actually require. See this post. This means that the dev/prod separated :source-paths are usually pointless and thus not supported.

As far as instrument is concerned I would recommend creating a single ns that does this and including it via :preloads into the build. That will ensure its only active during development.

Thanks! I tried it out and it sort of works. Problem is that instrument needs to run after all other namespaces have been loaded so that the functions have been speced. Or am I missing something?

:preloads by definition are loaded first but they still follow :require rules. So just simply require your main app namespace in that ns and you should be good. If not just :require the problematic namespaces as well.

BRILLIANT! It works. Thanks!

Okay, next problem :smile:

I want to automate a Shadow CLJS release build when i do lein uberjar so that the main.js file is included in the uberjar file

So I put the following in my project.cljs

{:uberjar {...
          :prep-tasks ["clean" ["clean"]
                       "compile"
                       ["run" "-m" "shadow.cljs.devtools.cli" "--npm" "release" "app"]]}}

It works, but only if I include [thheller/shadow-cljs "2.11.7"]among my regular lein dependencies. If I include Shadow CLJS in my dev dependencies, lein uberjar fails with Can't find 'shadow.cljs.devtools.cli' as .class or .clj for lein run:

Problem is, that including Shadow CLJS among my regular dependencies adds >40 MB to my uberjar file. I assume that none of that code is really needed?

Can I somehow let uberjar :prep-tasks access Shadow CLJS without including it as a dependency that is included in the uberjar?

I don’t know.

I just run shadow-cljs release app && lein uberjar or so via some shell command. I don’t see the point in trying to make lein do everything.

Fair enough :slight_smile:

I love workflows but I should be able to remember to use shadow-cljs release app && lein uberjar when I’m doing a release.

Thank you for your answers and your terrific work with Shadow CLJS @thheller!

1 Like

You can configure lein uberjar command

  :profiles {:uberjar {:aot :all
                       :prep-tasks ["compile"
                                    ["shadow-cljs" "release" "example"]
                                    "css-example"]}}

Thanks @Serioga, but doesn’t work. The uberjar fails with the message 'shadow-cljs' is not a task. See 'lein help'.

Well, it was about running tasks with lein uberjar command, not exact configuration for your case.
The example is taken from https://github.com/serioga/webapp-clojure-2020/blob/master/project.clj where “shadow-cljs” is an alias for external command.

Oh yeah, sorry! I looked at your source, and it looks like that would also compile Shadow CLJS into the uberjar. The alias looks very much like the :prep-tasks that I tried above.