Fork me on GitHub
#shadow-cljs
<
2022-10-20
>
rschmukler13:10:55

I am working with a react application and am noticing an interesting bug w/ shadow + uix (v1) and live-reload with a :after-load a/render in my build config. If I have

;; a.cljs
(ns a
  (:require [b])

(defn app []
  [b/render])

(defn render []
  (uix.dom/render [app] (js/document.getElementById "app")))

;; b.cljs

(ns b)

(defn render []
  [:p "Hello world"])
Everything works fine (ie. it will update if I modify b/render and save the file If I have:
;; a.cljs
(ns a
  (:require [b]))

(defn app []
  [b/render])

(defn render []
  (uix.dom/render [app] (js/document.getElementById "app")))

;; b.cljs

(ns b
  (:require [c]))

(defn render []
  [c/render])

;; c.cljs

(ns c)

(defn render []
  [:p "Hello world"])
It will not reload if I modify c/render until I also re-save b.cljs. If I add c as a require to a then it will work properly. I suspect it has to do with caching intermediates, but I would expect this to work… Is this expected?

Lone Ranger13:10:37

Are you sure c isn’t reloading? Throw in some printlns to rule out that this isn’t a dataflow issue.

Lone Ranger13:10:31

Meaning, c/render doesn’t take any arguments here and there is no internal hooks so it has no reason to know to rerender (even if the code has changed and reloaded)

Lone Ranger13:10:55

Same thing with b/render

rschmukler14:10:26

So adding printlns confirms that the files are reloaded correctly on save (ie. modifying c.cljs will cause c and b to be reloaded

rschmukler14:10:40

and it also confirms that the render functions are not fired

rschmukler14:10:00

But, when requiring c from a then the render function is fired… Which is what I am having trouble understanding

rschmukler14:10:38

ie. I could understand it one way or the other, but why does re-requiring c from a cause it to work?

Lone Ranger15:10:23

been there, partner haha

Lone Ranger15:10:52

The order of requires is probably important there too.

(ns a
  (:require b c))
is probably not going to be the same as
(ns a 
  (:require c b))

Lone Ranger15:10:29

I'm guessing that c/render needs to be redefined first, then b/render, then a/render for hot reload path to function correctly

👍 1
Lone Ranger15:10:53

due to potential for eager binding in scope capture

rolt15:10:04

do you by any chance use react 18 ? i remember seeing an issue on live reload with 3 namespaces

rschmukler16:10:39

I’m actually using react 16 I believe

thheller17:10:35

as far as I can tell this is all working as intented

Lone Ranger18:10:44

Agreed, I think this is more of a reactive coding / framework nuance issue and shadow is working as expected

rschmukler12:10:16

Yeah I think it has to do with react memoization inside uix v1

rschmukler12:10:39

The dummy prop is a cute trick though, hadn’t thought of that.

Lone Ranger14:10:10

I've been called worse 😉

shaunlebron16:10:18

I noticed that in dev builds, source files and source maps (in a base64 url) are embedded in an eval statement. I’m suspecting that react-native doesn’t support source maps on eval’d code. Is there a workaround for this?

thheller17:10:55

since it doesn't support source maps at all there really isn't anything to do

shaunlebron17:10:07

it looks like react native supports source maps yeah? https://reactnative.dev/docs/next/sourcemaps

thheller17:10:51

yes, for the sources it processes itself

thheller17:10:52

but not input source maps

shaunlebron17:10:23

input source maps are an issue with Metro that I’ve been looking at for a couple weeks now yeah

thheller17:10:22

the eval js maps are only emitted so source maps at least work when using the run in chrome thing

shaunlebron17:10:40

oh, so these “embedded” source maps produced by shadow dev builds never have to be processed as input source maps if they’re evaluated at runtime right?

thheller17:10:43

not sure thats even still a thing though. been a couple of years since I looked at react native stuff

thheller17:10:58

they aren't meant to be processed by metro no

shaunlebron17:10:01

i guess the important question here is if shadow can be configured to create a dev build without the embedded source maps

thheller17:10:23

you can use :target :npm-module for that

thheller17:10:51

that loads just fine in react-native IIRC, may require some boilerplate though

thheller17:10:03

but that target does no eval and has regular source maps

shaunlebron17:10:17

hmm okay, i'm surprised no one else has asked for this. I’m pretty sure the embedded source maps are responsible for nonsensical stack traces inside Expo: https://docs.expo.dev/get-started/errors/#redbox-errors-and-stack-traces

thheller17:10:33

but IIRC it had the issue that metro will try to process everything. and it used to take a lllooooooooooong time to process cljs.core

thheller17:10:53

I spent a lot of time trying to get source maps to work

thheller17:10:00

and it just wasn't possible back then

thheller17:10:14

I doubt much has changed but haven't checked in a long time

thheller17:10:39

if you get it to work with :target :npm-module I'll happily change :target :react-native

shaunlebron17:10:41

metro issue #104 has been open for five years, yeah I saw your post on it there all those years ago, I took a stab at it, still trying

thheller17:10:08

but there is no option to turn of the eval mode for :react-native. it is there for good reasons

shaunlebron17:10:01

im new to react-native which is part of the problem here, but what are the reasons for the eval? was it specifically for testing react-native somehow in chrome?

thheller17:10:44

speed is biggest reason

thheller17:10:55

if you expose sources to metro it'll process all of them

thheller17:10:12

and takes like 30sec or so for cljs.core alone, so things just get annoyingly slow

shaunlebron17:10:20

oooh, that explains why I have not seen a speed issue

thheller17:10:35

(it used to anyways, not sure its still an issue)

shaunlebron17:10:47

i saw the re-natal-husk solution in the cljsrn wiki talking about the slowness with that

thheller17:10:03

but also this was the only way to get source maps to work reliably in the chrome devtools runtime thingy

thheller17:10:24

yes, just run with :target :npm-module and you'll see if its still slow

shaunlebron17:10:45

i suppose most people are just developing on chrome, then for prod they’re manually mapping error locations in the sentry logs to cljs sources using their own manual tooling

thheller17:10:34

no clue what people do. I have never done any actual react-native dev myself

shaunlebron17:10:05

i got a stack trace trying to load an npm-module build into Expo Go

shaunlebron17:10:29

might be a path issue

shaunlebron17:10:59

i'll go down this rabbit hole another time, but thanks for the context here.

shaunlebron18:01:49

@thheller I’m trying again to get :target :npm-module working on react native to test if it’s still slow. I see that it doesn’t generate an <output-dir>/index.js like target react-native would.

shaunlebron18:01:50

Is there a way I can recreate that index.js file required by react-native, I feel that including my main project’s js file will be missing other things that seem to be included by shadow-cljs to make the build work

thheller19:01:34

why does it have to generate the index.js? I mean just create it yourself?

thheller19:01:02

just something like require("./cljs/your.ns.js").init(); or so

shaunlebron19:01:55

that was my question actually why your react-native target creates one, and what would I be missing if I tried to do it myself

shaunlebron19:01:33

thumbing through it, I saw a lot of shadow-react-native namespaces being included

thheller19:01:01

the :target :react-native tries to HIDE all the CLJS output from metro

thheller19:01:11

but still try to make all JS requires visible

thheller19:01:21

thats why it creates a bunch of boilerplate code

thheller19:01:44

:npm-module is just plain commonjs, it doesn't try to hide anything and therefore doesn't need all this boilerplate

thheller19:01:03

but that also means that metro will process all of it, which used to be very slow

thheller19:01:14

the only point of the index.js is to have some code that renders something. that code can be all CLJS, but it needs to be a called from a index.js file

thheller19:01:39

so just that one line loading the cljs code and calling the init fn is equivalent to what :init-fn in :target :react-native does

shaunlebron19:01:04

got it, thank you

shaunlebron19:01:23

I created the index.js file with require('./dev.js').init_BANG_(), since our config has :init-fn dev/init!, but I got back an error when trying to load it:

iOS Bundling complete 130ms
 ERROR  Error: Requiring unknown module "undefined".
Somewhere this is failing, and this doesn’t seem equivalent to the index.js file created for the react-native target.

thheller19:01:12

whats the full error? where is the undefined coming from?

thheller19:01:53

as I just explained. it is obviously not equivalent in mechanics, since it doesn't do any of the hiding

thheller19:01:06

but if you strip out the hiding part, then just the init call remains

thheller19:01:25

if you want to setup a repo I can take a look

thheller19:01:34

otherwise I can't really guess enough what you are doing

thheller19:01:05

the 130ms is already suspicious enough. there is no way it processed all the code in that time

thheller19:01:27

also I strongly recommend keeping the generated code separate

thheller19:01:18

so :output-dir "cljs" so that all the files go to <project>/cljs/..., then <project>/index.js does require('./cljs/dev.js').init_BANG_()

thheller19:01:55

that way you can at any time kill the cljs dir without worrying about any of your other files

shaunlebron19:01:15

I am driving to a doctors appt, i will answer when i get back. There's a lot to debug and figure out but i got no info on where the error was coming from. The build time of 130ms was because it was a rebuild, it took longer the first time. Sorry and thank you

Lone Ranger18:10:12

This is kind of a bit off topic for shadow but @thheller I'm kind of curious if you've done any devlogs or podcasts where you talk about how you can stay so motivated on an open source project for so long. It's really impressive.

thheller18:10:33

well, I built it for my own work projects in the first place. and still use it for that, so sort of just solving my own problems that others also happen to have 😉

Lone Ranger18:10:06

nah come on haha

Lone Ranger18:10:51

well where it comes from, good stuff, and thanks 😄