Fork me on GitHub
#shadow-cljs
<
2022-04-06
>
oddsor11:04:40

Hello! I’ve been testing shadow-cljs and bumped into a similar issue reported by others with not being able to import css-modules (npm-libraries that basically just wrap css-files). To work around the issue I went with a solution suggested in this thread with creating a separate js-bundle that only contains the module containing css which is then prebuilt with webpack: https://clojurians.slack.com/archives/C6N245JGG/p1603638343132300 Is creating a separate bundle still close to the “best practise” for getting around this problem?

thheller11:04:48

hard to say. some css-modules are precompiled so the code has foo.css require but also a foo.css.js file exists. those should work fine

thheller11:04:57

if the .js files don't exist you only option is going with webpack yes

oddsor11:04:06

Cool, thanks! I was testing a design system that had react components and styles as separate libraries, so the css-one seems to only contain styles. So I went the “dual bundle”-route 😊

oddsor12:04:32

Sort of! Since I’m only hacking to improve my Clojurescripting at the moment I just used Webpack to build a js-file with only one import (the css) and reference that in index.html. I guess you could call it a bastardized version of option 2 🙈

thheller12:04:59

as long as it only is css thats fine

thheller12:04:16

if you add js you may end up duplicating stuff though

oddsor12:04:44

Right, I’ll go with option 2 for real if I get beyond the hacking stage with this. Thanks for the info!

👍 1
oddsor17:04:11

Whelp, I couldn’t stop thinking about this so I ended up doing it the “correct way” and it worked surprisingly well! Now I have shadow-cljs watching with

npx shadow-cljs watch app
and webpack watching with
npx webpack watch
Very cool! :thumbsup: One thing I did try to probe the limitations is to comment out and re-add a js-library, which rebuilds the target js-file and in turn causes webpack to rebuild the “lib-file”. I get the impression hot reloading doesn’t work in that case? Or is there some magic that can be applied to fix even that scenario?

oddsor17:04:55

Oh well, I’m mostly wondering for sake of curiosity, but since the js-libraries won’t change often this works damn great 👏

thheller18:04:59

yeah, cannot hot reload the webpack output

1
wombawomba12:04:06

I'm using lazy loading for Reagent components as described in https://code.thheller.com/blog/shadow-cljs/2019/03/03/code-splitting-clojurescript.html, and for some reason it works in Firefox but not in Chromium. My code is as follows:

(ns website.my-component)

(defn <my-component>
  []
  [:div "Hello world!"])

(ns website.core
  (:require ["react" :as react]
            [shadow.lazy :as lazy]
            [reagent.core]
            [reagent.dom]))

(def *<my-component>
  (lazy/loadable website.my-component/<my-component>))

(defn <app-root>
  []
  [:> react/Suspense {:fallback (reagent.core/as-element [:div "Loading..."])}
   [:>
    (if (lazy/ready? *<my-component>)
      (reagent.core/reactify-component (fn [& _] [@*<my-component>]))
      (react/lazy
       #(do (prn "loading step 1")
            (-> (lazy/load *<my-component>)
                (.then (fn [_<elem>]
                         (prn "loading step 2")
                         #js {:default (reagent.core/as-element
                                        (fn [& _]
                                          (prn "loading step 3")
                                          [@*<my-component>]))}))))))]])
(defn init-app!
  []
  (reagent.dom/render [<app-root>] (.getElementById js/document "app")))
Firefox prints both "loading step n" statements, whereas Chromium only prints "loading step 1". No errors are logged to the console in either case. Any idea what's wrong?

thheller12:04:50

much easier to answer with the full code

wombawomba12:04:17

@thheller I edited the code. Does that help?

thheller12:04:11

why not use the full setup from my example? actually using React.lazy I mean?

wombawomba12:04:23

hmm.. not sure I follow

thheller12:04:24

that needs to be a def. not something you recall on every render

thheller12:04:16

pulling it into the render function doesn't work since that recreates it every time never allowing react.lazy to do what it does

wombawomba12:04:06

perhaps it's getting garbage-collected in Chrome then?

thheller12:04:40

dunno. :default (reagent.core/as-element this also seems wrong

thheller12:04:48

did you check the browser console for errors?

wombawomba12:04:12

yeah like I wrote, no errors

wombawomba12:04:05

I'll try defing the react/lazy call and see if that resolves it

wombawomba13:04:22

@thheller I changed the code to the following, but it didn't help:

(ns website.my-component)

(defn <my-component>
  []
  [:div "Hello world!"])

(ns website.core
  (:require ["react" :as react]
            [shadow.lazy :as lazy]
            [reagent.core]
            [reagent.dom]))

(def *<my-component>
  (lazy/loadable website.my-component/<my-component>))

(def lazy-component
  (react/lazy
   #(do (prn "loading step 1")
        (-> (lazy/load *<my-component>)
            (.then (fn [_]
                     (prn "loading step 2")
                     #js {:default (reagent.core/reactify-component
                                    (fn [& _]
                                      (prn "loading step 3")
                                      [:div "hello"]))}))))))

(defn <app-root>
  []
  [:> react/Suspense {:fallback (reagent.core/as-element [:div "Loading..."])}
   [:> lazy-component]])

(defn init-app!
  []
  (reagent.dom/render [<app-root>] (.getElementById js/document "app")))

wombawomba13:04:30

It seems to me that the problem isn't really in this code, but somewhere else. In Firefox, the module JS gets loaded after the "loading step 1" printout, but in Chrome it's not loaded at all.

thheller13:04:14

why do you introduce all the extra stuff?

wombawomba13:04:27

sorry, what extra stuff?

thheller13:04:27

(lazy/ready? *<my-component>)?

thheller13:04:46

should be just [:> lazy-component] nothing else

wombawomba13:04:24

okay yeah sure

wombawomba13:04:45

the point of that was to avoid calling react/lazy on every render

wombawomba13:04:07

now that I moved react/lazy to a def I forgot to get rid of it

thheller13:04:14

the entire point of react/lazy is to call it on every render

wombawomba13:04:17

either way, I got rid of it now and it didn't make a difference

thheller13:04:39

it handles the if stuff for you. so you are just repeating it, which is redundant

thheller13:04:39

try to not use prn when debugging stuff

thheller13:04:58

just use (js/console.log ...) instead. async printing is icky if you expect output to show up in the REPL

wombawomba13:04:38

that doesn't make a difference in this case though

thheller13:04:59

this is also not just using [:div "hello"] instead of the actual lazy component?

wombawomba13:04:36

I figured I'd eliminate the possibility that there was something wrong when the code was being rendered

wombawomba13:04:28

to be clear, this shows up as an empty single-page div with "Loading..." in Chrome, and "hello" in Firefox

thheller13:04:12

but does it load the extra module with the component?

wombawomba13:04:57

it does not in Chrome

wombawomba13:04:03

in Firefox, it does

thheller13:04:30

dunno. if you want to setup a repo so I can check it out I can take a look

pinkfrog13:04:44

Out of a sudden I am seeing this error, what does it mean?

error: Error: Unable to resolve module  from /Users/matthew/Drive/dev/mobile2/target/app/index.js:  could not be found within the project or in these directories:
  node_modules
> 1 | var $CLJS = global;
    |                    ^
  2 | var shadow$start = new Date().getTime();
  3 | var shadow$provide = global.shadow$provide = {};
  4 | var goog = global.goog = {};

thheller13:04:35

that is not a shadow-cljs error? where/when do you get that?

pinkfrog13:04:18

inside metro

pinkfrog13:04:23

I am developing with react native.

pinkfrog13:04:54

What does the error say? Is the global object missing or what else?

thheller13:04:19

I don't know. as I said this is not a shadow-cljs error. without more context I cannot guess further.

pinkfrog13:04:50

It’s a react-native project. The output is from metro bundler

thheller13:04:14

yes, but under what circumstances? like what is the full log? what command did you run? what was the state of cljs compiliation at the time of that error

thheller13:04:33

the source code it shows looks weird since there is no module lookup happening

thheller13:04:41

what is the build config?

thheller13:04:05

maybe just try restarting metro. I really don't know. I don't use react-native so my guesses are very limited.

pinkfrog13:04:06

yeah. Thanks. I am gonna recreate the react project.

wombawomba14:04:18

@thheller FYI I managed to get it working by restarting my computer, cleaning my browser cache, and deleting .shadow-cljs and all build files

wombawomba14:04:36

not sure which one of those fixed it 🙂

wombawomba14:04:43

apologies for wasting your time

wombawomba14:04:27

(my hunch is that Chromium had somehow cached an old JS build file, that it wasn't letting go despite me hard refreshing the page)

thheller14:04:11

if you use the :dev-http server that can't happen since it sets the proper cache headers

thheller14:04:21

if you use any other webserver then sure that can happen (or a service worker)

wombawomba14:04:10

yeah, that's probably it

wombawomba14:04:21

I'll double check my cache headers

pinkfrog14:04:05

Every time if a namespace mismatches, shadow will get stuck and stops compiling even if after I fixed the naming issue:

pinkfrog14:04:31

I waited some more time and the build begins again.

pinkfrog14:04:37

Can I tune the waiting time?

thheller14:04:22

which shadow-cljs version? that issue should have been fixed like 2 years ago

pinkfrog14:04:26

{thheller/shadow-cljs$aot {:mvn/version “2.17.8”}

thheller15:04:36

that should be fine. it should recompile fine when you just fix the error?

pinkfrog16:04:00

What does SHADOW_ENV.evalLoad do?

pinkfrog16:04:42

Also, what does this line mean?

$CLJS.shadow$js["../../resources/assets/landing-bg.jpg"] = function() { return require("../../resources/assets/landing-bg.jpg"); };

pinkfrog16:04:25

Reason I asked this is, in my source code, if I replace the line

(defonce landing-bg (js/require (config/asset-path "landing-bg.jpg")))
with
(defonce landing-bg (js/require "../../resources/assets/landing-bg.jpg"))
Then the
$CLJS.shadow$js["../../resources/assets/landing-bg.jpg"] = function() { return require("../../resources/assets/landing-bg.jpg"); };
will be gone. In my code, config/asset-path is a macro. The output of (config/asset-path "landing-bg.jpg") is exactly “../../resources/assets/landing-bg.jpg”.

pinkfrog16:04:49

Further more background on this, the react native require only supports literal strings and dynamic variables are not supported, (see https://reactnative.dev/docs/images#:~:text=In%20order%20for%20this%20to%20work%2C%20the%20image%20name%20in%20require%20has%20to%20be%20known%20statically). So I think I might go with macro so that it will be expanded to literal strings.

pinkfrog16:04:33

However, the generated file missies the $CLJS.shadow$js["../../resources/assets/landing-bg.jpg"] = function() { return require("../../resources/assets/landing-bg.jpg"); }; line compared to the case of a vanilla (defonce landing-bg (js/require "../../resources/assets/landing-bg.jpg")) .

thheller17:04:55

js/require must be a single simple string. macros are not supported there.

pinkfrog00:04:01

Sounds like a great feature to support macros.

pinkfrog00:04:36

Shadow is already expanding the macro, for example,

(defonce landing-bg (js/require (config/asset-path "landing-bg.jpg")))
gets converted to
(defonce landing-bg (js/require "../../resources/assets/landing-bg.jpg"))
It’s just this piece is missing
$CLJS.shadow$js["../../resources/assets/landing-bg.jpg"] = function() { return require("../../resources/assets/landing-bg.jpg"); };

thheller05:04:19

I think (defasset landing-bg "landing-bg.jpg") is a macro you could write instead that just emits the (defonce landing-bg (js/require "../../resources/assets/landing-bg.jpg")) for you

thheller05:04:24

that should work I think

pinkfrog06:04:24

(defonce landing-bg (js/require (my-macro "landing-bg.jpg")))

pinkfrog06:04:33

What full support of the above?

thheller06:04:11

as I said this is not currently supported

thheller06:04:21

the compiler is looking for a call to js/require with a string argument. nothing else

thheller06:04:35

you have a js/require with a macro argument (this is before expansion)

thheller06:04:41

so thats why it doesn't currently work

thheller06:04:55

in theory it could be made to work but currently it doesnt

pinkfrog06:04:43

By compiler, what do you mean? Are you referring to shadow, or metro?

thheller06:04:54

the cljs compiler, with an extension part of shadow-cljs

pinkfrog06:04:44

I see, so you were saying the extra creation of this line

$CLJS.shadow$js["../../resources/assets/landing-bg.jpg"] = function() { return require("../../resources/assets/landing-bg.jpg"); };
is not possible in the current cljs/shadow compiler.

pinkfrog06:04:49

Which indeed is the case.

thheller06:04:25

the compiler is looking for EXACTLY a call to js/require with a string argument

thheller06:04:47

not possible currently yes. could be made to work yes.

pinkfrog06:04:51

Then what about the defasset macro?

thheller06:04:14

in theory that should be fine. can't guarantee and don't have time to try

thheller06:04:26

but that expands to a js/require call with a string argument

thheller06:04:31

so in theory that should work

pinkfrog06:04:34

I will have a try now.

pinkfrog07:04:00

That works. Thanks.

ag23:04:03

where can I find an offline version of the User Guide?

ag23:04:02

oh come on... you know what I meant.. 🙂 alright, I'll convert it myself

thheller00:04:18

docs/UsersGuide.html is the file

ag00:04:59

Oh wait a minute, there's shadow-cljs GH org? I did not know that

ag00:04:45

whoa, there's so much good stuff in there too