shadow-cljs

agorgl 2025-06-15T08:03:33.396279Z

I'm switching into using shadow-cljs from my dev/user.clj with the api (trying to get rid of node). I got most of my hints from this article: https://code.thheller.com/blog/shadow-cljs/2024/10/18/fullstack-cljs-workflow-with-shadow-cljs.html At some point it is recommended to add {:shadow/requires-server true} to the start fn that calls the watch, but the user guide says Commands that do long-running things implicitly start a server instance (eg. watch) but it is often advisable to have a dedicated server process running. I noticed that even without the requires-server hint the watch works, what's the catch? My simple setup is a repl started with clj -M:dev:test:repl/headless:cljs where the :cljs alias is:

:cljs {:extra-paths ["src/cljs"]
         :extra-deps {thheller/shadow-cljs {:mvn/version "3.1.6"}}
         :main-opts ["-m" "shadow.cljs.devtools.cli" "clj-repl"]}
After that I just run in my dev/user.clj a simple (shadow.cljs.devtools.api/watch :app) manually by hand

thheller 2025-06-15T08:04:58.910029Z

this is where I'm currently at with this whole setup

thheller 2025-06-15T08:05:09.592619Z

so you run clj -M:dev:start

thheller 2025-06-15T08:05:49.294399Z

this then runs https://github.com/thheller/shadow-stack-mvp/blob/main/src/dev/repl.clj#L51

thheller 2025-06-15T08:06:22.311889Z

that then starts the shadow-cljs server

thheller 2025-06-15T08:07:45.478689Z

dunno what you are doing but {:shadow/requires-server true} would be required if you are never starting a server someplace else

thheller 2025-06-15T08:08:04.555759Z

but only if actually going through shadow.cljs.devtools.cli, which my stuff doesn't do (anymore)

thheller 2025-06-15T08:09:09.251419Z

many ways to do this, kinda depends on personal preference

agorgl 2025-06-15T08:10:19.750289Z

Hmm I suppose I could get rid of the :main-opts ["-m" "shadow.cljs.devtools.cli" "clj-repl"] and get a normal repl, that then drives the dev processes using the shadow.cljs.devtools.api

agorgl 2025-06-15T08:10:52.397409Z

It seems I was trying to do the same thing as the examples you posted above, but with extra steps 🙂

agorgl 2025-06-15T08:11:26.115289Z

I suppose your article above gives a little bit outdated advice 😛

thheller 2025-06-15T08:11:45.617849Z

not really

thheller 2025-06-15T08:11:51.966399Z

it still all applies just fine

agorgl 2025-06-15T08:12:24.555579Z

What is your preferred way of managing other long running subprocesses along the shadow-cljs watch?

agorgl 2025-06-15T08:12:48.461889Z

E.g. If I wanted to run the tailwindcss watch process alongside the shadow-cljs watch process, how would you do it?

thheller 2025-06-15T08:12:59.180729Z

same way that does 😛

agorgl 2025-06-15T08:13:02.187889Z

Ah yes

agorgl 2025-06-15T08:13:04.544089Z

fantastic

agorgl 2025-06-15T08:13:15.106619Z

this is great

agorgl 2025-06-15T08:13:25.757379Z

I can't thank you enough

agorgl 2025-06-15T08:14:37.015909Z

With shadow-cljs api, replicant and a good skeleton I might actually arrive at a point that having a single repo full-stack clj/cljs app doesn't feel heavily glued with lots of external tools

thheller 2025-06-15T08:15:12.214069Z

nothing wrong with external tools built to glue stuff together if you ask me 😛

agorgl 2025-06-15T08:15:45.811259Z

In the past I did use npm run-p to manage shadow-cljs and tailwindcss processes

agorgl 2025-06-15T08:16:00.694389Z

but recovering from the tailwindcss crashes was a pain

agorgl 2025-06-15T08:16:30.153229Z

Also I prefer a lot to manage my whole codebase just with clojure

thheller 2025-06-15T08:17:30.296749Z

I do too, but usually that means more work 😛

agorgl 2025-06-15T08:18:24.058859Z

One more question: does the :dev-http server start with the watch command or with the server command?

thheller 2025-06-15T08:20:11.764899Z

(server/start!) as :dev-http is not tied to any particular build and always runs regardless of watch

agorgl 2025-06-15T08:29:58.121319Z

thanks again for the clarifications

agorgl 2025-06-15T08:39:48.058749Z

Sorry again, I have one more question about your mpv example: what does requiring [shadow.user] namespace do in the repl ns?

thheller 2025-06-15T08:41:11.086979Z

for this starts a REPL for the process you are starting itself

thheller 2025-06-15T08:41:43.979689Z

not sure I have actually yet used that for anything, but the intent was to have a REPL for the command line process

thheller 2025-06-15T08:41:51.395499Z

and I wanted that to switch to the shadow.user namespace

thheller 2025-06-15T08:42:25.835449Z

you can adjust that however you like, it doesn't do anything "required". if you don't feel like using that just remove it

thheller 2025-06-15T08:42:42.309159Z

but the in-ns there requires the namespace to be already loaded

agorgl 2025-06-15T08:43:10.778979Z

In my case that I use the repl headless (I just connect to it from my editor (nvim with conjure)) I suppose its kinda redundant

thheller 2025-06-15T08:43:30.921969Z

then you can skip that entire thing yeah

thheller 2025-06-15T08:44:01.612179Z

there have been times where I wanted to interact with the process without opening my editor first

thheller 2025-06-15T08:44:06.432809Z

but its rare 😛

agorgl 2025-06-15T08:47:10.921739Z

should I also require cider/nrepl middleware to avoid :missing-nrepl-middleware?

thheller 2025-06-15T08:48:31.393329Z

if you intend to use that middleware it needs to be on the classpath, as in added to your dependencies

agorgl 2025-06-15T08:49:07.041319Z

it is

thheller 2025-06-15T08:49:43.302869Z

well do you actually call it over nrepl?

thheller 2025-06-15T08:49:56.693189Z

I'm guessing blindly, so this is kinda pointless

agorgl 2025-06-15T08:51:19.964639Z

my global deps.edn is this one from Johnny, and I use this alias: https://github.com/practicalli/clojure-cli-config/blob/140a24384e574be43f2264b9aa8855201b9bbd32/deps.edn#L119

thheller 2025-06-15T08:51:42.542209Z

that is not what I asked 😛

thheller 2025-06-15T08:51:49.996079Z

you are calling nrepl-select somewhere

thheller 2025-06-15T08:52:08.044349Z

is that call actually triggered over an nrepl connection

thheller 2025-06-15T08:52:27.427129Z

if its called as part of start or whatever your setup has, then it isn't invoked over nrepl, thus will fail

agorgl 2025-06-15T08:59:24.586479Z

ok so I start my repl using clj -M:dev:test:repl/headless:cljs where these aliases are the one you would expect:

{:dev {:extra-paths ["dev/clj"]}
  :test {:extra-paths ["test/clj"]
         :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"}
                      io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}}}
  :cljs {:extra-paths ["src/cljs"]
         :extra-deps {thheller/shadow-cljs {:mvn/version "3.1.6"}}}}}
(+ the :repl/headless alias above) I then connect to the normal clojure repl using neovim with conjure (same as all my projects). I eval the dev/user.clj namespace and call (start) by hand from my editor, that is over the nrepl connection). I was used to call ConjureShadowSelect (that calls shadow.cljs.devtools.api/nrepl-select https://github.com/Olical/conjure/blob/8edc78f7cf2cd49c0feba3ddd9a29eca1673a8ce/doc/conjure-client-clojure-nrepl.txt#L50) before opening to a cljs file to 'switch' my repl to a cljs one. I thought that starting the shadow-cljs server (and watch) was a clojure/host operation, do I understand something wrong?

thheller 2025-06-15T09:01:15.941389Z

you giving me partial information makes that kinda hard to answer 😛

thheller 2025-06-15T09:01:49.278709Z

i.e. thats a different :cljs alias than the one you posted above

thheller 2025-06-15T09:01:51.657829Z

which one is it?

agorgl 2025-06-15T09:02:13.988759Z

ah yes, I changed it after your example 🙂

thheller 2025-06-15T09:03:08.013609Z

ok, so the shadow-cljs nrepl server will automatically add the cider nrepl middleware if its on the classpath

thheller 2025-06-15T09:03:40.689679Z

assuming you are actually connecting to the nrepl server shadow-cljs started then it should just work with no extra configuration

agorgl 2025-06-15T09:03:58.170049Z

yes thats what I assumed too

thheller 2025-06-15T09:04:15.192719Z

also be aware that all code in dev/user.clj will ALWAYS run whenever ANY clojure process is started

thheller 2025-06-15T09:04:51.533109Z

so depending on how that file looks it may have unintended effects if you think that you triggering the load over the REPL is the "first" load

thheller 2025-06-15T09:05:28.524769Z

there is a reason I outlined this exact start/stop workflow in my post and that it wasn't in the user namespace

thheller 2025-06-15T09:06:26.257889Z

so, unless you provide me with more information I really cannot help you further

agorgl 2025-06-15T09:06:37.525789Z

I am not starting anything automatically, so far my user.clj looks like this:

(ns user
  (:require
   [shadow.cljs.devtools.api :as shadow]
   [shadow.cljs.devtools.server :as shadow-server])
  (:import
   [java.lang ProcessBuilder ProcessBuilder$Redirect]))

(defn shadow-watch []
  (shadow-server/start!)
  (shadow/watch :app))

(defn css-watch []
  (-> ["npx" "tailwindcss" "-i" "src/css/site.css" "-o" "target/classes/public/css/main.css" "--watch"]
      (ProcessBuilder.)
      (.redirectError ProcessBuilder$Redirect/INHERIT)
      (.redirectOutput ProcessBuilder$Redirect/INHERIT)
      (.start)))

(defn watch []
  (shadow-watch))
  ;(css-watch))

(comment
  (watch))

thheller 2025-06-15T09:07:10.736919Z

I strongly recommend not using user, but otherwise that looks ok

agorgl 2025-06-15T09:07:36.786229Z

After your above hint I'll probably move it to a dedicated ns too

thheller 2025-06-15T09:07:39.900849Z

where/how/what calls watch

agorgl 2025-06-15T09:07:58.627459Z

I manually call watch from my editor

agorgl 2025-06-15T09:08:06.269519Z

(over the nrepl connection)

thheller 2025-06-15T09:08:17.503169Z

well ... then something isn't quite right is it?

thheller 2025-06-15T09:08:46.227879Z

you are starting the shadow-cljs server in that shadow-watch. so its nrepl server won't be running beforehand? so how are you connected to the repl to call it in the first place? 😛

agorgl 2025-06-15T09:09:07.490119Z

ahhhhhh

agorgl 2025-06-15T09:09:15.681899Z

now I kinda understand

agorgl 2025-06-15T09:09:22.888199Z

shadow/server starts its OWN nrepl server

thheller 2025-06-15T09:09:43.984459Z

which I assumed you are using? but guess not

agorgl 2025-06-15T09:10:05.609969Z

and I call shadow-server/start from my nrepl connection, spawing another second nrepl connection (shadow-cljs one)

thheller 2025-06-15T09:10:38.334609Z

what for though? 😛

agorgl 2025-06-15T09:10:45.546849Z

so thats the problem, I need to spawn the shadow-cljs server with its nrepl connection first and connect to THAT

thheller 2025-06-15T09:10:57.220319Z

or add the shadow-cljs nrepl middleware to the first

agorgl 2025-06-15T09:10:58.052839Z

so that is why you have a main entrypoint for this

thheller 2025-06-15T09:11:28.660589Z

I do not know what you mean by this

agorgl 2025-06-15T09:11:57.943479Z

so let me type real quick an overview of what I understand for the problem:

thheller 2025-06-15T09:12:16.617699Z

(main/repl :init repl-init) if you mean that line then no that has absolutely zero to do with it

thheller 2025-06-15T09:12:41.783379Z

if you mean (start) being called in -main then yes, because that starts shadow-cljs and the whole other development setup

thheller 2025-06-15T09:12:56.956139Z

I want that to start stuff when the process starts, not when I connect to the REPL

agorgl 2025-06-15T09:15:34.103459Z

I start a normal clojure nrepl, I connect to it from my editor, and from that I spawn an shadow-cljs server+watch (by calling (watch) in the user ns). When the shadow-cljs server starts, it creates a specific nrepl connection itself that I am NOT connected into (I am connected into the nrepl connection of the process that started the shadow-cljs server through the api), so when I try to do nrepl-select I get the :missing-nrepl-middleware because I try to run it in the wrong nrepl connection

agorgl 2025-06-15T09:16:34.315089Z

A better way would be instead of manually calling (watch) to create an entrypoint as you do, assign it to an alias, run the (watch) (and thus the shadow-server/start) before connecting with my editor

thheller 2025-06-15T09:16:34.617069Z

dunno why you'd make your life complicated like that but ok 😛

agorgl 2025-06-15T09:16:54.014599Z

and then nrepl-select

thheller 2025-06-15T09:17:28.805399Z

shadow.cljs.devtools.server.nrepl/middleware this is the shadow-cljs middleware for nrepl, you need to add that to the first server you are starting, if you intend to switch the REPL in that nrepl connection

thheller 2025-06-15T09:18:05.056759Z

or just use one nrepl server 😛

agorgl 2025-06-15T09:18:09.591739Z

(also, I do not try to make my life complicated, its all a consequence of incorrect understanding of how some of the above things work)

agorgl 2025-06-15T09:18:41.204559Z

I think that I'll go with the one nrepl server route similar to what you have

agorgl 2025-06-15T09:18:47.835529Z

Now everything is clear, thank you