Fork me on GitHub
#shadow-cljs
<
2022-12-09
>
Sam Ritchie17:12:44

hey all - for some reason I am not seeing a manifest.edn in my output directory. is there some way to ensure that itโ€™s produced?

thheller17:12:10

only :target :browser generates the manifest. the other targets don't need it

zalky18:12:53

Hi all, I'm trying to use code splitting to load debugging code wtihout having the production app burdened with the debug code. The way it is now, the debug module needs to be called after the re-frame subscription cache is cleared, but before the app is rendered to the dom.

(defn init!
  []
  (f/clear-subscription-cache!)
  (shadow.loader/load "debug")          ; Must happen here
  (some->> "container"
           (.getElementById js/document)
           (dom/render [ui/app])))
However, it seems that with the code above the debug module is actually being executed after the dom/render. I have the following in my shadow config:
{:module-loader true
 :modules       {:main  {:init-fn app/init!}
                 :debug {:depends-on #{:main}
                 :entries    [app.debug.loader]}}}
Are my expectations wrong that the module would be loaded synchronously at (shadow.loader/load "debug")?

thheller18:12:43

yes, it requires IO. so it can only load async.

thheller18:12:59

also don't do this

thheller18:12:31

make a second build with all the debug stuff included. that just loads everything

thheller18:12:14

something like

{:module-loader true
 :modules       {:main  {:entries [app.debug.loader]
                         :init-fn app/init!}}}

thheller18:12:48

don't ever use code-splitting to "remove" debug code

thheller18:12:06

just the presence of the debug code in the build will hinder :advanced and likely make your build much larger even in the :main module

zalky18:12:52

Thanks for the response! Hmm... at that point do I even need the module-loader? It would seems that there's no dynamic module loading with that approach?

thheller18:12:08

correct. not needed

zalky18:12:36

Ok, and just to be clear, you're suggesting I should duplicate app/init! one with the debug line and one without?

thheller18:12:53

{:modules {:main {:init-fn app/init!}}
 :dev {:modules {:main {:entries [app.debug.loader]}}}}

thheller18:12:58

you can also do this with just one build

zalky19:12:31

Apologies, I'm not sure I immediately parse the above map. Is it missing an :app build? How do you mean "one build". A little more detail on what I'm trying accomplish: I'm putting together a little OSS lib that has both domain features and debug features. In an ideal world, a user would use the domain features as normal, and where necessary include a preloads namespace for the debug module. However, the use of re-frame f/clear-subscription-cache! introduces imperative logic around instantiated subscriptions in the debug module. I can't just dump the debug module in a preload namespace because the debug module has to be loaded after the (f/clear-subscription-cache!), but ideally before the render. I'm not really sure I see how to accomplish this imperatively without dynamic module loading. I guess a solution where the user defines two builds, with duplicated init methods one with and without the debug loading could work. But I've been trying to identify the approach that would be easiest to use as a library, and I'm not sure I've found it yet.

folcon18:12:52

Has something changed about how shadow-cljs works? I've got it setup in my user.clj to require shadow in the form of [shadow.cljs.devtools.api :as shadow] so I can then call (shadow/watch :app) and (shadow/browser-repl). However just trying to start the repl blows up. Commenting out the require seems to work and then sending the form to my repl does this:

Connecting to local nREPL server...
Clojure 1.11.1
nREPL server started on port 63444 on host 127.0.0.1 - 
(ns user
  (:require [shadow.cljs.devtools.api :as shadow]))
Execution error (NoSuchMethodError) at com.google.javascript.jscomp.deps.ModuleLoader/createRootPaths (ModuleLoader.java:257).
'java.util.stream.Collector com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet(java.util.Comparator)'
I'm using [thheller/shadow-cljs "2.20.13"] via lein.

thheller18:12:36

nothing has changed, except that you introduce a dependency conflict. likely on guava

folcon18:12:48

To add more detail:

*e
=>
#error{:cause "'java.util.stream.Collector com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet(java.util.Comparator)'",
       :via [{:type clojure.lang.Compiler$CompilerException,
              :message "Syntax error macroexpanding at (closure.clj:77:5).",
              :data #:clojure.error{:phase :execution, :line 77, :column 5, :source "closure.clj"},
              :at [clojure.lang.Compiler$StaticMethodExpr eval "Compiler.java" 1750]}
             {:type java.lang.NoSuchMethodError,
              :message "'java.util.stream.Collector com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet(java.util.Comparator)'",
              :at [com.google.javascript.jscomp.deps.ModuleLoader createRootPaths "ModuleLoader.java" 257]}],
       :trace [[com.google.javascript.jscomp.deps.ModuleLoader createRootPaths "ModuleLoader.java" 257]
               [com.google.javascript.jscomp.deps.ModuleLoader <init> "ModuleLoader.java" 147]
               [com.google.javascript.jscomp.deps.ModuleLoader <init> "ModuleLoader.java" 48]
               [com.google.javascript.jscomp.deps.ModuleLoader$Builder build "ModuleLoader.java" 139]
               [com.google.javascript.jscomp.deps.ModuleLoader <clinit> "ModuleLoader.java" 408]
               [com.google.javascript.jscomp.DiagnosticGroups <clinit> "DiagnosticGroups.java" 182]
               [jdk.internal.misc.Unsafe ensureClassInitialized0 "Unsafe.java" -2]
               [jdk.internal.misc.Unsafe ensureClassInitialized "Unsafe.java" 1160]
               [jdk.internal.reflect.MethodHandleAccessorFactory
                ensureClassInitialized
                "MethodHandleAccessorFactory.java"
                300]
               [jdk.internal.reflect.MethodHandleAccessorFactory
                newMethodAccessor
                "MethodHandleAccessorFactory.java"
                71]
               [jdk.internal.reflect.ReflectionFactory newMethodAccessor "ReflectionFactory.java" 159]
               [java.lang.reflect.Method acquireMethodAccessor "Method.java" 720]
               [java.lang.reflect.Method invoke "Method.java" 575]
               [clojure.lang.Reflector invokeMatchingMethod "Reflector.java" 167]
               [clojure.lang.Compiler$StaticMethodExpr eval "Compiler.java" 1743]
               [clojure.lang.Compiler$InvokeExpr eval "Compiler.java" 3713]
               [clojure.lang.Compiler$DefExpr eval "Compiler.java" 457]
               [clojure.lang.Compiler eval "Compiler.java" 7199]
               [clojure.lang.Compiler load "Compiler.java" 7653]
               [clojure.lang.RT loadResourceScript "RT.java" 381]
               [clojure.lang.RT loadResourceScript "RT.java" 372]
               [clojure.lang.RT load "RT.java" 459]
               [clojure.lang.RT load "RT.java" 424]
               [clojure.core$load$fn__6908 invoke "core.clj" 6161]
               [clojure.core$load invokeStatic "core.clj" 6160]
               [clojure.core$load doInvoke "core.clj" 6144]
               [clojure.lang.RestFn invoke "RestFn.java" 408]
               [clojure.core$load_one invokeStatic "core.clj" 5933]
               [clojure.core$load_one invoke "core.clj" 5928]
               [clojure.core$load_lib$fn__6850 invoke "core.clj" 5975]
               [clojure.core$load_lib invokeStatic "core.clj" 5974]
               [clojure.core$load_lib doInvoke "core.clj" 5953]
               [clojure.lang.RestFn applyTo "RestFn.java" 142]
               [clojure.core$apply invokeStatic "core.clj" 669]
               [clojure.core$load_libs invokeStatic "core.clj" 6016]
               [clojure.core$load_libs doInvoke "core.clj" 6000]
               [clojure.lang.RestFn applyTo "RestFn.java" 137]
               [clojure.core$apply invokeStatic "core.clj" 669]
               [clojure.core$require invokeStatic "core.clj" 6038]
               [shadow.build.js_support$eval5552$loading__6789__auto____5553 invoke "js_support.clj" 1]
               [shadow.build.js_support$eval5552 invokeStatic "js_support.clj" 1]
               [shadow.build.js_support$eval5552 invoke "js_support.clj" 1]

folcon18:12:32

Oh? I'm not requiring guava myself, so there's a deps mismatch that I need to resolve?

folcon18:12:55

So I'll just deps tree and require the latest version? Or does shadow-cljs need a specific older version?

thheller18:12:45

shadow-cljs doesn't use it at all. the closure compiler does

thheller18:12:56

the most common problem is having datomic on the classpath as well

thheller18:12:05

which brings in an older incompatible version

thheller18:12:12

check the lein deps :tree output

thheller18:12:21

somewhere it'll tell you about a guava conflict

folcon18:12:27

Ah cool, ok it seems metosin is requiring [com.google.guava/guava "16.0.1"]

thheller18:12:46

uhm yeah that is waaay too old ๐Ÿ˜›

thheller18:12:56

31 something I think is the needed version

folcon18:12:06

Yep, trying [com.google.guava/guava "31.1-jre"]

folcon18:12:21

This is the one thing I do wish was a bit better, you can have stuff working perfectly well which then blows up when you're doing a deps build on a clean repo and you have no idea what's causing the failure...

folcon18:12:33

Ok, this is weird, now shadow is complaining that shadow-cljs has not been started yet?

(shadow/watch :app)
Execution error (ExceptionInfo) at shadow.cljs.devtools.server.runtime/get-instance! (runtime.clj:10).
shadow-cljs has not been started yet!
In embedded mode you need to call (shadow.cljs.devtools.server/start!) to start it.
If you have a shadow-cljs server or watch running then you are not connected to that process.
But I have it running in a separate terminal.

folcon18:12:35

Same folder

folcon18:12:53

So it should be working off the same .shadow-cljs folder

thheller18:12:16

I dont understand the question

folcon18:12:08

Sorry, I'm calling (shadow.cljs.devtools.api/watch :app) Which normally just says the watch is running. So I can confirm that the server REPL is seeing the same stuff that the watch command I'm running via npx shadow-cljs watch app is seeing.

thheller18:12:13

the problem is self inflicted by mixing in your CLJ dependencies

thheller18:12:26

if you just use shadow-cljs.edn with only your CLJS deps this won't happen

thheller18:12:37

yes, I understand the watch part

thheller18:12:57

but if you get shadow-cljs has not been started yet! you are not connected to a running shadow-cljs

thheller18:12:01

so maybe you started a lein REPL

thheller18:12:18

in which case you need to start shadow-cljs in that REPL first

folcon18:12:30

So my process, which I normally use is based on: 1. Run npx shadow-cljs watch app 2. Start the lein repl via cursive and require [shadow.cljs.devtools.api :as shadow] 3. Confirm the connection via (shadow/watch :app) 4. Start a browser repl with (shadow/browser-repl) The problem is that if I don't do it this way, I don't get clojurescript symbol import. Checking the embedded link you shared

thheller18:12:12

ok, so you start two separate JVMs

thheller18:12:24

just skip the npx shadow-cljs watch app

thheller18:12:36

and run the start! described in the docs

folcon18:12:52

Yep, which has worked, as it keeps clojurescript errors contained in one location (the terminal)

folcon18:12:05

Ok, so that's the way to do this now?

thheller18:12:29

there are a million ways to do this and none of this has changed since it was released

thheller18:12:35

it is all up to you and how you want to use it

folcon19:12:07

That's fair, I suppose I'm trying to work out why this is no longer able to detect the running shadow-cljs instance whereas previously it could.

thheller19:12:18

if you run npx shadow-cljs watch app you don't need an extra (shadow/watch :app) since that is redundant

folcon19:12:51

Oh sure, but in this case it confirms that the clojure server can also see the same watcher.

thheller19:12:08

it all depends on which JVM you are connected to

folcon19:12:17

As I think it emits an already-started keyword or something

thheller19:12:19

if you connect to the actual shadow-cljs instance everything is fine

thheller19:12:29

if you start a new JVM with a new REPL you get the not started error

folcon19:12:10

That's fair, I'll try a remote connect

folcon19:12:31

Huh, that's interesting:

(require '[shadow.cljs.devtools.server])
=> nil
(shadow.cljs.devtools.server/start!)
Execution error (ExceptionInfo) at shadow.cljs.devtools.server/check-for-other-instance! (server.clj:412).
shadow-cljs already running in project on . Use or terminate it before starting another one.
So it can detect it, but not connect to it?

folcon19:12:59

Hmm, ok, running it directly in the repl doesn't quite work:

=> :shadow.cljs.devtools.server/started
(shadow/watch :app)
[:app] Configuring build.
[:app] Compiling ...
[:app] Build failure:
IllegalArgumentException: No matching method legacySetOutputFeatureSet found taking 1 args for class com.google.javascript.jscomp.CompilerOptions
	clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:127)
	clojure.lang.Reflector.invokeInstanceMethod (Reflector.java:102)
	shadow.build.closure/set-options (closure.clj:166)
	shadow.build.closure/convert-goog* (closure.clj:2298)
	shadow.build.closure/convert-goog (closure.clj:2445)
	shadow.build.closure/convert-goog (closure.clj:2396)
	shadow.build.compiler/maybe-closure-convert (compiler.clj:1241)
	shadow.build.compiler/compile-all (compiler.clj:1475)
	shadow.build.api/compile-sources (api.clj:253)
	shadow.build/compile (build.clj:493)
	shadow.cljs.devtools.server.worker.impl/build-compile (impl.clj:360)
	shadow.cljs.devtools.server.worker.impl/eval16926/fn--16928 (impl.clj:445)
	clojure.lang.MultiFn.invoke (MultiFn.java:234)
	shadow.cljs.devtools.server.util/server-thread/fn--16684/fn--16685/fn--16693 (util.clj:283)
	shadow.cljs.devtools.server.util/server-thread/fn--16684/fn--16685 (util.clj:282)
	shadow.cljs.devtools.server.util/server-thread/fn--16684 (util.clj:255)
	java.lang.Thread.run (Thread.java:1589)
Thanks for your help =)... I'll try and see if I can re-engineer this to work again, the split workflow was helpful as I could keep the client-side / server side deps separate, whereas now I would need to co-mingle them. The biggest issue is no longer having easy access to the browser repl, though I can just use a remote repl connection to use that, so perhaps it's fine ๐Ÿ˜ƒ...

thheller19:12:20

this again is a dependency conflict

thheller19:12:24

this time on the closure compiler itself

thheller19:12:58

the CLJ parts never try to connect to another server

thheller19:12:03

the shadow-cljs command does

thheller19:12:41

that "shadow-cljs already running in project" is an error I added recently because people often unknowingly started two shadow-cljs instances which compete with each other

folcon19:12:29

Ah ok, it appears I had some working frankenbuild ๐Ÿ˜ƒ...

hifumi12321:12:14

Is it possible to have dependencies specific to a build in a pure shadow-cljs project? For context on my problem: the dev builds of my app make use of two devtools preloads. One depends on highlight.js and has it added as a normal dependency in my package.json file, instead of as a dev dependency. I want to avoid unnecessarily packing libraries into production builds

thheller21:12:35

@hifumi123 not required, if you don't require it it won't be included in the build. extra dependencies otherwise have no impact.

hifumi12321:12:35

Awesome. One more question, is it possible to get autocomplete for node modules?

hifumi12321:12:51

I'm able to get autocomplete for e.g js/document and of course ClojureScript libraries, but not node modules

thheller21:12:59

not something shadow-cljs provides generally. your editor would do that

hifumi12321:12:22

Hmm... I guess I will have to figure out how to get this set up with CIDER then. Thanks anyway!