shadow-cljs

Marten Sytema 2025-04-10T14:01:25.672079Z

Hi folks, I have this project / editor setup: • emacs / cider • I ’m migrating from figwheel to shadows • it’s a mono repo with server-side code (clojure) and cljs • server side i want to start a http-kit server • and i want to run shadow-cljs • preferrably by just running one command: cider-jack-in-clj-and-cljs Is this possible? I was talking to chatGPT but eventhough it’s very confident, it doesn’t seem to help me. I get this: Caused by: java.io.FileNotFoundException: Could not locate shadow/cljs__init.class, shadow/cljs.clj or shadow/cljs.cljc on classpath. I was planning to run my app in locally via:

(ns cs2.dev
  (:require
   [shadow.cljs.devtools.api :as shadow]
   [cs2.handler :refer [handler]]
   [org.httpkit.server :as http]))

(defonce server (atom nil))

(defn start-httpkit! []
  (when-not @server
    (println "Starting HTTP-Kit server on port 3447")
    (reset! server (http/run-server handler {:port 3447}))))

(defn stop-httpkit! []
  (when-let [stop-fn @server]
    (stop-fn)
    (reset! server nil)
    (println "HTTP-Kit server stopped")))

(defn watch-client! []
  (println "Starting shadow-cljs watch for :app")
  (shadow/watch :app))

(defn start-dev! []
  (start-httpkit!)
  (watch-client!)
  (println "✅ Dev environment running."))

thheller 2025-04-10T15:14:25.105119Z

if you run embedded you need to start shadow-cljs via server/start! https://shadow-cljs.github.io/docs/UsersGuide.html#embedded

Marten Sytema 2025-04-10T14:01:43.697469Z

is what i want possible at all, or do i need a separate watch terminal running

martinklepsch 2025-04-10T16:08:20.928809Z

I just spent a bit of time seeing how far I could push the JS integration, specifically if I could integrate something like shadcn Some notes from that process: • The src/gen approach seemed good, I ended up using swc to compile .tsx files but otherwise 👌 • Shadcn heavily relies on aliases to create location-independent, consistent requires in the form of @/ui/components/badge etc, adding the snippet below to .swcrc seemed to make that work • Finally I ran into ReferenceError: Can't find variable: cn$$module$js$lib$utils — the source JS file looks reasonable so this is where I gave up 😅

"baseUrl": ".",
    "paths": {
      "@/*": ["./src/js/*"]
    }

martinklepsch 2025-04-10T16:11:01.137299Z

Full SWC config and command

yarn swc -w src/js --out-dir src/gen --strip-leading-paths
{
  "$schema": "",
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": true
    },
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "./src/js/*"
      ]
    },
    "transform": {
      "react": {
        "runtime": "automatic"
      }
    }
  }
}

thheller 2025-04-10T16:29:16.777919Z

anything larger than just 1 or 2 files I'd recommend putting into packages/foo/that/stuff and (:require ["foo/that/stuff" :as x]) + :js-options {:js-package-dirs ["packages" "node_modules"]}. that gives those files npm semantic (e.g. :simple only)

thheller 2025-04-10T16:30:14.608679Z

still needs conversion from JSX of course, but it basically becomes your own npm package that way

martinklepsch 2025-04-10T16:33:10.744959Z

ohh

martinklepsch 2025-04-10T16:33:40.768959Z

does this need a package.json etc?

thheller 2025-04-10T16:35:09.206769Z

yeah, npm init -y in packages/foo is usually sufficient

thheller 2025-04-10T16:35:52.499749Z

I'm not sure if you can configure shadcn to use a different alias, otherwise packages/@ is a valid folder name 😉

martinklepsch 2025-04-10T16:41:06.925029Z

hmmm that makes it a bit more complicated 😄

martinklepsch 2025-04-10T16:41:23.009209Z

I'll keep playing with this, thanks for the ideas!

martinklepsch 2025-04-10T16:41:57.127389Z

I guess one other option with this is also just bundling all JS into a single foreign lib and importing from that?

martinklepsch 2025-04-10T16:42:13.594629Z

I guess then you have the challenge of shared deps etc

thheller 2025-04-10T16:42:19.876169Z

not a fan of bundling twice, but sure 😛

thheller 2025-04-10T16:42:27.004299Z

:js-provider :external also works for that

Marten Sytema 2025-04-10T20:16:17.380689Z

i am trying to migrate to shadow-cljs, and when I run npx shadow-cljs watch app, it also starts compiling .clj files (ie. the server-side part / Ring part of the webapp app). Is this expected? Because I don’t think i would want that

thheller 2025-04-10T20:29:09.156319Z

most likely this is triggered by a user.clj on your classpath. clojure unconditionally loads that and shadow-cljs can't stop it.

Marten Sytema 2025-04-10T20:31:26.431239Z

yeah i have that file. I use it from time to time. Do you recommend not using :deps true? Or does user.clj need to go

thheller 2025-04-10T20:32:24.410039Z

I usually avoid having a user.clj due to the unconditional load

Marten Sytema 2025-04-10T20:32:42.829949Z

okay thanks!

thheller 2025-04-10T20:32:44.311039Z

you can move it so its only on the classpath when you start the server

Marten Sytema 2025-04-10T20:32:52.712239Z

yeah i see

Marten Sytema 2025-04-10T20:32:53.666419Z

thanks!

thheller 2025-04-10T20:33:20.261769Z

or change the code in that file, so that it only does stuff when actually needed 😛

Marten Sytema 2025-04-10T20:33:44.096679Z

yeah

Marten Sytema 2025-04-10T20:33:56.440709Z

thanks sir!

Marten Sytema 2025-04-10T20:35:22.642069Z

BTW: chatGPT was helpful, but i told it you were more helpful 😄

thheller 2025-04-10T20:36:16.352949Z

hehe

Marten Sytema 2025-04-10T20:17:10.542319Z

$> npx shadow-cljs watch app
shadow-cljs - config: /Users/marten/Sites/clojure/cs2/shadow-cljs.edn
shadow-cljs - starting via "clojure"
Warning: environ value 30 for key :retention-days has been cast to string
Warning: environ value true for key :is-dev has been cast to string
Warning: environ value 640 for key :image-thumb-width has been cast to string
those environ things are a server side thing

Marten Sytema 2025-04-10T21:06:16.662369Z

i’m almost there… migrating from figwheel-main -> shadow

[:app] Build failure:
The required namespace "webpack.bundle" is not available, it was required by "cs2/core.cljs"
One more thing, i have a bundle.js, that’s built with webpack, that i previously just required like
(cs2.core 
 (:require [webpack.bundle])
The compiled bundle.js is still tehere, and this is my shadow-cljs.edn
{:deps true
 :builds
 {:app
  {:target :browser
   :output-dir "public/js"
   :asset-path "/js"
   :modules {:main {:init-fn cs2.core/init}}
   :foreign-libs [{:file "resources/public/js/compiled-webpack/bundle.js"
                   :provides ["webpack.bundle"]
                   :global-exports {webpack.bundle window}}]
   }}}
and this was how it was compiled:
window.deps = {
    'parchment' : require('parchment'),
    'variableblot' : require('../quill/VariableBlot'),
    'menumarkerblot' : require('../quill/MenuMarkerBlot'),
    'uppy-core' : require ('@uppy/core'),
    'uppy-xhrupload' : require('@uppy/xhr-upload'),
    'uppy-dashboard' : require('@uppy/dashboard'),
    'uppy-dragdrop' : require('@uppy/drag-drop'),
    'uppy-progressbar' : require('@uppy/progress-bar'),
    'uppy-fileinput' : require('@uppy/file-input'),
    'uppy-droptarget' : require('@uppy/drop-target'),
    'uppy-imageeditor' : require('@uppy/image-editor'),
    'papaparse' : require('papaparse')
};

window.Uppy = window.deps['uppy-core'];
window.XHRUpload = window.deps['uppy-xhrupload'];
window.Dashboard = window.deps['uppy-dashboard'];
window.DragDrop = window.deps['uppy-dragdrop'];
window.DropTarget = window.deps['uppy-droptarget'];
window.ProgressBar = window.deps['uppy-progressbar'];
window.FileInput = window.deps['uppy-fileinput'];
window.ImageEditor = window.deps["uppy-imageeditor"];
window.Parchment = window.deps['parchment'];
window.VariableBlot = window.deps["variableblot"];
window.MenuMarkerBlot = window.deps["menumarkerblot"];
window.Papa = window.deps["papaparse"];

thheller 2025-04-10T21:10:38.962899Z

shadow-cljs doesn't support :foreign-libs. Usually you'd just require the things directly without the webpack.bundle at all. so just (:require ["parchment" :as parchment]) and using parchment directly instead of js/parchment

thheller 2025-04-10T21:11:13.415179Z

if for some reason you want to stick to webpack you can use :js-provider :external which basically generates that JS code for you

thheller 2025-04-10T21:12:24.838589Z

or if you want it manual you just have webpack build that file and you include it separately via its own <script> tag

thheller 2025-04-10T21:12:40.336259Z

this you can just remove then (:require [webpack.bundle])

Marten Sytema 2025-04-10T21:12:58.857809Z

right, so indeed most of these things i can put in package.json is what you are saying, right? The two Blot things i created myself back in the day… so i need to make those js modules presumably

thheller 2025-04-10T21:13:02.761589Z

assuming you access things via js/Uppy and stuff anyway

Marten Sytema 2025-04-10T21:13:04.159389Z

ah yeah of course that’s also an option

Marten Sytema 2025-04-10T21:13:10.628279Z

i do yeah

Marten Sytema 2025-04-10T21:13:22.969299Z

that’s pragmatic indeed

thheller 2025-04-10T21:13:31.263699Z

then just build it like you are used to and don't tell shadow-cljs about it 😉

Marten Sytema 2025-04-10T21:13:45.068899Z

🤫 ack

Marten Sytema 2025-04-10T21:06:44.922119Z

should I do that differently? or should this just work with :foreign-libs?