shadow-cljs

2025-04-25T07:16:21.989749Z

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
2025-04-25T07:26:56.856439Z

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

thheller 2025-04-25T08:14:34.227289Z

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

thheller 2025-04-25T08:15:04.625549Z

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

thheller 2025-04-25T08:15:18.999769Z

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

cjohansen 2025-04-25T08:47:45.194619Z

Cool!

2025-04-25T11:39:04.642829Z

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)?

thheller 2025-04-25T11:40:58.335049Z

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

thheller 2025-04-25T11:41:29.593119Z

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

๐Ÿ˜ฎ 1
thheller 2025-04-25T11:42:02.170279Z

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

thheller 2025-04-25T11:42:13.796079Z

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

cjohansen 2025-04-25T11:43:21.736719Z

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.

thheller 2025-04-25T11:44:09.284439Z

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

cjohansen 2025-04-25T11:44:59.049209Z

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

2025-04-25T11:56:29.376529Z

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
cormacc 2025-04-25T12:04:24.524629Z

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.

2025-04-25T12:13:39.776319Z

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.

2025-04-25T12:26:59.995559Z

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.

2025-04-25T13:26:34.208179Z

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}                                

thheller 2025-04-25T13:29:44.018039Z

odd, looks fine to me

thheller 2025-04-25T13:30:50.537579Z

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

2025-04-25T13:34:54.167049Z

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

2025-04-25T13:35:56.532269Z

{: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}}}}}

thheller 2025-04-25T13:36:27.216949Z

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

thheller 2025-04-25T13:37:18.352909Z

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

2025-04-25T13:37:29.350099Z

Works for me

cormacc 2025-04-25T13:38:36.647119Z

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

2025-04-25T13:48:02.844559Z

Thanks @cormacc I'll take a look

thheller 2025-04-25T14:11:30.757529Z

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

thheller 2025-04-25T14:11:31.997429Z

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

thheller 2025-04-25T14:11:49.619439Z

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

thheller 2025-04-25T14:12:26.027459Z

will fix

2025-04-25T14:13:26.194339Z

Thank you!

thheller 2025-04-26T09:54:56.324059Z

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

2025-04-26T14:43:15.129089Z

It works now! Thanks again

Shako Farhad 2025-04-25T10:06:37.950839Z

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
Shako Farhad 2025-04-25T10:09:38.938439Z

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: 

thheller 2025-04-25T10:11:39.538519Z

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
Shako Farhad 2025-04-25T10:12:01.409289Z

Alright. Sounds good. Thanks! ๐Ÿ™‚

thheller 2025-04-25T10:13:17.491369Z

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 ๐Ÿ˜›

thheller 2025-04-25T10:14:09.606629Z

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

๐Ÿ‘ 1
Shako Farhad 2025-04-25T10:15:33.986209Z

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 ๐Ÿ˜„

cormacc 2025-04-25T14:36:20.776809Z

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

woody992 2025-04-25T11:13:08.749479Z

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?

thheller 2025-04-25T11:26:11.694359Z

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

thheller 2025-04-25T11:26:24.129229Z

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

thheller 2025-04-25T11:28:26.707719Z

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

thheller 2025-04-25T11:28:46.125379Z

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

thheller 2025-04-25T11:30:16.973059Z

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

thheller 2025-04-25T11:30:26.534839Z

ok actually fails as expected

thheller 2025-04-25T11:31:25.257099Z

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

thheller 2025-04-25T11:32:11.052959Z

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

thheller 2025-04-25T11:32:26.672999Z

as in {"zlib" false}

woody992 2025-04-25T11:40:37.447599Z

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

thheller 2025-04-25T11:47:59.607349Z

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

thheller 2025-04-25T11:48:08.134339Z

most popular stuff has been updated by now

woody992 2025-04-25T11:51:18.764519Z

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

woody992 2025-04-25T11:52:59.530429Z

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).

woody992 2025-04-25T11:53:19.308479Z

Thank you again for helping us out

๐Ÿ‘ 1
jrychter 2025-04-25T11:32:46.345709Z

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

thheller 2025-04-25T11:34:07.633629Z

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

thheller 2025-04-25T11:34:51.966819Z

check which via https://clojars.org/thheller/shadow-cljs

thheller 2025-04-25T11:35:07.237879Z

"3.0.2" -> cljs 1.12.35

jrychter 2025-04-25T11:35:26.498569Z

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

thheller 2025-04-25T11:35:35.625119Z

and which shadow-cljs version?

jrychter 2025-04-25T11:35:45.832429Z

3.0.2.

jrychter 2025-04-25T11:36:25.869369Z

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

thheller 2025-04-25T11:36:37.745689Z

that doesn't matter if you have deps.edn

thheller 2025-04-25T11:37:18.390879Z

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

thheller 2025-04-25T11:38:25.801699Z

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

thheller 2025-04-25T11:39:09.863419Z

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

thheller 2025-04-25T11:39:24.833509Z

so must be something rather drastic missing?

jrychter 2025-04-25T11:40:02.650849Z

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.

jrychter 2025-04-25T11:40:47.044099Z

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

jrychter 2025-04-25T11:43:50.601859Z

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

thheller 2025-04-25T11:45:15.778849Z

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

thheller 2025-04-25T11:46:08.104989Z

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

thheller 2025-04-25T11:46:20.021849Z

is it failing in a particular stage or file?

jrychter 2025-04-25T11:48:27.010279Z

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.

thheller 2025-04-25T11:50:45.080599Z

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

thheller 2025-04-25T11:51:52.843499Z

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

thheller 2025-04-25T11:52:05.377569Z

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

thheller 2025-04-25T11:53:32.504749Z

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

thheller 2025-04-25T11:54:06.436899Z

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

jrychter 2025-04-25T11:54:39.145349Z

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=>
๐Ÿ˜

thheller 2025-04-25T11:55:12.145099Z

gotta hate stack traces just being ommitted ๐Ÿ˜›

thheller 2025-04-25T11:55:58.600179Z

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

thheller 2025-04-25T11:56:47.179269Z

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

thheller 2025-04-25T11:57:29.786539Z

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

thheller 2025-04-25T11:57:56.282799Z

it was -J right? can't remember exactly

jrychter 2025-04-25T12:00:21.410889Z

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.

thheller 2025-04-25T12:00:52.202569Z

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

jrychter 2025-04-25T12:00:53.130909Z

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'

thheller 2025-04-25T12:01:10.882879Z

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

jrychter 2025-04-25T12:01:38.720099Z

Thanks! Trying it now.

thheller 2025-04-25T12:02:00.383389Z

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

jrychter 2025-04-25T12:04:50.944579Z

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

thheller 2025-04-25T12:05:35.197919Z

hmm might be the repl just dropping it

thheller 2025-04-25T12:06:12.168059Z

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

thheller 2025-04-25T12:06:30.388339Z

just need the full trace somehow ๐Ÿ˜›

thheller 2025-04-25T12:07:39.012429Z

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

thheller 2025-04-25T12:09:07.355989Z

or lets go really deep

thheller 2025-04-25T12:09:47.184139Z

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)

thheller 2025-04-25T12:10:11.857039Z

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

thheller 2025-04-25T12:10:33.043579Z

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

jrychter 2025-04-25T12:11:35.124209Z

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

jrychter 2025-04-25T12:13:06.456299Z

#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.

thheller 2025-04-25T12:13:42.249529Z

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

jrychter 2025-04-25T12:15:02.904699Z

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

thheller 2025-04-25T12:15:11.708189Z

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

thheller 2025-04-25T12:15:38.465679Z

(prn *e) maybe?

jrychter 2025-04-25T12:16:49.881719Z

Ok, got it.

thheller 2025-04-25T12:16:57.080919Z

maybe just try upgrading rum ๐Ÿ˜‰

jrychter 2025-04-25T12:18:07.498839Z

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.

jrychter 2025-04-25T12:19:29.220769Z

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).

jrychter 2025-04-25T12:20:18.947009Z

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! ๐Ÿ™

thheller 2025-04-25T12:20:20.314509Z

I see no reason why rum wouldn't work

thheller 2025-04-25T12:21:27.646079Z

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

thheller 2025-04-25T12:23:56.354629Z

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

thheller 2025-04-25T12:39:15.583709Z

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

thheller 2025-04-25T12:39:32.311109Z

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

thheller 2025-04-25T12:41:37.539239Z

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

thheller 2025-04-25T12:42:29.738269Z

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

jrychter 2025-04-26T00:29:49.788469Z

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=>

jrychter 2025-04-26T00:34:24.802999Z

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

thheller 2025-04-26T08:41:57.237769Z

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

thheller 2025-04-26T08:42:05.971679Z

ctrl+enter on they keypad works just fine

thheller 2025-04-26T08:42:26.629399Z

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

thheller 2025-04-26T08:47:41.734449Z

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

thheller 2025-04-26T08:47:47.786059Z

so open the REPL as usual

thheller 2025-04-26T08:47:49.817299Z

(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))
          )))))

thheller 2025-04-26T08:48:14.421249Z

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

thheller 2025-04-26T08:48:29.165219Z

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

thheller 2025-04-26T08:49:26.460799Z

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

thheller 2025-04-26T08:49:55.426809Z

do you trigger some custom compilation via macros or something?

thheller 2025-04-26T08:50:46.950809Z

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

jrychter 2025-04-26T09:55:23.014269Z

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.

thheller 2025-04-26T09:57:11.423019Z

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

jrychter 2025-04-26T10:02:28.169929Z

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.

jrychter 2025-04-26T10:02:42.833339Z

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

thheller 2025-04-26T10:06:41.984019Z

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

jrychter 2025-04-26T10:07:08.210679Z

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

jrychter 2025-04-26T10:07:37.294829Z

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

jrychter 2025-04-26T10:09:11.765279Z

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

thheller 2025-04-26T10:10:22.192569Z

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

thheller 2025-04-26T10:11:28.280599Z

if you click the #error line in the inspect

thheller 2025-04-26T10:11:42.017289Z

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

jrychter 2025-04-26T10:11:42.058009Z

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.

jrychter 2025-04-26T10:12:24.548509Z

Yes. It's the same thing.

thheller 2025-04-26T10:12:26.408709Z

: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

jrychter 2025-04-26T10:13:05.524949Z

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

jrychter 2025-04-26T10:13:37.742169Z

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

thheller 2025-04-26T10:14:05.470049Z

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

jrychter 2025-04-26T10:14:24.371209Z

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

thheller 2025-04-26T10:15:03.456739Z

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

jrychter 2025-04-26T10:23:40.759739Z

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?

jrychter 2025-04-26T10:24:08.988129Z

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

thheller 2025-04-26T10:24:44.369909Z

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

thheller 2025-04-26T10:25:57.464319Z

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

jrychter 2025-04-26T10:32:55.223639Z

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

jrychter 2025-04-26T10:35:55.340429Z

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

jrychter 2025-04-26T10:36:15.361139Z

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

thheller 2025-04-26T10:37:15.779529Z

so the error is entirely gone now?

jrychter 2025-04-26T10:38:10.303779Z

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

thheller 2025-04-26T10:38:57.218289Z

you mean 17?

thheller 2025-04-26T10:39:00.552279Z

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?

thheller 2025-04-26T10:39:25.925319Z

ah wait daiquiri. let me try that

jrychter 2025-04-26T10:39:43.030359Z

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

jrychter 2025-04-26T10:40:13.550389Z

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).

jrychter 2025-04-26T10:40:34.810639Z

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

๐Ÿคท 1
jrychter 2025-04-26T10:47:01.771749Z

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

thheller 2025-04-26T10:48:48.775659Z

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