Fork me on GitHub
#shadow-cljs
<
2021-03-21
>
jamesmintram11:03:05

Hey all - I think this is a shadow question: I have integrated slate using shadow's automatic npm imports. I see this error:

Uncaught TypeError: a is undefined
createGenerator shared.js:27
levels index.js:757
levels: function* levels(editor) {
.....
    yield* levels;
  },
Does anyone know if there some shadow setting I have missed? Or something else obvious?

thheller11:03:01

well the "obvious" answer most likely is that you are looking at the wrong piece of code

thheller11:03:19

more likely that you are passing an incorrect argument to a function somewhere

thheller11:03:48

like where/how are you calling slate?

jamesmintram11:03:25

In this particular case, the error occurs when I click the slate widget. This is how I have create the slate component:

(defn text-editor* []
  (r/as-element
    (let [editor (slate-react/withReact (slate/createEditor))
          [value setValue!] (react/useState  [{:type "paragraph"
                                             :children [{ :text "A line of text in a paragraph." }]
                                             }])]
      [:> slate-react/Slate {:editor editor
                             :value value
                             :onChange setValue!}
       [:> slate-react/Editable]])))

jamesmintram11:03:32

I cannot see a call to createGenerator and I notice that the fn above createGenerator in the callstack itself a generator (`yield return..`) - I am guessing google closure does something to insert the call to createGenerator

jamesmintram11:03:56

(which led me to wonder if there were any compiler settings I could tweak via the shadow config)

thheller11:03:34

and you did verify that editor is actually what you expect? I mean log it or so. I don't have a clue what any of this does but the code looks very suspicious

jamesmintram11:03:47

Yep - it's what I expect

superstructor12:03:34

@thheller Would you consider providing a new build hook that gives access to change/replace forms ? i.e. after the reader step but prior to compilation, ideally before macro expansion ? Is it even a feasible idea or difficult to provide ?

thheller13:03:22

@jamesmintram I cannot say without an reproducible example. much of the code you posted looks very suspicious though. for example in the react/useState you are using a CLJS datastructure that you then hand to the slate-react/Slate via :value. so unless slate-react is some kind of CLJS wrapper for slate that will likely not work. also the (slate/createEditor) would recreate the editor on every render.

jamesmintram14:03:47

Thanks. I think I have found something interesting (maybe not!) but inside the function that fails, I see

arguments: undefined
arguments: Arguments(2) [{…}, {…}, callee: ƒ, Symbol(Symbol.iterator): ƒ]
inside of chrome's Scope Local display. Clearly something weird there. Should I be asking about this in the more general clojurescript or could this be some weird compilation/module bundling thing caused by shadow?

jamesmintram14:03:10

(I've updated the code to use r/with-let and clj->js on the maps provided. Strangely, the observed behaviour did not change (I originally though there was some magic happening doing the clj->js conversion behind the scenes)

thheller14:03:44

I can almost guarantee that you are looking at the wrong place

thheller14:03:52

this is almost certainly not a bug in clojurescript or shadow-cljs

thheller14:03:31

it is far more likely that something in your code is either passing some incorrect data or calling a wrong function or so

thheller14:03:42

I cannot possibly help you with that without seeing more code

thheller14:03:51

digging into the tools before you debug your own code is not useful

thheller14:03:25

make sure that all the data you are passing around is exactly what you are supposed to pass around

thheller14:03:05

of course this could be a problem in shadow-cljs or cljs but 99% of the time it isn't

jamesmintram14:03:27

> I can almost guarantee that you are looking at the wrong place > > > Ok. Thanks. I'll pull it into a minimal example. (it will probably start working then 😂 )

thheller14:03:32

so stop looking at library implementation code and investigate your own code first

thheller14:03:59

this also all appears to be using react hooks so make sure you are actually using a modern reagent version with functional components

👍 3
thheller13:03:24

@superstructor what is the use case? it would be fairly easy to provide such a hook but depending on what it wants to do might be rather tricky to integrate. eg. caching might be affected.

superstructor13:03:16

The use case is for debugging/tracing tooling without having to insert macros throughout the source code, so to do the transformation instead in dev builds.

superstructor13:03:01

Like, for example adding fn-traced (re-frame-debux) macro to trace all the values of all the forms in all functions throughout an entire codebase; or on specific namespaces with a metadata tag.

superstructor13:03:44

Unfortunately would need to modify ns form to add :requires as well, so I imagine that would make it more difficult to integrate ?

thheller13:03:07

yes, as soon as it affects more than just the form you are processing things get tricky

thheller13:03:55

I spent quite a bit of time thinking about an integrated tracing solution in shadow-cljs myself and the UI and tap> integration is basically the first step in that direction

thheller13:03:26

since being able to store the traces and looking at them remotely is probably the hardest part

thheller13:03:40

but nothing practical regarding the tracing is implemented yet

superstructor13:03:34

what are the main missing pieces ? instrumenting an entire codebase without manually adding tap ?

thheller13:03:14

well I don't know. I don't really mind adding an extra require and calling something as long as that something doesn't get in the way in release builds and just seamlessly disappears

thheller13:03:08

so once that exists there could be an extra convenience thing that allows you to skip that extra require and uses metadata or so to control what is traced

thheller13:03:06

tracing the entire codebase will IMHO give you way too much data you don't really care about. it is more about tracing app semantics (eg. event A -> B -> C) than tracing (+ x 5)

thheller13:03:12

at least for me

superstructor13:03:27

yes, agreed. Our main use case is tracing re-frame event handler functions, and probably only namespaces tagged by the developer by metadata. But also wouldn't want to design a re-frame only solution for shadow-cljs, obviously.

thheller13:03:21

yeah dunno how that would look

superstructor13:03:51

Ideally, we'd like to be able to • look at the namespace metadata for a ^:re-frame/trace • If it is there, then look for all reg-event-* forms in that ns • Replace all the fn or defn forms in those forms with fn-traced or defn-traced from re-frame-debux • Or, if macroexpansion has already run just do the equivalent of the re-frame-debux macroexpansion inline, but we would still need to refer to some helper fns / global state to store the tracing via requires.

superstructor13:03:08

In :compile-prepare I see a bunch of files in the build-state . Can one just replace those files in their entirety with a different file (i.e. read the file, modify it, write it out to a tmp location) then return the new tmp files in the build state ? or is that a really bad idea ?

thheller13:03:31

well kind of a bad idea yeah

thheller13:03:51

in :compile-prepare all dependencies are already resolved. so you cannot add a new require or so for example.

thheller13:03:43

but I honestly don't see a problem with the current re-frame tracing stuff apart from how the macros work. you could rewrite those fairly simply so they don't rely on switching classpath dependencies or shadow-cljs :ns-aliases

thheller13:03:47

but yeah I don't have an answer for you if your goal is to get rid of some extra require/forms and only control it via metadata instead

thheller13:03:18

shadow-cljs will actually get in your way if you try to do that more than cljs.main would

thheller13:03:22

some of the stuff shadow-cljs does favors more repeatable/predictable builds so sacrifices some more dynamic aspects of the compilation that would technically be possible

superstructor13:03:37

all good 👍:skin-tone-2:Thanks for all the info. I will think some more about it.

zendevil.eth15:03:23

Not sure if this is a shadow-cljs issue or a react-native issue, but I’ve found that when I make a change in my app and save it, and after the shadow-cljs compiler compiles the code, instead of getting a refresh (live reload is on), I’m seeing the whole js bundle reloading each time. Is there a way to prevent the whole bundle reloading each time?

thheller15:03:41

that is likely the default built-in react-native reloading mechanism. you should turn that off. no clue how. used to be an option in the react-native menu thingy on the device.

zendevil.eth15:03:59

But when I “Disable Fast Refresh” in the React Native Debug Menu, the refresh also stops so the app isn’t updated

thheller15:03:48

shadow-cljs provides it own hot-reloading and you need to turn off all built-in reloading from react-native as they will otherwise interfere

thheller15:03:50

I cannot help you debug hot-reload issues other than point at the example that has it built-in and working https://github.com/thheller/reagent-react-native/blob/master/src/main/test/app.cljs#L33-L39

thheller15:03:11

the core essential part of the render-root call. if you do not use that you are on your own and I cannot help.

zendevil.eth15:03:26

In fact, I am using render-root and also added {:dev/after-load true} but the refresh doesn’t happen

thheller15:03:49

check "things to avoid" ... you might be doing one of those

zendevil.eth15:03:37

@thheller I have goog.object/get in a reframe handler. Does that come under “missing :ns require”?

zendevil.eth15:03:02

And of course I have js/

zendevil.eth15:03:24

Other than that, I’m not using defonce at all

zendevil.eth15:03:37

and am using the init-fn

zendevil.eth15:03:10

{:deps true
  :builds
 {:test
  {:target    :npm-module
   :output-dir "test-out"
   :entries [humboi.core-test]}
  :dev
  {:target     :react-native
   :init-fn    humboi.core/init
   :output-dir "app"
   :compiler-options {:closure-defines
                      {"re_frame.trace.trace_enabled_QMARK_" true}}
   }}}

zendevil.eth15:03:16

(defn
  init
  {:dev/after-load true}
  []
  (dispatch [:register-db])
  (render-root "Humboi" (as-element [root-comp])))

thheller15:03:58

this is impossible to debug without seeing more code. I don't have a clue what the :register-db thing does or the root-comp

thheller15:03:28

one common issue is storing functions in your app database for example

thheller15:03:50

those are not hot-reloaded and will you will end up calling "old" code instead of the new

zendevil.eth15:03:56

(defn
  init
  {:dev/after-load true}
  []
  (render-root "Humboi" (as-element [view])))
Change to this ^^, but still no hot-reloading

zendevil.eth15:03:49

I’m not storing any functions in the app database

thheller15:03:54

> this is impossible to debug without seeing more code

thheller15:03:24

add a (js/console.log "foo") to the init fn to check if its actually getting called

thheller15:03:37

if so it is all up to your code

zendevil.eth15:03:19

no it’s not being called

thheller15:03:53

is the websocket connected?

zendevil.eth15:03:11

how does one tell?

thheller15:03:35

log should say so

thheller15:03:58

or open the UI http://localhost:9630/runtimes should be listed there

zendevil.eth15:03:18

new metro logs have stopped ever since I turned off live reload in the emulator

zendevil.eth15:03:16

Yes there’s one “21 minutes ago”

thheller15:03:41

there must be at least 2? the first #1 is the CLJ runtime

thheller15:03:54

yes, that is missing the react-native runtime. so the websocket is not connected.

zendevil.eth15:03:25

how do I connect the websocket?

thheller15:03:51

I assume you have the watch running so it should connect automatically

thheller15:03:00

if not look for the error message in the log

zendevil.eth15:03:18

there’s no error message in the shadow log

thheller15:03:27

not talking about the shadow log

thheller15:03:32

react-native log

zendevil.eth15:03:04

the last log is the following:

giving up trying to connect

thheller15:03:34

didn't we already talk about the IP stuff before? it might be picking an incorrect IP address so you need to tell it the correct one

thheller15:03:58

either by setting :local-ip "1.2.3.4" in your build config or shadow-cljs watch app --config-merge '{:local-ip ...}'

zendevil.eth15:03:13

I removed some :devtools from shadow config and it works now. Thanks for your time @thheller 🙂 The periodic websocket disconnects are gone as well.

noorvir16:03:16

I’m a newbie so apologies if this is naive: how do you release a reagent app built with shadow-cljs release app ? I create an app template using create-reagent-template. I’m using shadow-cljs with the lein flag set to true. The dev setup works using shadow-cljs watch app but I noticed there’s no index.html file generated. Similarly, when I build the release version, there’s not index.html . I’m failing to understand what is the entry point of the reagent app. I’m trying to deploy to Firebase Hosting but predictably it complains with a 404. Here’s my shadow-cljs.edn :

{:lein         true
 :builds       {:app {:target     :browser
                      :output-dir "public/js"
                      :asset-path "/js"
                      :modules    {:app {:entries [noorvir.com.core]}}
                      :devtools   {:after-load noorvir.com.core/mount-root}}}
 :dev-http     {3000 {:root    "public"
                      :handler noorvir.com.handler/app}}}
Thanks in advance!

thheller16:03:45

@noorvir.aulakh creating the index.html is your job. shadow-cljs does not handle html generation.

noorvir17:03:16

I tried that too. This is the index.html file I have in the public folder right not:


<html>
<head>
    <meta charset="utf-8">
    <meta content="width=device-width, initial-scale=1" name="viewport">
    <link href="/css/styles.css" rel="stylesheet" type="text/css">
</head>
<body class="body-container">
<div id="app"></div>
<script src="js/app.js" type="text/javascript"></script>
</body>
</html>
Am I supposed to call an init method somewhere?

thheller17:03:56

with your build config yes

thheller17:03:17

you can swap :entries [.core] to :init-fn .core/init

noorvir17:03:26

Ok this worked! It took way longer because the function was called init! which I didn’t notice.

thheller17:03:25

then the code will call that fn on load

thheller17:03:46

how do you handle this in dev? this is not specific to release? what did your :handler do?

noorvir17:03:27

Ok trying this out now. This is the handler that was generated by the template:

(def app
  (reitit-ring/ring-handler
    (reitit-ring/router
      [["/" {:get {:handler index-handler}}]
       ["/items"
        ["" {:get {:handler index-handler}}]
        ["/:item-id" {:get {:handler    index-handler
                            :parameters {:path {:item-id int?}}}}]]
       ["/about" {:get {:handler index-handler}}]
       ["/cards" {:get {:handler cards-handler}}]])
    (reitit-ring/routes
      (reitit-ring/create-resource-handler {:path "/" :root "/public"})
      (reitit-ring/create-default-handler))
    {:middleware middleware}))

thheller17:03:19

so in dev you have a server but in production you don't? I'm confused

noorvir17:03:16

I was for serving files for local dev I assumed. https://shadow-cljs.github.io/docs/UsersGuide.html#dev-http It did seem a little odd to me that there should be two different entry points.

thheller18:03:19

yeah if you don't do anything server-side then just a regular index.html and :dev-http {3000 "public"} is enough

👍 3
noorvir17:03:26

Ok this worked! It took way longer because the function was called init! which I didn’t notice.

noorvir17:03:37

Thanks a lot for the help! 🙌 🙏

ribelo20:03:33

I thought there was a page/app somewhere that allowed to see what js output

ribelo20:03:43

unfortunately I can't find

ribelo20:03:02

and unfortunately I didn't save

ribelo20:03:06

ok, found it