Fork me on GitHub
#shadow-cljs
<
2022-04-28
>
thheller04:04:53

@santosx99xx that is a Clojure error. So you are connected to a clojure REPL. you first need to switch to the CLJS REPL from that CLJ REPL. dunno what editor you use https://shadow-cljs.github.io/docs/UsersGuide.html#_editor_integration

fullNameHere05:04:06

I'm using vs code and calva

wombawomba13:04:58

So I've built a nice little in-browser REPL using cljs_eval for my app, and I'm trying to add autocompletion to it. Is there a way to get a list of loaded namespaces, either via the standard library (I tried https://cljs.github.io/api/cljs.analyzer.api/all-ns but it doesn't seem to work) or shadow-cljs?

thheller07:04:45

can't really do this on top of cljs-eval. really need access to the compiler env which is on the clojure side.

wombawomba07:04:38

There's no way to smuggle in a clojure command from the browser?

wombawomba07:04:20

I'm okay with finding a hacky approach to this; doesn't need to go through cljs_eval

thheller08:04:13

well the common tools people use for autocomplete and such all run over nrepl

thheller08:04:38

there is no direct way to gain access to nrepl from the browser though

wombawomba08:04:52

Maybe there should be? ;)

thheller08:04:39

I don't like nrepl at all so I would be building this kind of thing over shadow.remote

wombawomba08:04:44

You've been working on some really cool browser-based stuff with the shadow-cljs UI. It seems to me like you could come up with a lot of use-cases for something like that

thheller08:04:19

yeah, I do have plans for all of that. just no time to do it all 😛

wombawomba08:04:43

Haha I can imagine

thheller08:04:45

the UI also uses shadow.remote for everything, everything it uses you also have access to

thheller08:04:15

but yeah totally undocumented and might be changing a lot still 😛

wombawomba08:04:13

Cool! Just read the rationale for shadow.remote and I think it sounds great

wombawomba08:04:42

I don't really care if it's undocumented/alpha

wombawomba08:04:10

If i wanted to use it to do stuff like this, how would I get started? :)

thheller08:04:20

so cljs-eval already uses it too 😉

thheller08:04:21

so you would add a namespace to you build loaded via :preloads

thheller08:04:01

so (add-plugin! ::foo #{} init-fn stop-fn)

thheller08:04:31

init-fn gets one parameter being the environment, including the runtime and all the dependencies

thheller08:04:41

the runtime you can use to receive and send messages

thheller08:04:02

I mean it gets very involved very quickly but the REPL is implemented on top of all of this

thheller08:04:19

basically you add the plugin, it on init then registers extensions

thheller08:04:31

and then they talk over the shadow.remote socket

thheller08:04:35

but yeah not documented anything here and the CLJ side is currently not using this plugin mechanism

thheller08:04:40

so extending that side is harder

wombawomba08:04:43

so if I wanted to add a plugin that (as a first step) lets me list all loaded namespaces, how would I set it up?

thheller08:04:54

the worker side on the server implements everything using a multi method

wombawomba08:04:00

from what you wrote earlier I assume it has to deal with clj?

thheller08:04:10

and the worker you'd need to talk to to get autocomplete and stuff

wombawomba08:04:10

does all the cljs code you linked run in the browser? or is there a cljs part on the backend too?

thheller08:04:51

there is no cljs part in the backend no

wombawomba08:04:14

I think this should be enough to get me started

wombawomba08:04:33

are there any specs or similar that describe the messages being passed around here?

thheller08:04:04

you probably shouldn't use any of the existing messages so no

thheller08:04:43

basically you would (defmethod impl/do-relay-msg ::my-auto-complete [worker-state msg] ...) somewhere in your code on the CLJ side

wombawomba08:04:59

alright, so my messages can be arbitrary data?

thheller08:04:25

yes, just clojure maps

thheller08:04:35

the two mandatory fields you need are these

wombawomba08:04:38

what about e.g. the stuff that gets passed to the add-plugin fns?

thheller08:04:45

so an :op you choose and the :to

wombawomba08:04:49

any specs for that?

thheller08:04:21

:to tells it to direct the messages to the worker, which can then answer via the multi-method

thheller08:04:03

the multi-method must return the worker-state it gets. in the worker-state there is a :build-state which has :compiler-env

thheller08:04:26

which has the :cljs.analyzer/namespaces you'd want to look at for autocomplete

wombawomba08:04:37

hey also, do you think it would be possible to use this approach to get require working from my browser-based repl?

thheller08:04:36

(add-plugin! ::plugin #{}
  (fn [{:keys [runtime] :as env}]
    (api/add-extension runtime ::auto-complete {})

    (shared/call runtime
      {:op ::complete-me
       :to env/worker-client-id
       :foo "bar"}
      
      {::complete-result
       (fn [msg]
         (js/console.log "result from worker" msg))})

    env)

  (fn [{:keys [runtime]}]
    (api/del-extension runtime ::auto-complete)))

thheller08:04:47

basic could look something like this on the client side

wombawomba08:04:07

alright, thanks

wombawomba08:04:12

seems straightforward enough 🙂

thheller08:04:47

(defmethod impl/do-relay-msg ::complete-me
  [worker-state {:keys [foo] :as msg}]
  
  (impl/relay-msg worker-state msg
    {:op :complete-result
     :yo []})

  worker-state)

thheller08:04:51

server side

thheller08:04:20

note that you need to add the multi-method implementations before starting the worker

thheller08:04:31

otherwise it may not pick them up properly

thheller08:04:51

what do you mean get require working? cljs-eval can require just fine?

thheller08:04:07

but yeah, given that the entire REPL is based on this you can do whatever too

wombawomba08:04:30

I had some problems with require earlier, but it might just have been an error on my part

wombawomba08:04:55

I'll take a closer look at it

wombawomba08:04:18

anyway thanks; looking forward to testing this out 🙂

pinkfrog14:04:58

I develop in calva, and the jack in command of calva with launch shadow with the command like this

npx shadow watch :app
The issue is, whenever I run the vscommand Developer reload window. And after the new vscode window show up, and then if I perform a jack in, it will say
shadow port already taken
I suspect this might be related to the shadow server mode. In all, is it possible to tell shadow to quite when vscode quits (to be exact, window reloads) ?

pez15:04:51

I think these are Calva issues. Please feel invited to post in #calva about it.

pez15:04:08

Oh, the reason I don't just help you here is that I am short on time. But hopefully in the #calva channel someone else can help.

pinkfrog15:04:05

Thanks. I will repost this in #calva.

sheluchin15:04:26

I don't understand it, but as long as I have :extra-paths ["src/dev"], and there's an empty (ns user) in there, it builds. Otherwise it gives me an exception.

:shadow-cli {:extra-paths ["src/dev" "resources"]
                        :extra-deps
                        {thheller/shadow-cljs {:mvn/version "2.16.12"}
                         binaryage/devtools {:mvn/version "1.0.0"}}
                        :main-opts ["-m" "shadow.cljs.devtools.cli"]},
$ tree src/dev/
src/dev/
└── user.clj

0 directories, 1 file

$ cat src/dev/user.clj
(ns user)
I don't want dev on my classpath at all for release, so I shouldn't need it as an :extra-path.

thheller17:04:06

@alex.sheluchin what exception would that be? I assume you just have another user.clj on some other :paths. by just adding src/dev you are effectively overwriting that one

sheluchin17:04:12

$ find . -iname "user.clj"
./src/dev/user.clj

thheller17:04:51

the find doesn't mean anything since I don't know your full classpath setup

thheller17:04:00

but none of the above is related to shadow-cljs in any way

thheller17:04:34

just try running clj -A:shadow-cljs

thheller17:04:40

I assume that gives you the same errors?

thheller17:04:18

if you try (enumeration-seq (.getResources (.getContextClassLoader (Thread/currentThread)) "user.clj"))

sheluchin17:04:44

I assume you mean clj -A:shadow-cli because that's the name of the alias I have. Nope, it doesn't give me errors.

sheluchin17:04:07

(#object[java.net.URL 0x2e01e8ae "file:/home/alex/repos/personal-projects/example/src/dev/user.clj"])

thheller17:04:27

and without this file?

thheller17:04:35

or just without src/dev declared?

sheluchin17:04:10

If I don't include :extra-paths ["src/dev"], that's when I get the above exception.

thheller17:04:04

and you are running this all directly from the command line directly AND manually

thheller17:04:10

no editor involved of any kind? or any other tool supposed to "help out"

thheller17:04:15

I mean something must trigger the loading of all that stuff in the exception.

thheller17:04:29

there seems to be a socket server involved in some way?

sheluchin17:04:50

I am running it from my command line, no editor. When you gave me that snippet to look for user.clj, I used clj -A:shadow-cli clj-repl to evaluate it.

thheller17:04:16

that already does too much

thheller17:04:27

I meant literally clj -A:shadow-cljs

thheller17:04:40

maybe need to comment out the :main-opts in deps.edn

thheller17:04:57

just trying to narrow down on when and how the error happens

thheller17:04:14

might also be related to core.async in some way. or some AOT classes you have somewhere

sheluchin17:04:23

You do mean :shadow-cli as I have it in my shadow-cljs.edn, yeah?

thheller17:04:26

I'm only guessing since I can't see your full setup

thheller17:04:01

I assume you mean deps.edn. yes, that one

thheller17:04:17

I mean you can just go with clj. that should be fine as well

thheller17:04:30

trying to get you to a blank clojure REPL that doesn't crash on startup somehow

thheller17:04:50

do you have other :main-opts or :jvm-opts configured in your deps.edn?

sheluchin17:04:04

Not at the top level, only under aliases that I'm not activating.

thheller17:04:11

so what happens with just clj or clj -A:dev

sheluchin17:04:11

Same exception. Unless I add src/dev.

sheluchin17:04:35

Should I take this to #clojure instead? I guess this rules out shadow.

thheller18:04:11

do you have anything in ~/.clojure/deps.edn?

thheller18:04:00

or I guess you can try clj -Srepro -A:dev to rule that out?

thheller18:04:28

I mean your entire setup is full of red flags for me but should still be possible to figure out exactly what is causing your issues

sheluchin18:04:24

I've been making do with it for local dev, but now that I'm looking at deployment stuff is indeed screaming at me.

sheluchin18:04:03

Yes, I do have some stuff in my global deps:

{:aliases
 {:cider-clj
  {:extra-deps {org.clojure/clojure {:mvn/version "1.10.1"}
                cider/cider-nrepl {:mvn/version "0.21.1"}}
                ; fipp {:mvn/version "0.6.23"}}
   :main-opts ["-m" "nrepl.cmdline"
               "--middleware" "[cider.nrepl/cider-middleware]"]}
  :cljs
  {:extra-deps {nrepl/nrepl {:mvn/version "0.7.0"}
                cider/piggieback {:mvn/version "0.4.1"}}}
                ; fipp {:mvn/version "0.6.23"}}}
  :nREPL
  {:extra-deps {nrepl/nrepl {:mvn/version "0.8.1"}}}
  :repl/reveal {:extra-deps {vlaaad/reveal {:mvn/version "1.3.270"}
                             lambdaisland/deep-diff2 {:mvn/version "2.0.108"}}
                :ns-default vlaaad.reveal
                ;; note Fira Code is a ligature font so it's not supported in Reveal, whjch only uses monospaced
                :jvm-opts   ["-Dvlaaad.reveal.prefs={:theme,:light,:font-family,\"Fira Code\",:font-size,8}"]
                :repl-options {:nrepl-middleware [vlaaad.reveal.nrepl/middleware]}
                :exec-fn repl}}}

thheller18:04:28

as long as its still is aliases that aren't activated that is fine

thheller18:04:32

did you at some point experiments with AOT maybe? are there a lot of .class files in your project somewhere?

sheluchin18:04:23

Not that I am aware of. I can't find any .class files from my root and I haven't knowingly used any AOT stuff. Should I try just blowing away my .m2 and pulling everything anew?

thheller18:04:47

check which core.async` version you end up with just in case

thheller18:04:20

clj -A:dev -Stree or so

thheller18:04:51

nevermind I'm seeing things. those are spec issues not core.async

thheller18:04:27

I mean yeah killing your .m2 might help in some way

sheluchin18:04:43

I have a largely unfounded suspicion this is somehow related to https://github.com/fulcrologic/guardrails, but don't really have any evidence to back up the hunch.

thheller18:04:53

could be, but finding how it gets loaded would help

sheluchin18:04:09

Let me kill .m2.

sheluchin18:04:27

Sorry for the noob troubles being so time consuming here @U05224H0W. Thank you for helping out.

sheluchin18:04:55

I think it worked...

sheluchin18:04:24

I have no idea how I could have broken anything in the Maven cache. I guess I'll just get in the habit it nuking it when things stop working smoothly.

sheluchin18:04:37

Is there some way I could verify the output to make sure it built correctly?

p-himik20:04:47

Huh. Moved some JS files from <script>s in index.html inside the compiled bundle, and now I can't build my project on Heroku where there's a restriction on memory. The stack trace doesn't mention shadow-cljs, it even looks like it's been trimmed for some reason because the entry point doesn't make sense.

p-himik20:04:51

{:clojure.main/message
 "Execution error (OutOfMemoryError) at java.util.Arrays/copyOf (Arrays.java:3537).\nJava heap space\n",
 :clojure.main/triage
 {:clojure.error/class java.lang.OutOfMemoryError,
  :clojure.error/line 3537,
  :clojure.error/cause "Java heap space",
  :clojure.error/symbol java.util.Arrays/copyOf,
  :clojure.error/source "Arrays.java",
  :clojure.error/phase :execution},
 :clojure.main/trace
 {:via
  [{:type java.lang.OutOfMemoryError,
    :message "Java heap space",
    :at [java.util.Arrays copyOf "Arrays.java" 3537]}],
  :trace
  [[java.util.Arrays copyOf "Arrays.java" 3537]
   [java.lang.AbstractStringBuilder ensureCapacityInternal "AbstractStringBuilder.java" 228]
   [java.lang.AbstractStringBuilder append "AbstractStringBuilder.java" 682]
   [java.lang.StringBuffer append "StringBuffer.java" 393]
   [java.io.StringWriter write "StringWriter.java" 122]
   [java.io.PrintWriter write "PrintWriter.java" 541]
   [java.io.PrintWriter write "PrintWriter.java" 558]
   [java.io.PrintWriter print "PrintWriter.java" 685]
   [clojure.data.json$write_string invokeStatic "json.clj" 317]
   [clojure.data.json$write_string invoke "json.clj" 290]
   [clojure.data.json$fn__262$G__257__269 invoke "json.clj" 286]
   [clojure.data.json$write_array invokeStatic "json.clj" 348]
   [clojure.data.json$write_array invoke "json.clj" 342]
   [clojure.data.json$fn__262$G__257__269 invoke "json.clj" 286]
   [clojure.data.json$write_object invokeStatic "json.clj" 335]
   [clojure.data.json$write_object invoke "json.clj" 319]
   [clojure.data.json$fn__262$G__257__269 invoke "json.clj" 286]
   [clojure.data.json$write_object invokeStatic "json.clj" 335]
   [clojure.data.json$write_object invoke "json.clj" 319]
   [clojure.data.json$fn__262$G__257__269 invoke "json.clj" 286]
   [clojure.data.json$write_array invokeStatic "json.clj" 348]
   [clojure.data.json$write_array invoke "json.clj" 342]
   [clojure.data.json$fn__262$G__257__269 invoke "json.clj" 286]
   [clojure.data.json$write_object invokeStatic "json.clj" 335]
   [clojure.data.json$write_object invoke "json.clj" 319]
   [clojure.data.json$fn__262$G__257__269 invoke "json.clj" 286]
   [clojure.data.json$write invokeStatic "json.clj" 475]
   [clojure.data.json$write doInvoke "json.clj" 424]
   [clojure.lang.RestFn invoke "RestFn.java" 425]
   [clojure.lang.AFn applyToHelper "AFn.java" 156]
   [clojure.lang.RestFn applyTo "RestFn.java" 132]
   [clojure.core$apply invokeStatic "core.clj" 671]],
  :cause "Java heap space"}}

p-himik20:04:10

There are some places in shadow-cljs where JSON writing could easily be made more memory efficient, like e.g. in this code:

(spit target
  (json/write-str sm-index))
But given the stacktrace, it's completely unclear where exactly the right place for an improvement would be...

thheller05:04:05

just set something like :jvm-opts ["-Xmx2G"] when building

thheller05:04:52

which java version is this using? older JVM versions didn't detect container memory correctly, so they used the actual machine memory as a reference

thheller05:04:10

which often ends up consuming way more than allocated to the container image

thheller05:04:48

if you use a newer jvm version that usually sorts itself out too, older require manual settings

thheller05:04:10

fine to leave this in always, shadow-cljs doesn't really need that much memory. the default jvm settings are a bit greedy

p-himik05:04:31

Yeah, that would require doubling my client's spending on Heroku because AFAIC you can't get a separate build dyno - it all runs with the same limits and the current RAM limit is 1 GB, which is plenty for the app itself. JDK 17

thheller05:04:00

hmm then maybe your build is actually too large an breaks the build 😉

p-himik05:04:11

I'll probably switch to building an uberjar or a full-on container locally. It's been a long time coming.

thheller05:04:19

never ever inline json files by the way if they are above 20 bytes or something

thheller05:04:38

oh nvm it was the source map generation

p-himik05:04:51

Well, I do inline KBs worth of static data in other projects - where it really makes sense. :) But not here.

thheller05:04:57

yeah dunno. I've seen it do big builds with just one gig allocated

p-himik05:04:37

FWIW the resulting release bundle is 5 MB and the source map is 29 MB. No clue where it's at on the "big builds" scale.

thheller06:04:55

yeah release source maps can get a bit out of hand