shadow-cljs 2025-04-25

Working on a personal but open-source task management app for myself but mostly wanted to preview a finite-state-machine library I've been working on. It's intended to be framework agnostic with the ability to implement an IStateMachine protocol to create adapters for uix, replicant, and we'll see what else. For this app, I'm just using the basic atom-fsm but swapping out atoms for reagent-atoms. Below is a recording of using it to implement a fun slide to delete system as I realized I just don't want to implement another confirm modal system, so wanted to try something fun. Anyway, the state machine library is https://github.com/jaidetree/finity and the slide UI in the video is at https://github.com/jaidetree/tasky/blob/e063511423f80fea11493906d4a24a5e34db4db9/frontend/src/main/dev/jaide/tasky/views/delete_rocker.cljs#L66. Looking to release the FSM library sometime this month.

๐ŸŽ‰ 1

There's a spec->diagram function to take the fsm-spec atoms and transform them into mermaid charts. On this project, I've just been rendering them on the bottom of the page so they update when the ns reloads

interesting. wrote a fsm thing for myself recently. less formal though, but the graph thing is nice

minor nitpick but there is a [clojure.pprint :refer [pprint]] require in the core ns but never used

this is never DCE'd so just makes the build larger for no reason ๐Ÿ˜‰

Good catch! Fortunately it's still pre-release at this point. Is there a clever way to provide it during dev builds but in release builds replace it with (fn [_ & [_]] nil)?

of course there is an option for almost everything ๐Ÿ˜‰

:release {:build-options {:ns-aliases {cljs.pprint your.fake-pprint}}}

๐Ÿ˜ฎ 1

(ns your.fake-pprint) (defn pprint [x]) or so

but its an option a user would have to provide in their build config, so not ideal in a library setting

What I've done for similar stuff is to have prod/mylib/myns.cljc and dev/mylib/myns.cljc that define alternatives for the same namespace, and include one in the production/release build and the other during development.

yeah, that works too. not a fan of having to switch the classpath so, since that requires a new JVM (kindof) ๐Ÿ˜‰

It's not super ergonomic, but for ~static stuff you rarely need to fuzz with, it works well enough

Thanks! ns-aliases is exactly what I was looking for. I think what I'll do is inverse that, define a my-project.utils/pprint with a docstr that by default is (defn pprint [x]) then on dev builds alias it to cljs.pprint. That should protect me from myself in the future I think

๐Ÿ‘ 1
๐Ÿ‘Œ 1

Heh - fun. Have you any plans to implement hierarchical state support? I've been iterating on an in-house embedded C framework for years that's basically a publish-subscribe event broker where each subscriber is an hierarchical state machine (written in a handy DSL abusing variadic macros) - it's a nice way to work with in that context. Have been playing with cljs-statecharts in my cljs/replicant experiments to replicate (hoho) some of the benefits.

I just hit my first use-case for that where my tasks-fsm maps all the tasks from a fetch request to a task-fsm as part of its transition. That has been working well so far given I can leverage the fsm/subscribe function to make them talk to each other as needed. I am still thinking on how to better support that.

One idea that just came to mind this morning is to have some system level actions that are dispatched to the state machine and up to implementers and subscribers to handle. This way if you run (fsm/destroy a-task-fsm) it could broadcast a :fsm/destroy action to subscribers which could be used to remove it from the parent. That could cover most use-cases flexibly at the cost of some verbosity. Then on top of that I was thinking of adding :relationships to the fsm-spec so that you could express:

:relationships {:tasks {:has_many :tasks}}
which would inform the fsm that in the :tasks state the [:context :tasks] are children state machines which would take care of setting up the subscription for the :fsm/destroy system action and destroying the children when the parent is destroyed. Needs some more thought though.

Strange. Just setup :dev {:build-options {:ns-aliases {dev.jaide.pprint cljs.pprint}}} and now the compile step hangs then aborts with the error:

[:test] Build failure:                                                                                                                                                                                                    
Multiple files failed to compile.                                                                                                                                                                                         
aborted par-compile, [:shadow.build.classpath/resource "dev/jaide/valhalla/context_test.cljs"] still waiting for #{dev.jaide.valhalla.context}                                                                            
{:aborted [:shadow.build.classpath/resource "dev/jaide/valhalla/context_test.cljs"], :pending #{dev.jaide.valhalla.context}}                                                                                              
ExceptionInfo: aborted par-compile, [:shadow.build.classpath/resource "dev/jaide/valhalla/context_test.cljs"] still waiting for #{dev.jaide.valhalla.context}                                                             
        shadow.build.compiler/par-compile-one (compiler.clj:1232)                                                                                                                                                         
        shadow.build.compiler/par-compile-one (compiler.clj:1197)                                                                                                                                                         
        shadow.build.compiler/par-compile-cljs-sources/fn--15624/iter--15646--15650/fn--15651/fn--15652/fn--15653 (compiler.clj:1315)                                                                                     
        clojure.core/apply (core.clj:667)                                                                                                                                                                                 
        clojure.core/with-bindings* (core.clj:1990)                                                                                                                                                                       
        clojure.core/with-bindings* (core.clj:1990)                                                                                                                                                                       
        clojure.core/apply (core.clj:671)                                                                                                                                                                                 
        clojure.core/bound-fn*/fn--5837 (core.clj:2020)                                                                                                                                                                   
        java.util.concurrent.FutureTask.run (FutureTask.java:317)                                                                                                                                                         
        java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1144)                                                                                                                                  
        java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:642)                                                                                                                                  
        java.lang.Thread.run (Thread.java:1583)                                                                                                                                                                           
aborted par-compile, [:shadow.build.classpath/resource "shadow/test/node.cljs"] still waiting for #{dev.jaide.valhalla.integration-test dev.jaide.valhalla.js-test dev.jaide.valhalla.core-test dev.jaide.valhalla.context
-test}                                
[:test] Build failure:                                                                                                                                                                                                    
Multiple files failed to compile.                                                                                                                                                                                         
aborted par-compile, [:shadow.build.classpath/resource "dev/jaide/valhalla/context_test.cljs"] still waiting for #{dev.jaide.valhalla.context}                                                                            
{:aborted [:shadow.build.classpath/resource "dev/jaide/valhalla/context_test.cljs"], :pending #{dev.jaide.valhalla.context}}                                                                                              
ExceptionInfo: aborted par-compile, [:shadow.build.classpath/resource "dev/jaide/valhalla/context_test.cljs"] still waiting for #{dev.jaide.valhalla.context}                                                             
        shadow.build.compiler/par-compile-one (compiler.clj:1232)                                                                                                                                                         
        shadow.build.compiler/par-compile-one (compiler.clj:1197)                                                                                                                                                         
        shadow.build.compiler/par-compile-cljs-sources/fn--15624/iter--15646--15650/fn--15651/fn--15652/fn--15653 (compiler.clj:1315)                                                                                     
        clojure.core/apply (core.clj:667)                                                                                                                                                                                 
        clojure.core/with-bindings* (core.clj:1990)                                                                                                                                                                       
        clojure.core/with-bindings* (core.clj:1990)                                                                                                                                                                       
        clojure.core/apply (core.clj:671)                                                                                                                                                                                 
        clojure.core/bound-fn*/fn--5837 (core.clj:2020)                                                                                                                                                                   
        java.util.concurrent.FutureTask.run (FutureTask.java:317)                                                                                                                                                         
        java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1144)                                                                                                                                  
        java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:642)                                                                                                                                  
        java.lang.Thread.run (Thread.java:1583)                                                                                                                                                                           
aborted par-compile, [:shadow.build.classpath/resource "shadow/test/node.cljs"] still waiting for #{dev.jaide.valhalla.integration-test dev.jaide.valhalla.js-test dev.jaide.valhalla.core-test dev.jaide.valhalla.context
-test}                                

odd, looks fine to me

maybe try wiping .shadow-cljs/builds and restarting with watch or compile. could be that :ns-aliases doesn't cause the cache to invalidate properly

Ran rm -rf .shadow-cljs/builds, restarted shadow-cljs watch test, unfortunately still hanging

{:deps {:aliases [cljs]}
:builds
 {:test {:target :node-test
         :output-to "build/js/node-tests.js"
         :ns-regexp "-test"
         :autorun true
         :compiler-options {:warnings {:invalid-arithmetic false}}
         :devtools {:preloads [cljs.pprint]}
         :dev {:build-options {:ns-aliases {dev.jaide.pprint cljs.pprint}}}
         :release {:autorun false
                   :compiler-options {:optimizations :simple
                                      :elide-asserts false}}}}}

:devtools {:preloads [cljs.pprint]} maybe try removing that? shouldn't hurt but otherwise no guesses

maybe push it somewhere so I can try locally ๐Ÿ˜›

Miro Samek - author of a C/C++ UML statechart framework (QP) wrote a book I found very helpful years ago while writing my own. A good deal of the material should be implementation language agnostic, and see now the digital edition is free to download these days. He has a lot of lived experience working with this pattern. See here: https://www.state-machine.com/psicc2

Thanks @cormacc I'll take a look

hmm ok. I don't know how :ns-aliases have ever worked.

> aborted par-compile, [:shadow.build.classpath/resource "dev/jaide/valhalla/context.cljs"] still waiting for #{dev.jaide.pprint}

its basically waiting for the ns to be compiled that will never be compiled because its aliased

just pushed 3.0.3 to fix the par-compile issue. should be all good now

It works now! Thanks again

I have found an issue with Shadow-cljs 3.0.2. When I have it as a dev dependancy in package.json I get this issue:

shadow-cljs - config: /home/dev/src/mono/bec-core-client/shadow-cljs.edn
shadow-cljs - starting via "clojure"
shadow-cljs - HTTP server available at 
shadow-cljs - server version: 3.0.2 running at 
shadow-cljs - nREPL server started on port 4005
shadow-cljs - watching build :app
[:app] Configuring build.
[:app] Compiling ...
[:app] Build failure:
The required JS dependency "process" is not available, it was required by "node_modules/react/cjs/react.production.js".

Dependency Trace:
        bec_core/cljs/core.cljs
        bec_cljs_common/utils/interop.cljs
        bec_cljs_common/utils/formatting.cljs
        re_frame/core.cljc
        re_frame/events.cljc
        re_frame/db.cljc
        re_frame/interop.cljs
        reagent/core.cljs
        node_modules/react/index.js
        node_modules/react/cjs/react.production.js

Searched for npm packages in:
        /home/dev/src/mono/bec-core-client/node_modules

See: 
But if I downgrade it to 2.28.21 (haven't tested others) in package.json and use whatever version in deps.edn, then it works fine. Any ideas why?

๐Ÿ‘ 1

When I had version 3.0.2 in package.json and version 2.28.21 in deps.edn, the message I got was slightly different:

shadow-cljs - config: /home/dev/src/mono/bec-core-client/shadow-cljs.edn
shadow-cljs - starting via "clojure"
shadow-cljs - HTTP server available at 
shadow-cljs - server version: 2.28.21 running at 
shadow-cljs - nREPL server started on port 4005
shadow-cljs - watching build :app
[:app] Configuring build.
[:app] Compiling ...
[:app] Build failure:
The required JS dependency "process" is not available, it was required by "node_modules/react/cjs/react.production.js".

Dependency Trace:
        bec_core/cljs/core.cljs
        bec_cljs_common/utils/interop.cljs
        bec_cljs_common/utils/formatting.cljs
        re_frame/core.cljc
        re_frame/events.cljc
        re_frame/db.cljc
        re_frame/interop.cljs
        reagent/core.cljs
        node_modules/react/index.js
        node_modules/react/cjs/react.production.js

Searched for npm packages in:
        /home/dev/src/mono/bec-core-client/node_modules
process is part of the node-libs-browser polyfill package to provide node-native package support
for none-node builds. You should install shadow-cljs in your project to provide that dependency.

        npm install --save-dev shadow-cljs

See: 

I removed the automatic polyfilling of node built-in packages. process is one of them. you can just npm install process to fix this one

๐Ÿ‘ 2

Alright. Sounds good. Thanks! ๐Ÿ™‚

or add npm install node-libs-browser to get the whole previous thing, but its been deprecated for like 5 years, so it time to get rid of that ๐Ÿ˜›

maybe I'll fix this internally though. forgot about react doing this nonsense. this isn't intentional ๐Ÿ˜›

๐Ÿ‘ 1

I have now added the process package and it fixed my issue with react. So if you add it internally then just mention it somewhere so I can remove it from package.json again ๐Ÿ˜„

This is also required for plotly.js (and supabase libs require buffer) -- both readily resolved via explicit install as suggested.

We have had a similar issue to @shakof91 mentioned https://clojurians.slack.com/archives/C6N245JGG/p1745575597950839 with version https://clojurians.slack.com/archives/C6N245JGG/p1745479994211049. However, ours comes from a dependency on a library qr-image which claims to have zero dependencies but in fact requires both stream and zlib and while running npm install stream resolved the stream dependency, when running npm install zlib we get the following error:

npm error code 127
npm error path /home/stuart/work/portal/node_modules/zlib
npm error command failed
npm error command sh -c node-waf clean || true; node-waf configure build
npm error sh: 1: node-waf: not found
npm error sh: 1: node-waf: not found
npm error A complete log of this run can be found in: ~/.npm/_logs/2025-04-25T11_04_32_299Z-debug-0.log
Any thoughts?

those are also node built-ins. previously it used stream via stream-browserify and zlib via browserify-zlib. install those and set

:js-options
{:resolve
 {"zlib" {:target :npm :require "browserify-zlib"}
  "stream" {:target :npm :require "stream-browserify"}}}
in your build config

of course also need to manually install those packages via npm install

I guess I should actually test how webpack handles this these days, could have sworn it just fails as well

well, package is 8 years old. maybe not many people actually using this in the browser?

ERROR in ./node_modules/qr-image/lib/qr.js 3:15-41
Module not found: Error: Can't resolve 'stream' in '/Users/thheller/code/tmp/webpack-test/node_modules/qr-image/lib'

BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.

If you want to include a polyfill, you need to:
	- add a fallback 'resolve.fallback: { "stream": require.resolve("stream-browserify") }'
	- install 'stream-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
	resolve.fallback: { "stream": false }
resolve 'stream' in '/Users/thheller/code/tmp/webpack-test/node_modules/qr-image/lib'
  Parsed request is a module

ok actually fails as expected

maybe should add a more detailed error message like that, otherwise I'll probably be answering questions like that for a while ๐Ÿ˜›

false also works, but I'm assuming the packages are actually used so that would probably break something

as in {"zlib" false}

Thank you, that has worked following require a few more dependencies, namely events , process &`assert` .

yikes, you are on some really old packages I guess? ๐Ÿ˜›

most popular stuff has been updated by now

The qr-image is quite old, we may look for a newer equivalent, but we liked the fact it had "zero" dependencies ๐Ÿ˜„.

I think a more detailed error message similar to webpack would have helped, or at a minimum in the docs have an example of the indirection required for the node built-ins in the https://shadow-cljs.github.io/docs/UsersGuide.html#_missing_js_dependency (or maybe just a mention in the changelog).

Thank you again for helping us out

๐Ÿ‘ 1

I'm trying (in admittedly rather stupid ways) to switch a large project from leiningen + figwheel-main to deps.edn, tools.build and shadow-cljs. As I'm banging my head against various obstacles, I encountered this:

[2025-04-25 20:28:57.370 - WARNING] :shadow.cljs.devtools.errors/format-error
Note: The following stack trace applies to the reader or compiler, your code was not executed.
ExceptionInfo  #:clojure.error{:source nil, :line 689, :column 14, :phase :compilation}
        cljs.analyzer/macroexpand-1 (analyzer.cljc:4114)
        cljs.analyzer/macroexpand-1 (analyzer.cljc:4110)
        cljs.analyzer/analyze-seq (analyzer.cljc:4147)
        cljs.analyzer/analyze-seq (analyzer.cljc:4127)
        cljs.analyzer/analyze-form (analyzer.cljc:4336)
        cljs.analyzer/analyze-form (analyzer.cljc:4333)
        cljs.analyzer/analyze* (analyzer.cljc:4389)
        cljs.analyzer/analyze* (analyzer.cljc:4381)
        cljs.analyzer/analyze (analyzer.cljc:4409)
        cljs.analyzer/analyze (analyzer.cljc:4392)
        cljs.analyzer/analyze (analyzer.cljc:4402)
        cljs.analyzer/analyze (analyzer.cljc:4392)
Caused by:
ExceptionInfo Cannot invoke "java.util.concurrent.Future.get()" because "fut" is null at line 689  {:file nil, :line 689, :column 14, :tag :cljs/analysis-error}
        cljs.analyzer/error (analyzer.cljc:787)
        cljs.analyzer/error (analyzer.cljc:783)
        cljs.analyzer/macroexpand-1 (analyzer.cljc:4114)
        cljs.analyzer/macroexpand-1 (analyzer.cljc:4110)
Caused by:
NullPointerException Cannot invoke "java.util.concurrent.Future.get()" because "fut" is null
        clojure.core/deref-future (core.clj:2317)
        clojure.core/deref (core.clj:2337)
        clojure.core/deref (core.clj:2323)
        cljs.analyzer/excluded? (analyzer.cljc:3953)
nil
That message isโ€ฆ not very helpful ๐Ÿ™‚ so I looked at shadow-cljs sources to find where this :shadow.cljs.devtools.errors/format-error appears and it appears in a function called user-friendly-error ๐Ÿ™ƒ Any pointers/hints? This error appears after lots of "Compile CLJS" and "Cache write" messages when I run npx shadow-cljs -v release app

I suspect this is from a dependency conflict? using a bad mix of clojurescript/shadow-cljs. make sure you have the clojurescript version appropriate for the shadow-cljs version you are using

"3.0.2" -> cljs 1.12.35

org.clojure/clojurescript {:mvn/version "1.12.35"} is what I have in my deps.edn.

and which shadow-cljs version?

No, hold on. I'm new to this package.json thing, and it says 2.28.2. I'll check again.

that doesn't matter if you have deps.edn

package.json only controls the npm package version, which isn't relevant here if deps.edn is actually used. as in :deps .. in shadow-cljs.edn

in which context does this error appear btw? during a build or during load or something?

@env/*compiler* this fails, but that binding should most definitely be set. otherwise nothing would work at all

so must be something rather drastic missing?

Ok, I got it to say 3.0.2 in all package*json files as well. But I still get the same error message. I get it when running npx shadow-cljs -v release app so just trying to build the app. And I'm sorry for newbie questions, but I managed to develop and maintain a large Clojure+ClojureScript app for 10 years now without even touching npm, so I don't understand its workings yet.

My configuration is most definitely broken at this point, I'm just looking for pointers on where to look.

Well, something must be working, because that message is after compiling hundreds of ClojureScript files. At least that's what -v says.

well its an internal error, so something rather funky must be going on ๐Ÿ˜›

could be a macro doing something nasty? need more context to give more useful comments

is it failing in a particular stage or file?

Well, the last message from -v are:

<- Compile CLJS: pbx/scan_ui.cljs (92 ms)
-> Cache write: pbx/scan_ui.cljs
<- Cache write: pbx/projects/cards.cljs (48 ms)
<- Cache write: pbx/projects/import_ui.cljs (42 ms)
<- Cache write: pbx/scan_ui.cljs (28 ms)
[2025-04-25 20:43:44.315 - WARNING] :shadow.cljs.devtools.errors/format-error
And it manages to go through hundreds of files before thatโ€ฆ I definitely do have some weird macrology, but nothing specifically in these files. These are actually rather boring.

its weird that this happens in macroexpand. a stage where this binding will most definitely be set

maybe we can do with a better stacktrace (as in not user friendlified ๐Ÿ˜‰

npx shadow-cljs clj-repl then (shadow/release! :app)

actually I have a guess. a macro throwing something containing a lazy-seq and that seq getting realized as part of trying to print it

might just be hiding the actual error in some way ๐Ÿ˜›

shadow.user=> (shadow/release! :app)
[:app] Compiling ...
Unexpected error (NullPointerException) compiling at (REPL:689:14).
Cannot invoke "java.util.concurrent.Future.get()" because "fut" is null
shadow.user=>
๐Ÿ˜

gotta hate stack traces just being ommitted ๐Ÿ˜›

try with :jvm-opts ["-XX:-OmitStackTraceInFastThrow"] in your deps.edn added

hmm maybe that is only allowed in aliases? can't remember ๐Ÿ˜›

clj -J-XX:-OmitStackTraceInFastThrow then (require '[shadow.cljs.devtools.api :as shadow]) and (shadow/release! :app)

it was -J right? can't remember exactly

Hmm. I can't seem to add :jvm-opts in deps.edn so that they are actually used. I'm adding an invalid option to check. Toplevel :jvm-opts seem to be ignored.

clj -J-XX:-OmitStackTraceInFastThrow seems to work, so just try that

And as for aliases, I'm not sure who or what decides which alias is used if I run 'npx shadow-cljs -v release app'

:deps {:aliases [:foo]} decides, true is just short for no aliases

Thanks! Trying it now.

but just go with the clj command, skips all the npm stuff.

Ok. I got it to use the option, but it doesn't produce any more output than before. Same with the clj command.

hmm might be the repl just dropping it

just look at it with *e maybe? as in type that in the repl after the error occurred?

just need the full trace somehow ๐Ÿ˜›

I swear I have regretted shortening stack traces every single damn time

or lets go really deep

in the REPL before running (shadow/release! :app) instead do (in-ns 'shadow.cljs.devtools.errors) and then

(defn user-friendly-error [e]
  (binding [*out* *err*]
    (try
      (println (error-format e))
      (catch Throwable ex
        (prn e)
        (log/warn-ex ex ::format-error)
        (println (.getMessage ex)))))
  :error)

then (in-ns 'shadow.user) (shadow/release :app) not release! so the fn is actually called

the actual error trying to be printed might be more useful anyway

Ok, now we're getting somewhere (*e).

#error {
 :cause "Cannot invoke \"java.util.concurrent.Future.get()\" because \"fut\" is null"
 :via
 [{:type clojure.lang.ExceptionInfo
   :message nil
   :data #:clojure.error{:source nil, :line 689, :column 14, :phase :compilation}
   :at [cljs.analyzer$macroexpand_1 invokeStatic "analyzer.cljc" 4114]}
  {:type clojure.lang.ExceptionInfo
   :message "Cannot invoke \"java.util.concurrent.Future.get()\" because \"fut\" is null at line 689 "
   :data {:file nil, :line 689, :column 14, :tag :cljs/analysis-error}
   :at [cljs.analyzer$error invokeStatic "analyzer.cljc" 787]}
  {:type java.lang.NullPointerException
   :message "Cannot invoke \"java.util.concurrent.Future.get()\" because \"fut\" is null"
   :at [clojure.core$deref_future invokeStatic "core.clj" 2317]}]
 :trace
 [[clojure.core$deref_future invokeStatic "core.clj" 2317]
  [clojure.core$deref invokeStatic "core.clj" 2337]
  [clojure.core$deref invoke "core.clj" 2323]
  [cljs.analyzer$excluded_QMARK_ invokeStatic "analyzer.cljc" 3953]
  [cljs.analyzer$excluded_QMARK_ invoke "analyzer.cljc" 3949]
  [cljs.analyzer$get_expander_STAR_ invokeStatic "analyzer.cljc" 3982]
  [cljs.analyzer$get_expander_STAR_ invoke "analyzer.cljc" 3980]
  [cljs.analyzer$get_expander invokeStatic "analyzer.cljc" 4009]
  [cljs.analyzer$get_expander invoke "analyzer.cljc" 4005]
  [cljs.analyzer$macroexpand_1_STAR_ invokeStatic "analyzer.cljc" 4058]
  [cljs.analyzer$macroexpand_1_STAR_ invoke "analyzer.cljc" 4048]
  [cljs.analyzer$macroexpand_1 invokeStatic "analyzer.cljc" 4114]
  [cljs.analyzer$macroexpand_1 invoke "analyzer.cljc" 4110]
  [cljs.analyzer$analyze_seq invokeStatic "analyzer.cljc" 4147]
  [cljs.analyzer$analyze_seq invoke "analyzer.cljc" 4127]
  [cljs.analyzer$analyze_form invokeStatic "analyzer.cljc" 4336]
  [cljs.analyzer$analyze_form invoke "analyzer.cljc" 4333]
  [cljs.analyzer$analyze_STAR_ invokeStatic "analyzer.cljc" 4389]
  [cljs.analyzer$analyze_STAR_ invoke "analyzer.cljc" 4381]
  [cljs.analyzer$analyze invokeStatic "analyzer.cljc" 4409]
  [cljs.analyzer$analyze invoke "analyzer.cljc" 4392]
  [cljs.analyzer$analyze invokeStatic "analyzer.cljc" 4402]
  [cljs.analyzer$analyze invoke "analyzer.cljc" 4392]
  [cljs.analyzer$analyze invokeStatic "analyzer.cljc" 4400]
  [cljs.analyzer$analyze invoke "analyzer.cljc" 4392]
  [clojure.lang.Var invoke "Var.java" 390]
  [daiquiri.compiler$infer_tag$fn__25218 invoke "compiler.clj" 53]
  [clojure.lang.AFn applyToHelper "AFn.java" 152]
  [clojure.lang.AFn applyTo "AFn.java" 144]
  [clojure.core$apply invokeStatic "core.clj" 667]
  [clojure.core$with_bindings_STAR_ invokeStatic "core.clj" 1990]
  [clojure.core$with_bindings_STAR_ doInvoke "core.clj" 1990]
  [clojure.lang.RestFn invoke "RestFn.java" 428]
  [daiquiri.compiler$infer_tag invokeStatic "compiler.clj" 51]
  [daiquiri.compiler$infer_tag invoke "compiler.clj" 44]
  [daiquiri.compiler$element_compile_strategy invokeStatic "compiler.clj" 310]
  [daiquiri.compiler$element_compile_strategy invoke "compiler.clj" 288]
  [clojure.lang.MultiFn invoke "MultiFn.java" 233]
  [daiquiri.compiler$compile_html invokeStatic "compiler.clj" 403]
  [daiquiri.compiler$compile_html invoke "compiler.clj" 397]
  [daiquiri.compiler$eval25282$fn__25284$fn__25289 invoke "compiler.clj" 183]
  [clojure.core$map$fn__5954 invoke "core.clj" 2772]
  [clojure.lang.LazySeq force "LazySeq.java" 50]
  [clojure.lang.LazySeq realize "LazySeq.java" 89]
it's longer, but I think this is the interesting part. Daiquiri is a library used by Rum to process hiccup.

more, gimme more ๐Ÿ˜› that is still realizing a lazy seq

I'm trying to figure out how to get you the full stacktrace. Seemingly simple thing, butโ€ฆ

I know there are some other rum users and never had a report like this

(prn *e) maybe?

Ok, got it.

maybe just try upgrading rum ๐Ÿ˜‰

Just to clarify: this is from an actively maintained app which compiles and runs just fine (leiningen, figwheel-main). It uses the latest libraries whenever it makes sense.

I do intend to move from Rum to UIx, but I can't do everything at once, and just this switch of build systems will likely take me a week or two to sort out. It's a big app (150kLOC, nearly 600 source files).

I have to go now, but I'll be back first thing tomorrow morning. If you come up with any pointers for me to try, I'd be very grateful, and if not, I'll just keep trying I guess. Many, many thanks for your help! ๐Ÿ™

I see no reason why rum wouldn't work

what I don't get is how shadow is nowhere in this trace, almost like this is starting a new thread or something which of course would not have that binding

from that trace I'd say you are not using shadow at all ๐Ÿ˜› not even a single line. shadow-cljs should be in there for sure

so my guess is that this is a secondary exception that happens while trying to print the first

otherwise I can't explain why the REPL is in this trace but shadow-cljs is not

try (try (shadow/release! :app) (catch Exception e (tap> e)) then look at in inspect via http://localhost:9630/inspect-latest

that'll need shadow-cljs running though, so ((requiring-resolve 'shadow.cljs.devtools.server/start!)) first

I'm having trouble getting something. I did this, but it just hangs forever and there is nothing in the inspector:

clj -J-XX:-OmitStackTraceInFastThrow                                                                                                                                                                                        ๎‚ฒ โœ” ๎‚ณ 1m 46s
Clojure 1.12.0
user=> (require '[shadow.cljs.devtools.api :as shadow])
nil
user=> ((requiring-resolve 'shadow.cljs.devtools.server/start!))
Apr 26, 2025 9:27:18 AM io.undertow.Undertow start
INFO: starting server: Undertow - 2.3.10.Final
Apr 26, 2025 9:27:18 AM org.xnio.Xnio <clinit>
INFO: XNIO version 3.8.8.Final
Apr 26, 2025 9:27:18 AM org.xnio.nio.NioXnio <clinit>
INFO: XNIO NIO Implementation Version 3.8.8.Final
Apr 26, 2025 9:27:18 AM org.jboss.threads.Version <clinit>
INFO: JBoss Threads version 3.5.0.Final
Apr 26, 2025 9:27:19 AM io.undertow.Undertow start
INFO: starting server: Undertow - 2.3.10.Final
shadow-cljs - HTTP server available at 
shadow-cljs - server version: 3.0.2 running at 
shadow-cljs - nREPL server started on port 7888
:shadow.cljs.devtools.server/started
user=> (try (shadow/release! :app) (catch Exception e (tap> e))
But the exception is happening, if I do the same thing without the catch:
clj -J-XX:-OmitStackTraceInFastThrow                                                                                                                                                                                    ๎‚ฒ โœ˜ INT ๎‚ณ 1m 42s
Clojure 1.12.0
user=> (require '[shadow.cljs.devtools.api :as shadow])
nil
user=> ((requiring-resolve 'shadow.cljs.devtools.server/start!))
Apr 26, 2025 9:28:57 AM io.undertow.Undertow start
INFO: starting server: Undertow - 2.3.10.Final
Apr 26, 2025 9:28:57 AM org.xnio.Xnio <clinit>
INFO: XNIO version 3.8.8.Final
Apr 26, 2025 9:28:57 AM org.xnio.nio.NioXnio <clinit>
INFO: XNIO NIO Implementation Version 3.8.8.Final
Apr 26, 2025 9:28:57 AM org.jboss.threads.Version <clinit>
INFO: JBoss Threads version 3.5.0.Final
Apr 26, 2025 9:28:58 AM io.undertow.Undertow start
INFO: starting server: Undertow - 2.3.10.Final
shadow-cljs - HTTP server available at 
shadow-cljs - server version: 3.0.2 running at 
shadow-cljs - nREPL server started on port 7888
:shadow.cljs.devtools.server/started
user=> (shadow/release! :app)
[:app] Compiling ...
Unexpected error (NullPointerException) compiling at (REPL:689:14).
Cannot invoke "java.util.concurrent.Future.get()" because "fut" is null
user=>

Hmm. Is the REPL in the dev tools supposed to work? It seems completely broken for me.

define broken? for some reason ctrl+enter doesn't work on my mac, as in the enter above the shift key

ctrl+enter on they keypad works just fine

but yeah the whole thing is more of a work in progress. didn't quite finish it the last time I worked on it

another thing you could try is go closer to the actual failing point

so open the REPL as usual

(require 'daiquiri.compiler)
(defonce orig daiquiri.compiler/infer-tag)
(alter-var-root #'daiquiri.compiler/infer-tag
  (fn [_]
    (fn [env form]
      (try
        (orig env form)
        (catch Exception e
          (tap> [:infer-ex form env e])
          (throw (ex-info "failed with form" {:form form} e))
          )))))

before or after ((requiring-resolve 'shadow.cljs.devtools.server/start!))

then (require '[shadow.cljs.devtools.api :as shadow]) and (shadow/release! :app)

although I still kinda expect this to fail at the "wrong" stage. I still can't explain how this would ever be nil or why shadow isn't anywere in the stacktrace

do you trigger some custom compilation via macros or something?

oh and please try :compiler-options {:parallel-build false}. it is true by default, but maybe it doesn't like being in multiple threads

Well, I wrote "broken", because evaluating (+ 2 2) in that REPL produces "Cannot invoke \"clojure.lang.Namespace.getMapping(clojure.lang.Symbol)\" because the return value of \"clojure.lang.Compiler.currentNS()\" is null". All I can evaluate are single values.

there is something really weird going on in your project ๐Ÿ˜›

No, it's me. I break software. I'm not even kidding. Things generally break around me, so much that my family sometimes tells me to step away 10m or so if an ATM machine fails.

But, we're getting somewhere! I do have something in the inspector!

do you have a user.clj in your project? do you use tools.namespace and anything that generally messes with namespaces/classpath or any of that stuff

No, nothing like that. But here is what the inspector shows.

There is nothing particularly weird about that place in the code, apart from the fact that that function is BIG.

Also, it is quite old, hasn't been changed lately, and compiles just fine with figwheel-main.

did you try :compiler-options {:parallel-build false}? I don't believe figwheel has that enabled by default

if you click the #error line in the inspect

I assume that shows the same stack as yesterday? without any shadow-cljs?

My app normally builds ClojureScript 20 times (once for each language) and 19 of those are always parallel and one always isn't. So both options were exercised. Right now I have :compiler-options {:parallel-build false} in shadow-cljs.edn.

Yes. It's the same thing.

:parallel-build is only affecting the namespaces being compiled for a singular build, it never affects multiple builds in any way, as in they won't be parallel or so

I understand, my point was that both versions were tried every time I built a release version.

e.g. 19 builds were run with parallel-build set to true and one with parallel-built set to false.

I'd like to focus on getting one to compile, ignore the other 19 for now

Gladly. Should I try breaking up the function into smaller pieces?

no, there is absolutely no reason for this to not compile if figwheel is fine with it

and yetโ€ฆ hold on, I think I found something by trying to compile the function with nobody. But I can't believe it. The function does destructuring and one of the parameters is destructured as:

{:project-id :project/id
  project-build-quantities :project/build-quantities
  sub-assembly-part-id :project/part-id
  :as project}
See the problem?

Removing the first colon makes it compile. But why did it ever work? ๐Ÿ‘€

and more mysterious is how does it and up with an exception trace like above?

do you ever use future or some other way of launching threads in macros? I still do not get how shadow-cljs isn't anywhere in the traces

Ok, so this took a while but I found it. That colon appeared thereโ€ฆ Tue Jan 5 12:39:14 2021 +0100.

No, my macros do not do any work with future or launch threads. Howeverโ€ฆ the ones for translations do use the nippy library.

I honestly have no idea how that code compiled, much less worked, for the last 4.5 years.

so the error is entirely gone now?

Yes. I now get through to "[:app] Build completed. (609 files, 52 compiled, 17 warnings, 14.69s)" and three bazillion warnings.

you mean 17?

31 | (defn whatever [{:kw-here :and-here}]
-------^------------------------------------------------------------------------
Syntax error macroexpanding cljs.core/defn.
Call to cljs.core/defn did not conform to spec.
-- Spec failed --------------------

  (... [{:kw-here :and-here}] ...)
        ^^^^^^^^^^^^^^^^^^^^

has extra input

or

should satisfy

  vector?

ah wait daiquiri. let me try that

It looks like three bazillion, because they are multi-line, have colors, and are scary ๐Ÿ™‚ But yes.

The error that you've shown above is what I saw after I disabled the entire body of the function and replaced it with a single (do).

Re-enabling the (big) body caused the bizarre behavior we were investigating.

๐Ÿคท 1

So, to summarize: my immediate problem is solved. I don't know if the bizarre behavior is something worth debugging.

I'd be super curious about a reproducible example, but without that probably not