Fork me on GitHub
#shadow-cljs
<
2021-08-14
>
pez05:08:24

In this talk, David Nolen demos using Krell to have two REPLs, running on separate devices, with separate state, using the same code base. https://youtu.be/3HxVMGaiZbc?t=2054 is something similar supported by shadow-cljs?

thheller06:08:31

@pez what do you mean specifically? this is pretty much the default of how shadow-cljs works? only that you only need one process and not 2 separate ones?

thheller06:08:54

you can connect as many devices as you want and/or run as many builds in parallel as you want (or your machine can tolerate)

pez06:08:58

Yes, I do that often. But can I control which REPL I am connected to?

thheller06:08:36

yeah thats a bit manual at the moment since no editor (wink, wink) has added support for manual selection yet

thheller06:08:56

quick overview you can check is http://localhost:9630/runtimes

pez06:08:05

I’ll be happy to add it to Calva.

thheller06:08:08

it'll list all the runtimes and their id

thheller06:08:44

and when you can pick when when you start the repl via (shadow.cljs.devtools.api/repl :the-build-id {:runtime-id 123})

thheller06:08:18

the id is also logged on startup by the runtime shadow-cljs: #4 ready!

pez06:08:51

I’ve seen those. 😃

thheller06:08:27

there is also (shadow.cljs.devtools.api/repl-runtimes :the-build-id) to get the data from the REPL without the UI

thheller06:08:53

[{:since #inst"2021-08-14T06:20:06.623-00:00",
  :proc-id "3133b4c9-82b9-4144-a0dc-68e8daddfdae",
  :connection-info {:remote true, :websocket true},
  :client-id 4,
  :user-agent "Chrome 537.36 [Win32]",
  :type :runtime,
  :lang :cljs,
  :build-id :browser,
  :host :browser,
  :dom true}
 {:since #inst"2021-08-14T06:20:07.205-00:00",
  :proc-id "3133b4c9-82b9-4144-a0dc-68e8daddfdae",
  :connection-info {:remote true, :websocket true},
  :client-id 5,
  :user-agent "Chrome 537.36 [Win32]",
  :type :runtime,
  :lang :cljs,
  :build-id :browser,
  :host :browser-worker,
  :dom false}]

thheller06:08:22

from my test build that also has a webworker connected (which you can also REPL into)

thheller06:08:47

I renamed some stuff so its :client-id from that list

pez06:08:20

Thanks! I'll play some with this. Great stuff!

pez09:08:00

I get a lot of #X ready! messages, with increasing id numbers. (And the id:s are bumped accordingly.) I’m using the :react-native target and have started a web and an iOS client. Looks like so after a while:

= web
#5 ready!
#7 ready!
#8 ready!
#10 ready!
#12 ready!
#14 ready!
#16 ready!
#17 ready!
#20 ready!

= iOS Simulator
#4 ready!
#6 ready!
#9 ready!
#11 ready!
#13 ready!
#15 ready!
What could be part of this is that I started the apps, then closed the lid on my laptop and made some breakfast to the family. Now opening the laptop again there is this. I think I just managed to reproduce the behaviour when closing and re-opening, but only in the web app. The runtime info looks like so:
[{:connection-info
  {:remote true,
   :websocket
   true},
  :since
  #inst "2021-08-14T08:45:55.502-00:00",
  :host
  :react-native,
  :type :runtime,
  :lang :cljs,
  :build-id :app,
  :proc-id
  "165ac90e-ac63-4516-9453-3c49e4f85815",
  :client-id 15}
 {:connection-info
  {:remote true,
   :websocket
   true},
  :since
  #inst "2021-08-14T08:53:30.446-00:00",
  :host
  :react-native,
  :type :runtime,
  :lang :cljs,
  :build-id :app,
  :proc-id
  "165ac90e-ac63-4516-9453-3c49e4f85815",
  :client-id 20}]
I can select the repl as per above, and evaluations happen in the right app.

thheller09:08:54

the ready message you get when the websocket connects. if that disconnects it'll attempt to reconnect, which gives you a new id

thheller09:08:33

don't know why you'd get that many though. never seen that before. is it constantly reconnecting the websocket or does that actually have that many websockets open?

thheller09:08:16

the only problem I remember was with android and react-native. that didn't disconnect the websocket when reloading the app. don't know if thats still a thing

pez09:08:04

I can test that.

pez10:08:46

> is it constantly reconnecting the websocket or does that actually have that many websockets open? Don’t know if this answers it, but if select an old id, I get no complaints, but when evaluating I get this message

The previously used runtime disappeared. Will attempt to pick a new one when available but your state might be gone.

pez10:08:02

It seems that the :since doesn’t change. Can I rely on that, as long as the app is not reloaded?

thheller10:08:35

yeah, so the longer backstory to all of this: I built shadow.remote as the default remote mechanism for shadow-cljs. even the UI uses that to do everything it does. All REPLs use it. so ideally any editors would also directly connect to the shadow.remote endpoint. part of shadow.remote is a notification for when a runtime connects or disconnects

thheller10:08:17

the editor would then be supposed to handle that and inform the user in some form of UI or auto select the next one etc

thheller10:08:39

the regular REPL however is stuck in a block Read with no way to ask the user what they want to do

pez10:08:48

Can I read about the remote API somewhere?

thheller10:08:14

so I had to compromise in picking the next one that arrives. however that logic doesn't apply if you specified one

thheller10:08:20

could be considered a bug I guess 😛

thheller10:08:13

very simple EDN (transit-encoded) messages over a websocket for now

thheller10:08:04

tap> and the inspect UI runs over shadow.remote, so anything the UI displays you have access to as well

pez10:08:12

Using this I can solve the old issue with Calva not really knowing when the REPL is available.

pez10:08:43

It does make shadow-cljs very special from Calva’s POV, but may well be worth it to create a nice UI/Ux.

thheller10:08:33

a shadow.remote over nrepl would be possible as well if that helps

thheller10:08:11

just as a transport layer I mean. don't know how well that would integrate into your nREPL code though as the message flow is a bit different. usually nrepl doesn't push messages to the client without receiving a request first

thheller10:08:41

but yeah all the extra ops you get are shadow-cljs specific

pez11:08:53

I’d love me a shadow.remote over nrepl. Calva has ways to deal with “orphaned” incoming messages, so I should be able to weed out the ones from shadow/remote there.

thheller11:08:35

well the messages you'd get are all marked as such, so "op" "shadow.remote" or so. haven't really looked into it too much yet

thheller11:08:31

the thing I don't like about nrepl is that there is no explicit connection management. so you don't know when a connection starts or ends from the middleware perspective

pez11:08:35

As for the android react-native client. It doesn’t disconnect at app reload, as you remembered. It is cleaned away after a few seconds though. Both iOS and web disconnect immediately before reconnecting.

thheller11:08:55

yeah I added that cleanup specifically for android RN

currentoor14:08:36

I’m trying to use react-bootstrap with shadow-cljs and I’m requiring like so

(:require ["react-bootstrap" :as bs])
But when I console.log bs roughly half the keys (component names) have no values
Image: {$$typeof: Symbol(react.forward_ref), defaultProps: {…}, render: ƒ}
InputGroup: {$$typeof: Symbol(react.forward_ref), Text: {…}, render: ƒ, Radio: ƒ, Checkbox: ƒ, …}
ListGroup: {$$typeof: Symbol(react.forward_ref), Item: {…}, render: ƒ}
ListGroupItem: {$$typeof: Symbol(react.forward_ref), defaultProps: {…}, render: ƒ}
Modal: undefined
ModalBody: undefined
ModalDialog: undefined
ModalFooter: undefined
Any idea why Modal and friends would not get imported?

currentoor14:08:20

I noticed this map also includes __esModule: true

currentoor14:08:25

not sure if that that’s relevant

currentoor15:08:01

I see a pattern, the components that are missing are all function components

thheller15:08:39

not sure. might be a version conflict?

currentoor15:08:23

of what? react?

currentoor15:08:15

(js/require "react-bootstrap/Tabs") does load the correct function component

currentoor15:08:35

ditto for (js/require "react-bootstrap")

currentoor15:08:58

although that might be something else?

thheller15:08:25

which :target is this?

currentoor15:08:47

:browser but it’s an electron app with node integration in the renderer

thheller15:08:14

hmm might be the other problem again. try setting :js-options {:entry-keys ["browser" "main"]} in your build config

currentoor15:08:28

yup that worked!

currentoor15:08:57

so getting rid of the default last "module" fixed it, that’s strange

thheller15:08:34

yeah, it has come up multiple times now

currentoor15:08:55

i see, well thank you so much for the help

currentoor17:08:57

with this setting, does that mean I cannot use some npm packages? i.e. do some packages require "module"?

thheller20:08:53

annoyingly some require that yes but after adding that more people reported problems than before

thheller20:08:26

overall most packages seem to work either way

currentoor20:08:56

the weird and wonderful world of npm

thheller20:08:55

well the transition to ESM away from CommonJS is going to bring many hurdles. this is one of them since commonjs<->ESM interop is tricky

thheller20:08:33

I would have expected this to go a little faster but so far it seems rather slow going

thheller20:08:51

no idea when it'll get better and how many weird patterns will emerge

thheller20:08:02

from a build tool perspective this is rather frustrating 😛

alexdavis17:08:33

Is there a way to output to the root directory? expo EAS requires the entry point to be a root level index.js file. I can make it work by copying shadows build/index.js to the root level and then doing a find/replace to fix the paths in the file, so is there a reason why shadow wouldn’t allow you to do this automatically?

thheller20:08:23

@alex395 do this instead https://github.com/thheller/reagent-react-native/blob/master/react-native/index.js. :output-dir "." might also work but not recommended, no guarantee that shadow-cljs won't overwrite a file that you might need for something. preferable to have its own directory that you can delete at any point

alexdavis20:08:11

Thanks! I actually tried "." but it doesn’t generate the correct paths for the js/require calls, then I pretty much did what you’ve suggested there

thheller20:08:05

:target :esm might also be an option. been a while since I looked at react-native stuff, see https://clojureverse.org/t/generating-es-modules-browser-deno/6116