shadow-cljs 2025-05-25

Hi, I'm running into a strange error I can't seem to resolve:

(NOBRIDGE) WARN  The shadow-cljs Websocket was disconnected.
 (NOBRIDGE) ERROR  shadow-cljs - remote-error {"isTrusted": false, "message": null}
Error: Unable to resolve module ./app/shadow.cljs.devtools.client.shared from /Users/alexispurslane/Development/yact/mobile/.: 

None of these files exist:
  * app/shadow.cljs.devtools.client.shared(.native.ts|.ts|.native.tsx|.tsx|.native.mjs|.mjs|.native.js|.js|.native.jsx|.jsx|.native.json|.json|.native.cjs|.cjs|.native.scss|.scss|.native.sass|.sass|.native.css|.css)
  * app/shadow.cljs.devtools.client.shared
    at ModuleResolver.resolveDependency (/Users/alexispurslane/Development/yact/mobile/node_modules/metro/src/node-haste/DependencyGraph/ModuleResolution.js:114:15)
    at DependencyGraph.resolveDependency (/Users/alexispurslane/Development/yact/mobile/node_modules/metro/src/node-haste/DependencyGraph.js:248:43)
    at /Users/alexispurslane/Development/yact/mobile/node_modules/metro/src/lib/transformHelpers.js:165:21
    at Server._resolveRelativePath (/Users/alexispurslane/Development/yact/mobile/node_modules/metro/src/Server.js:1104:12)
    at Server._explodedSourceMapForBundleOptions (/Users/alexispurslane/Development/yact/mobile/node_modules/metro/src/Server.js:1052:35)
    at async Promise.all (index 1)
    at Server._symbolicate (/Users/alexispurslane/Development/yact/mobile/node_modules/metro/src/Server.js:1009:26)
    at Server._processRequest (/Users/alexispurslane/Development/yact/mobile/node_modules/metro/src/Server.js:419:7)
 (NOBRIDGE) LOG  shadow-cljs #4 ready!
I've tried a few different things, including deleting .shadow-cljs/ and updating shadow-cljs, then updating its version in project.clj, but to no avail

well. does the file exist? /Users/alexispurslane/Development/yact/mobile/app/shadow.cljs.devtools.client.shared.js?

dunno why you'd get the disconnect message but then LOG shadow-cljs #4 ready!? this message most definitely wouldn't exist if the shared.js file actually failed to load?

what is the build config used?

@thheller I figured out the problem

Apparently on Android you need to use adb reverse to port forward, essentially, any ports you want your Android device to be able to access from your development workstation or vice versa

which is odd since its browser can access those ports just fine, but whatever 🤷‍♀️

good to know

sorry for the delayed reply, I've never used Slack before and I didn't see the thread until just now xD anyway, no, that file does not exist, and neither does the app/ directory. I just don't know why it thinks it needs that file – where that dep is coming from

> this message most definitely wouldn't exist if the shared.js file actually failed to load? right, but shadow-cljs doesn't seem to be working properly, so I'm more inclined to say something didn't load that was required, and it's just incorrectly printing something that indicates it did

> what is the build config used? Here's my shadow-cljs.edn:

{:source-paths ["src" "../src/cljc" "../src/commonjs"]

 :dependencies [
                [reagent "1.2.0"]
                [functionalbytes/sibiro "0.1.4"]
                [re-frame "1.2.0"]
                [dk.cst/xml-hiccup "0.1.5"]
                [com.cognitect/transit-cljs "0.8.280"]
                ]

 :builds
 {
  :mobile {:target     :react-native
           :output-dir "out"
           :asset-path "out"
           :init-fn    yact.mobile/init
;;           :devtools   {:after-load yact.mobile/init}}}

   :devtools {:autoload true
              :preloads [shadow.expo.keep-awake]}}}
:js-options {
             ;;:resolve "node_modules"

             ;;:resolve {"react" "node_modules/react"
             ;;          "react-dom" "node_modules/react-dom"}
             ;;:js-package-dirs ["react-native/node_modules"]
             }
 :compiler-options {
                    :output-feature-set :es5
                    :infer-externs :auto
                    }
 }

and you have a running watch for the :mobile build? npx shadow-cljs watch mobile?

Sometimes, but not consistently, I also get these errors from the npx expo process:

(NOBRIDGE) WARN  The shadow-cljs Websocket was disconnected.
 (NOBRIDGE) ERROR  shadow-cljs - remote-error {"isTrusted": false, "message": "Failed to connect to /192.168.254.14:9630"}
 (NOBRIDGE) WARN  The shadow-cljs Websocket was disconnected.
 (NOBRIDGE) ERROR  shadow-cljs - remote-error {"isTrusted": false, "message": "Failed to connect to /192.168.254.14:9630"}
 (NOBRIDGE) WARN  shadow-cljs: giving up trying to connect to  

these always seem to come around 10-30mins after the first errors

what is unclear is why it is trying to refernence files from the app dir when shadow-cljs is generating the files into the out dir

💯 1

and normally it shouldn't refer to those individual files at all since everything is loaded from the out/index.js directly

👀 1

interesting

I've tried deleting .shadow-cljs to wipe any leftover cache or configuration or whatever, but that didn't seem to help

let me try doing a project-wide grep to see if I've hardcoded this somewhere

under which circumstances does the error happen? maybe triggered over the REPL? or hot-reload? or does it happen directly on load?

As soon as I run npx shadow-cljs watch :mobile and then npx expo run:android -d

it can't even begin to hot reload at all, that's actually why I'm trying to track this issue down in the first place

since otherwise, every change I make takes 1-2 mins to see on the device

the only file anything should be referencing is out/index.js the other files basically only exists for REPL purposes

I'm totally new to Clojure, let alone React Native or anything, and this is someone else's project I've been brought on to help with, so there may be stray config files or something I'm missing

> the only file anything should be referencing is out/index.js the other files basically only exists for REPL purposes hmmmm

its been many years since I looked at anything react-native/expo, so not even sure how configs look on that side these days

either the expo config should directly refer to out/index.js

or some other JS file

I'll try to find it

one other possible factor is that I did try to connect to shadow-cljs through cider – so, presumably the repl, if I understand the byzantine architecture of all this correctly – once or twice, but it errored out both times so I gave up and decided to just use figwheel for close-enough editor support and run shadow-cljs in the background

so maybe there's some kind of left over half-built JS or some conflict going on

{
  "expo": {
    "name": "yact",
    "slug": "yact",
    "version": "1.0.0",

    "icon": "./assets/icon.png",
    "userInterfaceStyle": "light",
    "splash": {
      "image": "./assets/splash-icon.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "newArchEnabled": true,
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "yact"
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      },
      "package": "com.yact"
    },
    "web": {
      "favicon": "./assets/favicon.png"
    },
    "plugins": [
      [
        "expo-build-properties",
        {
          "android": {
            "compileSdkVersion": 35,
            "targetSdkVersion": 34,
            "buildToolsVersion": "35.0.0",
            "kotlinVersion": "1.9.25"
          }
        }
      ],
      [
      "react-native-ble-plx",
       {
          "isBackgroundEnabled": true,
          "modes": ["peripheral", "central"],
          "bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices"
        }
    ]]
  }
}

this is the expo config

no mention to app/ or anything that I can see

maybe package.json?

or just <project>/index.js

it just says "main": "index.js"

ok, so <project>/index.js it is

and that file does exist

and likely has a import "./out/index.js" or require

if its referencing anything else in the out dir that should be rmeoved

Yeah, it's importing...

man, I hate codebases I didn't write

it's importing Blah from ./out/index.js

anyway yeah, and the out directory has all the shadow-cljs related files that the program is looking for in app for some reason

seems fine to me

yeah, so why is it trying to pull from app/...

what if I just change the output dir in shadow-cljs.edn

maybe something in the code trying to do something clever? kinda hard to debug this without seeing anything

yeah that's 100% fair

let me see if just changing where it sends all the output makes the error go away at least; I'd like to understand what's /causing/ this, but we can't have everything in life

you can check the out/index.js, there should be a line starting with SHADOW_ENV.evalLoad("shadow.cljs.devtools.client.shared.js" ...

OKay, wait this is progress

okay, so replacing out/index.js with app/index.js, and setting the output and assets directories in shadow-cljs.edn to app/ has eliminated the error about not being able to find a module

but the other errors, and the underlying problem I'm trying to fix, remain

so the big error was a red herring

not a good strategy IMHO. best to identify first what is trying to load that file, since that is the thing that shouldn't be happening in the first place. just accepting that it happens is no good

right but if accepting that it happens and eliminating /that/ error still leads to the main thing I was trying to fix, that does tell us something useful at least?

but yeah I should probably figure out why it's happening. I usually would but this whole project is a clusterfck and I don't want to lose any more sanity points lol

what error is that?

right, so having everything shadow-cljs generates end up where it expects (in the app/ directory) eliminates the big error where it can't find the shared devtools module

now all I get is this:

(NOBRIDGE) WARN  The shadow-cljs Websocket was disconnected.
 (NOBRIDGE) ERROR  shadow-cljs - remote-error {"isTrusted": false, "message": null}
 (NOBRIDGE) LOG  shadow-cljs #8 ready!

shadow.cljs.devtools.client.* are all the namespaces related to hot-reload/REPL. so if for some reason they get loaded twice the connection will be unreliable at best I'd say

there's only one such line in app/index.js

thats correct. just wanted to check its actually there.

👍 1

yup I was confirming

that I found it

it's definitely there, and it's not duplicated, so hopefully that narrows it down a bit. I'm gonna take a look at SHADOW_ENV and see where it's getting its value from and what's in it, maybe that'll have a hint as to where it's getting app from

ah ok no luck there, that happens prior it looks like

if it gets to LOG shadow-cljs #8 ready! then everything seems fine. that has loaded all the code and the websocket connected

you can run npx shadow-cljs clj-repl and then (shadow/get-server-addrs)

right, but it says websocket disconnected and remote error before it says ready, which is odd. that seems like a race condition almost

that'll give you a list of addrs the client will attempt to connect to. if there is more than one that may explain the disconnect msg

ok, lemme take a look

the connect logic will try them all in sequence until it finds one it can connect to

nope, only one address, my computer's IP address

hmm ok then I don't know why it would fail and then succeed

oh hey that's interesting. killing the watch and starting the repl gave me a few more errors

(NOBRIDGE) WARN  The shadow-cljs Websocket was disconnected.
 (NOBRIDGE) ERROR  shadow-cljs - remote-error {"isTrusted": false, "message": null}
 (NOBRIDGE) WARN  The shadow-cljs Websocket was disconnected.
 (NOBRIDGE) ERROR  shadow-cljs - remote-error {"isTrusted": false, "message": "Failed to connect to /192.168.254.14:9630"}
 (NOBRIDGE) WARN  The shadow-cljs Websocket was disconnected.
 (NOBRIDGE) ERROR  shadow-cljs - remote-error {"isTrusted": false, "message": "Failed to connect to /192.168.254.14:9630"}
 (NOBRIDGE) WARN  The shadow-cljs Websocket was disconnected.
 (NOBRIDGE) ERROR  shadow-cljs - remote-error {"isTrusted": false, "message": "Failed to connect to /192.168.254.14:9630"}
 (NOBRIDGE) WARN  shadow-cljs: giving up trying to connect to  

is there maybe another instance of shadow-cljs still running?

let me see

just 2 instances fighting with each other? I added protection for this a while ago, but dunno which version you are running

if this is a case of duplicate shadow-cljs servers, with one connecting and one not, that wouldn't explain why hot reloading is breaking though would it?

> just 2 instances fighting with each other? I added protection for this a while ago, but dunno which version you are running

shadow-cljs - config: /Users/alexispurslane/Development/yact/mobile/shadow-cljs.edn
=== npm package
cli:                  2.28.23
config-version:       2.28.23

=== shadow-cljs version (via npm)
shadow-cljs version:  2.28.23

=== Java
openjdk version "23.0.2" 2025-01-21
OpenJDK Runtime Environment Homebrew (build 23.0.2)
OpenJDK 64-Bit Server VM Homebrew (build 23.0.2, mixed mode, sharing)

=== Source Paths
/Users/alexispurslane/Development/yact/mobile/src
/Users/alexispurslane/Development/yact/src/cljc
/Users/alexispurslane/Development/yact/src/commonjs

it all depends 😛

😁 1

it's old because that's what the project used

I could try updating it, but I'm afraid to do that lol

that is fairly recent, definitely has that check

okay... it looks like there may be another shadow-cljs instance. but then that's odd, since the ones I was starting werent' complaining or anything

zombies are no good, so kill that one just to be sure

👍 1

it's a java process with a massive classpath list?

aw fuck no that was my figwheel instance lol

it was just loading shadow-cljs related stuff...?

well let's see if that helps

unlikely, but dunno what figwheel is doing in this setup 😛

definitely better to keep it away while debugging this

oh man you don't wanna know. I disavow any and all things related to this fucked dev environment

I was /not/ consulted

basically we have a react (reagent/re-frame) app and a react native app conjoined at the hip, sharing not just core business logic but also a lot of UI code via wrappers that are compiled from different modules based on the architecture compiled for

so I need to keep figwheel and shadow-cljs going at once so that when I make a change for web, I know it doesn't break mobile, or vice versa

we can talk about that later. for now just don't run it 😛

😂 1

okay, let me rerun everything now that figwheel is gone and see what happens

just to confirm, I'm supposed to be running the shadow-cljs command, and then the expo run android command one after the other, right?

I'd wait till shadow-cljs finishes the build, then run expo

yeah that's what I'm doing

👍 1

okay, no, figwheel being gone doesn't change anything

so change :output-dir "foo" and index.js to use ./foo/index.js

i.e. a completely empty new dir

still getting "android bundled ... some initial app debug logs... [big app hang] ... [the errors]"

ok will do

app/ was completely new and empty tbf

but it was somehow expected previously, so yeah

well, yeah we still need to verify that nothing references the app dir anymore

well, I assume stuff does, since I eliminated the error temporarily by just putting things in app/ where it expected them and... oh so this is interesting

okay, so I switched the output and assets dirs, then started shadow-cljs, waited for it to finish building, then started expo

and I got this message:

(NOBRIDGE) ERROR  Stale Output! Your loaded JS was not produced by the running shadow-cljs instance. Is the watch for this build running?

could be that expo is caching something somewhere? i.e. not using that latest output?

let me see if there are any sneaky cache directories

:asset-path is btw not an option for :target :react-native so you can just remove that. it does nothing.

ah okay

alright, I could just delete .expo again

deleting .expo does not resolve the cache message, let me look somewhere else

but! interestingly, there doesn't seem to be any error about not being able to find a module anymore

so there's that

it's because when we switched to foo/ I forgot to change that in <projectdir>/index.js. Okay, back on track

nope, okay, it's still looking for things in ./app, and we still get the strange "websocket disconnected" "remote-error" /then/ "shadow-cljs #4 is ready!" stuff

so, just to verify. there is only the shadow-cljs watch running. you are not connected to the REPL via your editor or anything else?

so then a grep or whatever to find app/ should show up somewhere?

I mean it must be coming from somewhere

just grep all .js files

maybe search for shadow.cljs.devtools.client.shared but that should have a lot of hits, so requires more filtering

what else is in the <project>/index.js?

okay so yeah when I ran the grep originally I didn't realize it was obeying .gitignore. there are a lot of hits looking for localhost:8081/app/<various source files, including shadow.cljs.devtools.client.shared> in out/index.js

but if it's coming from somewhere, at least so far it's way too lost in the results. I'll keep trying to filter

likely only the sourceURL for source map purposes? otherwise dunno where localhost would be coming from

e.g. # sourceURL\x3d\n\n

I didn't spot the sourceURL there at first, so I was hoping for it to be moreuseful

honestly I have no clue how any of this works even looking at https://github.com/thheller/reagent-expo

makes fuck all sense that this is referencing a default export that doesn't exist at all

maybe just try running npx shadow-cljs compile mobile once (with no watch running)

alright, let's see

just to see if it still tries to load that file at all

it shouldn't attempt to connect to anything

makes sense given what the reagent docs say

let's see.

my computer isn't slow, neither is my phone, but this whole round trip is lke tryihng to download a 4K video over dialup

okay, so yeah, when properly compiled instead of watched, it doesn't try to reference anything external

then my only guess is that the expo stuff for some reason makes use of the sourceURL and gets the app/ from there, since that is hardcoded I guess https://github.com/thheller/shadow-cljs/blob/40d9ae9d27bc61f83e3afc76771fc17f9aa68be9/src/main/shadow/build/targets/react_native.clj#L190

just use :output-dir "app" I guess

full circle lol

FWIW the compile will also still reference that, but it won't have any reference to the shadow.cljs.devtools.client.... stuff

👍 1

okay, so what about the shadow cljs server being both ready and failing to connect? I don't have a firewall on or anything

🤷 1

and that IP address that cljs is looking to connect to is accessible on the local wifi network

alright then lol

this is the code doing the connect logic

you can take this file and put it into your classpath into the proper location

so src/main/shadow/cljs/devtools/client/react_native.cljs or whatever source path you use

you can then add more logging to maybe figure this out

I give up 😛

thanks for your help! I'll do that and see if I'm able to figure anything out

it shouldn't even get to that line if there really is only one address

👀 1

if you can repro this I can take a look, but going over this blind makes this rather hard

yeah, I'll check back if I figure out a way to reproduce this

but even replicating this escheresque project structure would be a multi-day project /hj

yeah no clue why anyone would use figwheel and shadow-cljs at the same time

I'm quite inexperienced with this stuff so I'd really appreciate some pointers