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 handthis is where I'm currently at with this whole setup
https://github.com/thheller/shadow-stack-mvp/blob/main/deps.edn#L11-L16
so you run clj -M:dev:start
this then runs https://github.com/thheller/shadow-stack-mvp/blob/main/src/dev/repl.clj#L51
https://github.com/thheller/shadow-stack-mvp/blob/main/src/dev/repl.clj#L16
that then starts the shadow-cljs server
dunno what you are doing but {:shadow/requires-server true} would be required if you are never starting a server someplace else
but only if actually going through shadow.cljs.devtools.cli, which my stuff doesn't do (anymore)
many ways to do this, kinda depends on personal preference
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
It seems I was trying to do the same thing as the examples you posted above, but with extra steps 🙂
I suppose your article above gives a little bit outdated advice 😛
not really
it still all applies just fine
What is your preferred way of managing other long running subprocesses along the shadow-cljs watch?
https://code.thheller.com/blog/shadow-cljs/2024/10/30/supercharging-the-repl-workflow.html
E.g. If I wanted to run the tailwindcss watch process alongside the shadow-cljs watch process, how would you do it?
same way that does 😛
Ah yes
fantastic
this is great
I can't thank you enough
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
nothing wrong with external tools built to glue stuff together if you ask me 😛
In the past I did use npm run-p to manage shadow-cljs and tailwindcss processes
but recovering from the tailwindcss crashes was a pain
Also I prefer a lot to manage my whole codebase just with clojure
I do too, but usually that means more work 😛
One more question: does the :dev-http server start with the watch command or with the server command?
(server/start!) as :dev-http is not tied to any particular build and always runs regardless of watch
thanks again for the clarifications
Sorry again, I have one more question about your mpv example: what does requiring [shadow.user] namespace do in the repl ns?
its for this https://github.com/thheller/shadow-stack-mvp/blob/main/src/dev/repl.clj#L48-L53
for this starts a REPL for the process you are starting itself
not sure I have actually yet used that for anything, but the intent was to have a REPL for the command line process
and I wanted that to switch to the shadow.user namespace
you can adjust that however you like, it doesn't do anything "required". if you don't feel like using that just remove it
but the in-ns there requires the namespace to be already loaded
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
then you can skip that entire thing yeah
there have been times where I wanted to interact with the process without opening my editor first
but its rare 😛
should I also require cider/nrepl middleware to avoid :missing-nrepl-middleware?
if you intend to use that middleware it needs to be on the classpath, as in added to your dependencies
it is
well do you actually call it over nrepl?
I'm guessing blindly, so this is kinda pointless
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
that is not what I asked 😛
you are calling nrepl-select somewhere
is that call actually triggered over an nrepl connection
if its called as part of start or whatever your setup has, then it isn't invoked over nrepl, thus will fail
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?you giving me partial information makes that kinda hard to answer 😛
i.e. thats a different :cljs alias than the one you posted above
which one is it?
ah yes, I changed it after your example 🙂
ok, so the shadow-cljs nrepl server will automatically add the cider nrepl middleware if its on the classpath
assuming you are actually connecting to the nrepl server shadow-cljs started then it should just work with no extra configuration
yes thats what I assumed too
also be aware that all code in dev/user.clj will ALWAYS run whenever ANY clojure process is started
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
there is a reason I outlined this exact start/stop workflow in my post and that it wasn't in the user namespace
so, unless you provide me with more information I really cannot help you further
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))I strongly recommend not using user, but otherwise that looks ok
After your above hint I'll probably move it to a dedicated ns too
where/how/what calls watch
I manually call watch from my editor
(over the nrepl connection)
well ... then something isn't quite right is it?
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? 😛
ahhhhhh
now I kinda understand
shadow/server starts its OWN nrepl server
which I assumed you are using? but guess not
and I call shadow-server/start from my nrepl connection, spawing another second nrepl connection (shadow-cljs one)
what for though? 😛
so thats the problem, I need to spawn the shadow-cljs server with its nrepl connection first and connect to THAT
or add the shadow-cljs nrepl middleware to the first
so that is why you have a main entrypoint for this
I do not know what you mean by this
so let me type real quick an overview of what I understand for the problem:
(main/repl :init repl-init) if you mean that line then no that has absolutely zero to do with it
if you mean (start) being called in -main then yes, because that starts shadow-cljs and the whole other development setup
I want that to start stuff when the process starts, not when I connect to the REPL
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
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
dunno why you'd make your life complicated like that but ok 😛
and then nrepl-select
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
or just use one nrepl server 😛
(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)
I think that I'll go with the one nrepl server route similar to what you have
Now everything is clear, thank you