Fork me on GitHub
#shadow-cljs
<
2022-10-19
>
mkvlr07:10:06

I'm running into ExceptionInfo: no goog/base.js with target :graaljs , full error in 🧵

mkvlr07:10:17

no goog/base.js
{}
ExceptionInfo: no goog/base.js
	shadow.build.output/closure-defines-and-base (output.clj:58)
	shadow.build.output/closure-defines-and-base (output.clj:53)
	shadow.build.targets.graaljs/flush-dev (graaljs.clj:127)
	shadow.build.targets.graaljs/flush-dev (graaljs.clj:111)
	shadow.build.targets.graaljs/process (graaljs.clj:168)
	shadow.build.targets.graaljs/process (graaljs.clj:160)
	clojure.lang.Var.invoke (Var.java:384)
	shadow.build/process-stage/fn--26795 (build.clj:161)
	shadow.build/process-stage (build.clj:158)
	shadow.build/process-stage (build.clj:150)
	shadow.build/flush (build.clj:494)
	shadow.build/flush (build.clj:488)
	shadow.cljs.devtools.server.worker.impl/build-compile (impl.clj:364)
	shadow.cljs.devtools.server.worker.impl/build-compile (impl.clj:344)
	shadow.cljs.devtools.server.worker.impl/do-config-watch (impl.clj:563)
	shadow.cljs.devtools.server.worker.impl/do-config-watch (impl.clj:556)
	shadow.cljs.devtools.server.util/server-thread/fn--28186/fn--28187/fn--28195 (util.clj:269)
	shadow.cljs.devtools.server.util/server-thread/fn--28186/fn--28187 (util.clj:268)
	shadow.cljs.devtools.server.util/server-thread/fn--28186 (util.clj:241)
	java.lang.Thread.run (Thread.java:833)

mkvlr07:10:29

let me know if you want an issue or a minimal repro

thheller07:10:38

in what context? just a regular watch?

thheller07:10:17

any custom build-hooks by chance? IIRC this could happen if a build hook doesn't return the proper build state?

mkvlr07:10:28

yes regular watch, I do have a build hook, let me see…

mkvlr07:10:27

actually, no, I had one but dropped it a while back

thheller07:10:43

dunno what else then. this is fairly standard code touched by multiple targets (and working fine for me locally)

mkvlr07:10:14

anything else look wrong in the config?

{:deps {:aliases [:sci]}
 :dev-http {7778 {:roots ["public" "classpath:public"]}}
 :nrepl false
 :builds {:browser {:target :graaljs
                    :output-to "public/js/viewer.js"
                    :output-dir "public/js"
                    :build-options {:ns-aliases {nextjournal.devcards nextjournal.devcards-noop}}
                    :modules {:viewer {:entries [nextjournal.clerk.sci-viewer
                                                 nextjournal.clerk.static-app]}}

                    :dev {:compiler-options {:optimizations :simple}}
                    :release {:output-dir "build/"
                              :compiler-options {:source-map true}}

                    :js-options {:output-feature-set :es8}}}}

thheller07:10:00

:dev {:compiler-options {:optimizations :simple}} does absolutely nothing since dev never does any optimizations at all

thheller07:10:16

but also won't affect this issue in any way

mkvlr07:10:29

ah yeah, just added this, I meant release 🙃

thheller07:10:56

since optimizations are not applied in dev regardless of what you set, you can just put it in normally

thheller07:10:12

so just :compiler-options {:optimizations :simple} without nesting in :dev/`:release`

👍 1
thheller07:10:14

not really sure what could cause your issue besides a bad build hook

thheller07:10:18

which shadow-cljs version? haven't really touched any of this in years though

mkvlr07:10:41

let me try the very latest

thheller07:10:20

uhm hang on

thheller07:10:28

:target :graaljs didn't support :modules?

thheller07:10:43

didn't it just have :entries [...]?

mkvlr07:10:22

trying…

mkvlr07:10:32

YES, thank you!

mkvlr07:10:44

tiny thing I noticed, should :graaljs be added to this list?

Target ":node" for build :browser was not found. The built-in targets are:
  - :browser
  - :browser-test
  - :node-script
  - :node-library
  - :npm-module
  - :karma
  - :bootstrap

thheller07:10:09

well I don't know of anyone using it besides you 😛

thheller07:10:24

never made it an official target since its somewhat sketchy 😉

thheller07:10:40

IMHO graal isn't a viable JS runtime at this time

thheller07:10:16

well viable but rather limited

mkvlr08:10:08

how so? Think its quite powerful with the polyglot stuff

thheller08:10:10

but the data transfer is so limited (eg. no passing a CLJ map to CLJS or back)

thheller08:10:26

so might as well just use a regular remote RPC mechanism and launch a node process

mkvlr08:10:14

things can access the same memory, so pretty sure passing a map can be implemented if you'd like to avoid going through json

thheller08:10:31

you really can't, since CLJS calls entirely different functions on maps than CLJ

1
mkvlr08:10:25

can't you implement the map interfaces like ILookup etc on both sides?

thheller08:10:16

then you end up with "proxies" and not real maps. at which point it would be better to serialize transit back/forth to get actual real maps

mkvlr08:10:45

these are indistinguishable from real maps

mkvlr08:10:19

and if one performs better than the other depends on if you'll need all data in the map or just parts

thheller08:10:24

not really, can't assoc for example

thheller08:10:26

also last time I checked graaljs was horribly slow compared to node

thheller08:10:40

so even if you did all that the node solution with serialization is faster

mkvlr08:10:52

after jit warmup the difference to node becomes less

mkvlr08:10:03

same ballpark last time we checked

thheller08:10:55

true but even the startup time is enough for me to now want to use it 😛

sirwobin11:10:09

I'm creating a node-library target with two build targets, one for app and one for test. Test target has :autorun true set and has worked well when I execute npx shadow-cljs compile test the tests run fine. Some new code I'm testing relies heavily on promises and I'm using https://github.com/funcool/promesa in my library and test code. Test assertions inside a promesa.core/let aren't run. I tried moving the deftest inside a promesa.core/let but that hasn't worked either and the test count went down, leading me to believe that the test runner has completely lost sight of the suite. Have I missed something or should tests with promises be conducted in a special way?

sirwobin11:10:59

In the shadow-cljs user guide promises are only mentioned twice. Nothing in connection with testing.

thheller11:10:14

do you use clojure.test/async correctly? async tests need to be marked as such. otherwise the runner won't know about them and won't wait for their completion

sirwobin11:10:14

ah, pretty sure I'm not since I haven't met clojure.test/async. Going to investigate now.

sirwobin12:10:12

Made the necessary changes and my assertions are running. Thanks for the pointer! 🙂

👍 1
Lone Ranger20:10:05

Two questions about module splitting / dynamic loading: 1. Is this sort of the intended usage for shadow.loader?

(ns example.core
  (:require [shadow.loader :as loader]))

(defn load-ui! []
  (-> (loader/load "ui")
      (.then #(example.other/mount-root))))

(defn ^:export ^:dev/after-load init []
  (load-ui!))
where I'm using example.other/mount-root as it I had :required it at the top level? It seems to work, just making sure that's the intention. (and if so -- any editor sympathetic tips for autocomplete, etc?) 2. I'm trying to figure out what the hell an ESM environment is, e.g., https://shadow-cljs.github.io/docs/UsersGuide.html#target-esm, so that I can use this sweet trick: https://shadow-cljs.github.io/docs/UsersGuide.html#_dynamic_module_import. But when I do
(shadow.esm/dynamic-import anything)
I get shadow_esm_import is not defined

thheller20:10:20

1) no this is not the intended use case. loading a module directly on startup is worse than having the code part of the "base" module directly

Lone Ranger20:10:46

yeah I get that. I meant syntactically/mechanically -- once the .then in the promise triggers, the namespace is now available to be used as if it were :required? I agree it's not the smartest way to split code.

thheller20:10:46

makes things a little more usable

thheller20:10:10

also covers how to use stuff after its loaded

thheller20:10:21

#(example.other/mount-root) technically works but not great

Lone Ranger20:10:29

Interesting. You're not using shadow.loader at all in that example

thheller20:10:42

because its API is annoying to use and just a direct mirror of cljs.loader for compatibility reasons

1
Lone Ranger20:10:46

I thought it was pretty good!!

thheller20:10:50

2) ESM is ecmascript modules, a modern variant of JS. shadow.esm only works in :target :esm builds, not in :target :browser

Lone Ranger20:10:42

Okay #3: When splitting code across modules, any insight into how state is shared? For instance, let's imagine

(ns example.db 
   (:require [reagent.core :as reagent]))
(defonce app-db (reagent/atom {:data 0}))
(ns example.a 
  (:require shadow.loader))

(-> (shadow.loader/load "db") (.then #(swap! example.db/app-db update :data inc)))
(ns example.b 
  (:require shadow.loader))

(-> (shadow.loader/load "db") (.then #(swap! example.db/app-db update :data inc)))
(ns example.ui
  (:require [example.db :as db]
            reagent.ratom))

(defn show-me []
  (let [number (reagent.ratom/make-reaction #(get @db/app-db :data))]
     (fn [] 
       [:div (str "Your number is: " @number)])))
I'm trying to imagine if show-me, when rendered, would show 0, 1, or 2 as the number

Lone Ranger20:10:37

assuming that a and b were loaded and run

thheller20:10:09

db would not be its own module, so a+b would directly depend on it

thheller20:10:39

and somewhere you'd trigger the load of a/b which would then increment the number

Lone Ranger20:10:41

Ok. So if I'm not mistaken that implies that that modules do not share state, and that state would need to be communicated through modules via some other method, almost as if there were independent workers

thheller20:10:31

everything runs in the same "scope". so they share all memory and are NOT independent such as workers

thheller20:10:05

in your example the example.db would be part of the base module

thheller20:10:17

a/b would be independent modules, loaded on demand when needed

thheller20:10:03

(ns example.a
  (:require
    [example.db :as db]))

(swap! db/app-db update :data inc)

thheller20:10:06

or whatever

thheller20:10:38

and something would load this via loader or shadow.lazy

Lone Ranger20:10:43

Ok. I guess I'm trying to figure out a pattern for making things such as a reagent/atom app-db and possible derived reagent.ratom/make-reactions separate from things like the UI and application logic. And I'm struggling to understand how, for instance, if the DB, the make-reactions, the UI, and the domain logic were all in separate modules, how could an on-click on one of the UI components update the app-db state if it were in a separate module -- and then how would the make-reactions pick up the reactive changes from the app-db, and then how would that get communicated back to the UI components. Maybe tl;dr I'm trying to figure out what sort of things MUST be directly coupled and which things can be indirect.

kennytilton11:10:47

Wow, that is a real eye-opener. I am always late to the party, in this case thinking only about massive CPUs and GPUs running Web browsers vs mobile. When mobile is taking over the world! Plz help a dummy: is this another argument for the Flutter/Dart platform, because it eschews JS? Or should those apps likewise sweat over chopping up functionality so it loads JIT? :thinking_face:

thheller17:10:11

apps don't really have the issue of trying to keep code size small, so its entirely different area

Lone Ranger20:10:23

Haha ok my bad, I'll just go off of that

Lone Ranger20:10:35

Sorry for all the q's, just really excited about all the new possibilities

Lone Ranger20:10:43

I've been writing massive monolithic apps forever

thheller20:10:51

the reagent atom parts do not change at all and won't be aware of module loading in any way

thheller20:10:27

the only part that changes is that something triggers the lazy load of some code on click or whatever

thheller20:10:10

there is only ONE app-db in the end, not one per module or whatever

Lone Ranger20:10:23

cool, yeah that makes sense (and keeps things easy)

thheller21:10:17

in the example code that would be your app-db

jplaza22:10:05

Hi everyone! I’m getting this error trying to use heroicons v2

Dependency: @heroicons/react/24/outline not provided by external JS. Do you maybe need a recompile?
Any ideas?

thheller22:10:32

looks like you are using :js-provider :external. did you recompile the :external-index file with whatever tool you use? (webpack?)

thheller22:10:52

and reloaded the page after doing so? that content is not hot-loaded in any way

Fredrik Andersson22:10:08

did you add the dependency to package.json? and installed it..

jplaza22:10:33

Thanks a lot for the help @thheller I’ve been banging my head with this for at least 45 minutes 😅 I didn’t setup this project and I’m not that familiar with shadow yet