Fork me on GitHub
#shadow-cljs
<
2018-09-29
>
priornix01:09:43

Hi there, I am using shadow-cljs with keechma. Hot-reload on the keechma project works with figwheel but does not work with shadow-cljs.

priornix01:09:02

How a keechma project is structured is usually this: core.cljs -> app/definition -> ui/ui -> component

priornix01:09:12

In figwheel when the component is modified the dependency graph is resolved correctly, and app/definition is reloaded. In shadow-cljs, the reloading propogates from component and stops at ui/ui

priornix02:09:58

The browser console output for shadow-cljs for the code above is as follows:

shadow-cljs: call example.client.core/stop 
shadow-cljs: load JS example/client/ui/main.cljs 
shadow-cljs: load JS example/client/ui.cljs
shadow-cljs: load JS example/client/core.cljs

priornix02:09:41

The changes did not propagate to although it clearly has a dependency on ui/ui:

(def definition
  {:components    ui
   :controllers   controllers
   :subscriptions subscriptions
   :router :history
   :routes [":category/:page"]})

thheller07:09:05

you are keeping references to namespace defs in a map in another namespace

thheller07:09:50

honestly this should be avoided as this is global state

priornix07:09:08

Yes, that's true.

priornix07:09:32

It does make the code simpler though.

thheller07:09:33

so you currently have 2 options

thheller07:09:00

(defn definition [] ...) and change all references to (definition)

thheller07:09:18

this will recreate the map whenever it is used. ensuring that it has all the latest references

priornix07:09:37

I tried to do that earlier that, but it didn't work.

thheller07:09:57

or tag the namespace with (ns ^:dev/always the.ns-with-definition)

thheller07:09:07

but honestly thats horrible

priornix07:09:51

I'd rather go with the first approach, but I didn't manage to get it to work, I'll try to replace ui as well.

thheller07:09:17

geez .. there is sooooooo much global state in this ...

thheller07:09:51

every file is full of it

priornix07:09:05

Yes, that's how keechma is structured. It's only for the component and the app-definition though. States are kept in it's own space. Feel free to ping @mihaelkonjevic

priornix07:09:40

Having said that, I prefer keechma to re-frame it's a higher order abstraction than re-frame and deals nicely with app states

thheller07:09:19

hmm yeah I'll add an option to force reload all dependents

priornix07:09:36

Actually if you look at re-frame it's full of global states as well

thheller07:09:39

its generally not necessary and slower

thheller07:09:38

yeah I know but those update references in a global map on each reload and generally don't keep state from another namespace

thheller07:09:47

(def x {:a another/b})

thheller07:09:58

thi sis the only problem

thheller07:09:09

calling functions etc from other namespace is not a problem

thheller07:09:28

or rather not even that is the problem

thheller07:09:35

the issue is when there is one-hop

priornix07:09:10

So currently figwheel does a global reload

thheller07:09:15

(def x {:a ns2/thing}) (ns ns2) (def thing {:a ns3/thing})

thheller07:09:11

figwheel isn't really involved in this

thheller07:09:20

the default in CLJS is to :recompile-dependents

thheller07:09:27

and all of them will be reloaded

priornix07:09:41

For now we can put the keechma definitions all within a file. Just a matter of convention

thheller07:09:42

but this recompiles a lot more namespaces that don't actually need to be recompiled

priornix07:09:56

Hence why shadow is much faster

thheller07:09:59

so it can be a lot slower in bigger apps

thheller07:09:22

so what is really needed is a :reload-dependents option

thheller07:09:36

since the code didn't change we only need to reload it to trigger the global state updates

thheller07:09:50

without actually recompiling it

priornix07:09:57

Yes, I was looking for such an option. To manually specify certain namespaces to be reloaded always

priornix07:09:19

The other way is to let the developer specify the namespaces that has to be reloaded. so {:reload-namspace :all} versus {:reload-namespace [foobar.core]}

thheller07:09:53

the other option is to not use those chained references from one namespace to another

thheller07:09:04

(ns a {:a another/a}) is totally fine

thheller07:09:16

since that ns will depend on another both will be reloaded

thheller07:09:41

the problem is when the second depends on the third because the first won't depend on the third

thheller07:09:53

you can just require the third in the first to make it reload

thheller07:09:18

then everything should work as expected

priornix07:09:55

Makes the code fragile though, having to remember which ones to require

priornix07:09:05

Fragile only in dev-mode of course

priornix07:09:43

Anyway, thanks @thheller for the help and the great tool.

thheller07:09:19

this has come up often enough now that I'll probably add it soon 😉

👍 4
bbss09:09:02

On dependencies in workers that depend on window: I've gone from multiple builds to splitting into multiple modules, that made them build together, but it's the :devtools :preloads fulcro.inspect.preload that have the thing that depends on window.-localStorage

thheller09:09:40

right those always get added to the default module

bbss09:09:51

okay, it's not really a big issue for me

bbss09:09:56

happy they build together now 🙂

thheller09:09:01

I can probably add support for :preloads in :modules themselves

thheller09:09:12

makes more sense actually

thheller09:09:03

so each module can have its own preloads instead

bbss09:09:26

Yeah that makes sense to me.

bbss09:09:00

Another issue I now face is that if I want to do a

(defn ^:dev/after-load recalc []
  (prim/transact!
   (:reconciler @app)
   `[(gsv.api.mutations/update-some-data {})]))
I don't have a ref to the state in the worker.

bbss09:09:30

:after-load should maybe be available from the config though. Looking into that.

thheller09:09:08

what do you mean?

bbss09:09:17

The namespace that starts the worker doesn't get re-evalled when just the worker changes.

bbss09:09:27

I want to redo the calculation that uses the worker.

thheller09:09:50

then you must create an after-load fn in the main app that notifies the worker to recalc (eg. send a message to it)

thheller09:09:07

shadow-cljs can't do that for you

thheller09:09:09

live-reloading currently also doesn't work for workers

thheller09:09:31

so I recommend killing them in a hook and restarting them

thheller09:09:18

it has been a while since I used the worker stuff myself. I think there are still a few rough edges that could be tweaked

bbss09:09:19

Yeah for now I've just been loading many workers and reloading the namespace that starts them, not caring about cleaning them up.

bbss10:09:45

So I see the shadow-cljs logo when the worker compilation happened. But I can't seem to find a hook into it from the config. These don't seem to work. https://shadow-cljs.github.io/docs/UsersGuide.html#build-hooks

thheller10:09:45

what do you mean by worker compilation happened? I though you used :modules?

bbss10:09:21

Yes, I mean when I change the worker cljs the build recompiles it.

thheller10:09:54

yes as I said .. no live-reload happens IN the worker itself

bbss10:09:14

I don't mind that. But the file that loads the worker needs to reload it.

thheller10:09:31

yes you do that via :dev/after-load

bbss10:09:53

So now I have to change the worker, and the file that requires it. No biggie, but would be nice if changing the worker triggered the reload on namespace that requires the worker.

thheller10:09:55

(defn ^:dev/after-load restart-workers [] (.terminate the-worker) (start-the-worker))

thheller10:09:11

in the namespace that manages the worker

thheller10:09:15

not the code of the worker itself

bbss10:09:43

that after-load won't be called if I just change the worker.

thheller10:09:48

yes it will?

bbss10:09:00

Hmm, let me re-check.

thheller10:09:47

remember NOT in the worker file itself

bbss10:09:44

Yes, changing the worker file, saving, triggers a recompilation. But neither the :after-load from :devtools nor the annotated function from the namespace that requires the worker gets called.

bbss10:09:52

Unless I change the file that requires the worker.

thheller10:09:23

after-load is completely independent of actual compilation

thheller10:09:58

open the browser console and post the log please

thheller10:09:36

oh wait haha I think it might just "optimize" the restart cycle away completely

thheller10:09:48

since non of the main-build code changed there is nothing to reload

thheller10:09:54

thus it is skipped completely

bbss10:09:27

Yes, that could be it. The

shadow-cljs: call gsv.client/start
browser.cljs:25 shadow-cljs: call gsv.client/recalc
util.cljs:187 Installing CLJS DevTools 0.9.10 and enabling features :formatters :hints :async
Don't happen when I change the worker code.

thheller10:09:45

hehe yep the reload gets optimized out

thheller10:09:49

the browser main doesn't have the worker code loaded (obviously) so it decides that it doesn't need to reload any code

thheller10:09:58

and skips calling the lifecycle fns as well

bbss10:09:33

That makes sense, I am quite happy that it loads "just what changed" usually 🙂

bbss11:09:56

Anyway it's not a big deal to just change both files so don't worry about solving it quickly or so.

thheller11:09:06

hmm I could always trigger the lifecycle but I doubt that you usually want that

thheller11:09:38

you could annotate some namespace with :dev/always so that some file of the main thread always gets reloaded

thheller11:09:43

but that seems rather dirty

bbss11:09:14

Hmm that could work, when developing I usually reload that namespace anyway.

thheller11:09:37

yeah but dependents also get reloaded. its a bit messy

thheller11:09:53

you could add an empty preload I guess

thheller11:09:05

(ns ^:dev/always some.reload-helper)

thheller11:09:17

:preloads [some.reload-helper]

thheller11:09:36

so nobody depends on that ns but it always gets reloaded to trigger the lifecycle fns

thheller11:09:43

super ugly hack but should work

thheller11:09:07

I need to review some of the webworker stuff to figure out how to deal with this properly

bbss11:09:14

Yep! That works and will do fine for now I think. :thumbsup:

bbss11:09:16

Thank you.

ClashTheBunny20:09:39

Has anybody gotten the latest posh working with shadow-cljs?

-- Syntax error -------------------
  (... ... ... (... [posh.plugin-base :as base :include-macros] ... ... ...))
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
has extra input
------ ERROR -------------------------------------------------------------------
 File: jar:file:/tank/ranmason/.m2/repository/posh/posh/0.5.6/posh-0.5.6.jar!/posh/reagent.cljs:1:1
--------------------------------------------------------------------------------
   1 | (ns posh.reagent
-------^------------------------------------------------------------------------
Invalid namespace declaration
-- Spec failed --------------------
  (... ... ... (:require ... ... ... ...))
                ^^^^^^^^
should be one of: :import, :import-macros, :refer-clojure, :require-macros, :use, :use-macros

thheller20:09:19

this is an invalid ns form

thheller20:09:53

shadow-cljs validates more strictly than the default so the default just sort of works accidentally

thheller20:09:14

should be :include-macros true or better yet a :require-macros in the posh.plugin-base itself