This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-06-14
Channels
- # biff (2)
- # calva (1)
- # clj-kondo (1)
- # clj-otel (34)
- # clojure (49)
- # clojure-europe (44)
- # clojure-nl (2)
- # clojure-norway (21)
- # clojure-sweden (11)
- # clojure-uk (6)
- # clojurescript (7)
- # data-science (3)
- # datahike (34)
- # emacs (17)
- # fulcro (1)
- # gratitude (5)
- # honeysql (1)
- # jobs (11)
- # leiningen (42)
- # lsp (5)
- # nextjournal (3)
- # observability (1)
- # practicalli (2)
- # shadow-cljs (153)
- # solo-full-stack (5)
- # squint (7)
- # xtdb (16)
I would like to generate some rendering functions in CLJS based on some templates defined in CLJC files, and would like to know what would be the best approach to do this using shadow-cljs.
I first tried to do it just using macros, it works when compiling the CLJC file in a CLJ env, but doesn't work in a CLJS env because it doesn't have the equivalent of clojure.core/resolve
which I am using to get a template's value from its var symbol.
Since my macros work when the CLJC are compiled in a CLJ env, I am wondering if there was a way to use that to generate some files from a shadow-cljs hook and have those files fed into the compiled CLJS program. Would it work? Is there an existing project which does that and from which I could learn?
and no, hooks should not generate files. if you already are in a CLJC file then just use CLJC
clojure.core/resolve
gives me the value of the symbol via (-> my-symbol resolve deref)
, cljs.analyzer.api/resolve
doesn't seem to do that.
yes, it returns a map instead of a var. what is it that you are trying to do exactly? usually you'd just want to get the resolved symbol no?
I have a few (defc my-component [,,,] ,,,)
in a CLJC file which I transform into (def my-component {:id 'my-ns/my-component, :ast ,,,})
using a macro.
I want to build a hashmap containing all the transitive components and their ast {'my-ns/my-component {:id ,,, :ast ,,,} ,,,other-dependent-components}
at compile time, that I give to a macro to generate some CLJ/CLJS code.
I have no problem getting the fully qualified symbol name, I have a problem getting the value associated to the var of that qualified symbol.
ok yes, that is not possible since during compilation the JS code is not actually executed, so runtime values obviously cannot be accessed
So my idea would be to extract that information by running the CLJC and macros in a CLJ env, then generate some CLJS files which ... somehow need to be included in the user's program, not sure if there is a way to do that using shadow-cljs.
yeah .. maybe running a separate program which is listening to changes on those CLJC files then generate the CLJS in the middle of the user's source directory.
it is possible to store analyzer data in a way that can be accessed and survives cache cycles
so you could store it there if you wanted, but that only works if its actual data, that transit can read/write
It's useful for inlining the AST from different components, and for an holistic code generation.
in it there is s :cljs.analyzer/namespaces
map which stores all analyzer data per namespace
so (get-in @cljs.env/*compiler* [:cljs.analyzer/namespaces 'your.ns ::whatever])
can be stored and accessed in macros
Thanks !
only the namespace that is currently being compiled may write the data as it is persisted after compiling that namespace
should something else attempt to modify data of other namespaces those changes may be lost, unless you turn caching off completely
and you should only store data off that namespace, so do not store the full AST (with data from other namespaces)
That should work in my case, I will push the AST data in cljs.env/*compiler*
from my defc
macro and then collect it all from the place where I want to process their aggregation.
I'm trying to explain that caching can break very very easily and will lead to very frustrating results debugging 😛

https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/resource.clj#L47-L49
oh and please use namespaced keywords to not conflict with actual analyzer data from the compiler
I did all that you said above and it works 🥳 .. with a small bug, but I understand why it happens.
When shadow-cljs is in watch mode, the place where I collect all the data stored in the analyzer's cache is not notified when one of the defc
component is changed by the user.
It happens because the cache mechanism stops the propagation of re-compilation to the namespaces which depend directly on the changed one, and if themselves don't change compared to before, then the re-compilation does not propagate further.
When I collect the data in the core.cljs
, I get all the transitive component dependencies starting from the root component that the user provides to my component-collecting macro used in core.clj
. But shadow-cljs doesn't know that core.cljs
now has a direct dependency to all of those transitive components' namespaces.
To fix my bug, I need to declare those dependencies in my component-collecting macro. Do you have an idea how I could do that?
Oh .. actually, each defc
component will need to list their transitive component dependencies if we want the incremental compilation to work.
Maybe listing those dependencies in an unused field will work, let me try ..
the compiler actually already tracks which vars a namespace used, but I guess in your case only the macro might be using stuff with no actual reference remaining in the CLJS code?
or maybe you are on an older shadow-cljs version that didn't do that? this was changed kinda recently
The defc macro only returns data containing quoted symbols, so shadow-cljs has no way to know about the dependency.
I use v2.28.8
is there an API in shadow-cljs for macros to record this type of dependency ?
API no. you technically do have access to it in build hooks, but I don't recommend you touching that
well .. that would be to fix the incremental compilation, which is shadow-cljs only
I will try to list the transitive dependencies as vars of qualified symbols. It should work.
It works 🥳
Thank you !!
Hello, I'm using this:
["expo-auth-session/providers/google" :as google]
and trying to use it here
(let [[request response promptAsync] (google/useIdTokenAuthRequest
#js {:clientId "***"
:androidClientId "*****"})]
but it's returning this ->
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
it seems that it's already in to body component, or is there other way to handle hooks?
If you're using Reagent, you need to render a function component using [:f> your-component-name]
. Examples here: https://github.com/reagent-project/reagent/blob/master/doc/ReactFeatures.md. Probably better to post in #clojurescript or #reagent too.
@U03QBKTVA0N Thank you, worked with :f>
why does this code crash shadow-cljs? does it happen to you?
user> (re-matches #"." nil)
Execution error (TypeError) at (:1).
re-matches must match against a string.
:repl/exception!
user> (map #(re-matches #"." %) (repeat 1000 nil))
(crashes...)
btw it's emacs/cider env.
INTERNAL REPL ERROR: Got an unexpected reply from relay, check Inspect[:unexpected-from-relay
{:op :unknown-op,
:msg
{:op :obj-as-str,
:oid "801cf7ae-f552-465b-b307-e434de415534",
:from 9},
:from 3}
{:ns user,
:runtime-id 3,
:stage :print,
:client-id 9,
:read-result
{:eof? false,
:source "(map #(re-matches #\".\" %) (repeat 1000 nil))"},
:eval-result
{:op :eval-result-ref,
:ref-oid "17d4949b-7c8a-4b39-afed-92d76dbdeab8",
:eval-ms 0,
:eval-ns user,
:warnings [],
:from 3},
:print-failed true}
{:system-bus
{:shadow.cljs.devtools.server.system-bus/service true,
:bus-mult-chan
#object[clojure.core.async.impl.channels.ManyToManyChannel 0xcaf4c05 "clojure.core.async.impl.channels.ManyToManyChannel@caf4c05"],
:bus-mult
#object[clojure.core.async$mult$reify__17794 0x5c147359 "clojure.core.async$mult$reify__17794@5c147359"],
:bus-pub-chan
#object[clojure.core.async.impl.channels.ManyToManyChannel 0x7aa04750 "clojure.core.async.impl.channels.ManyToManyChannel@7aa04750"],
:bus-pub
#object[clojure.core.async$pub$reify__18406 0x56ae920c "clojure.core.async$pub$reify__18406@56ae920c"]},
:proc-id "904845d4-f336-42d0-b449-4fee099d36df",
:fs-watch
{:shadow.cljs.devtools.server.fs-watch/service true,
:control
#object[clojure.core.async.impl.channels.ManyToManyChannel 0x2ac1b8d "clojure.core.async.impl.channels.ManyToManyChannel@2ac1b8d"],
:watch-dirs
[{:dir
#object[java.io.File 0x136b8bbc "....../resources/public"],
:watcher
#object[shadow.util.FileWatcher 0x800ac18 "shadow.util.FileWatcher@800ac18"]}],
:thread
#object[clojure.core.async.impl.channels.ManyToManyChannel 0x6ebc52eb "clojure.core.async.impl.channels.ManyToManyChannel@6ebc52eb"]},
:output-mult
#object[clojure.core.async$mult$reify__17794 0x4f3cad99 "clojure.core.async$mult$reify__17794@4f3cad99"],
:shadow.cljs.devtools.server.worker.impl/proc true,
:status-ref
#,
:state-ref
#
thanks for the report. its fixed. https://github.com/thheller/shadow-cljs/commit/3d81bff7b3fd907b9d38d8e2d7d8b8fe54381363
no error reported at all. is this expected?
cljs.user> (re-matches #"." nil)
cljs.user> (map #(re-matches #"." %) (repeat 1000 nil))
cljs.user>
(map #(re-matches #"." %) (repeat 1000 nil))
The result object failed to print. It is available via *1 if you want to interact with it.
The exception was:
TypeError: re-matches must match against a string.
=> :repl/print-error!
clj repl still works
shadow.user> (re-matches #"." nil)
Execution error (NullPointerException) at java.util.regex.Matcher/getTextLength (Matcher.java:1806).
Cannot invoke "java.lang.CharSequence.length()" because "this.text" is null
shadow.user> (map #(re-matches #"." %) (repeat 1000 nil))
Error printing return value (NullPointerException) at java.util.regex.Matcher/getTextLength (Matcher.java:1806).
Cannot invoke "java.lang.CharSequence.length()" because "this.text" is null
shadow.user> (/ 1 0)
Execution error (ArithmeticException) at shadow.user/eval68294 (user.clj:48).
Divide by zero
shadow.user>
at this point I don't have the slightest clue what you are doing, so impossible to make any kind of comment
if the REPL is in any kind of broken state it should be restarted. just trying to keep using it leads nowhere
I can maybe help diagnosing the initial error. every error/misbehavior coming after that I always assume was caused by the first
with the patch cljs repl doesn't show any return value, switching ns doesnt change prompt, no exception etc
cljs.user> 4
cljs.user> 5
cljs.user> 6
cljs.user> (+ 1 1)
cljs.user> (ns kkk)
cljs.user> (/ 1 0)
cljs.user>
this is easy to test. instead of using cider at all run the REPL built into shadow-cljs directly
I don't know what setup you are using given that the fix I did is not in any release yet
so my shadow-cljs is 2.28.8 plus the patched repl_impl.clj but nothing between 2.28.8 and when repl_impl.clj was patched.
$ find src
src/clj ...
src/cljs ...
src/cljc ...
src/clj/shadow
src/clj/shadow/cljs
src/clj/shadow/cljs/devtools
src/clj/shadow/cljs/devtools/server
src/clj/shadow/cljs/devtools/server/repl_impl.clj
this is how i incorporate the patched to testsorry can't really help you debug cider issues. I don't have a clue what it might not like
is it possible that you are using deps.edn or project.clj and only updated the shadow-cljs npm version?
I mean the change was so minimal that it is pretty much impossible for it to affect anything else
so I suspect that you are using an actually older shadow-cljs verison which had a very different repl_impl.clj out of the box
$ clj -Stree -M:dev:cider | grep shadow
thheller/shadow-cljs 2.28.8
. thheller/shadow-util 0.7.0
. thheller/shadow-client 1.3.3
. thheller/shadow-undertow 0.3.4
. thheller/shadow-cljsjs 0.0.22
$ cat user.clj | grep ...
(ns ...
[shadow.cljs.devtools.api :as shadow]
[shadow.cljs.devtools.server :as server])
(server/start!)
(shadow/watch :app)
$ cat shadow-cljs.edn
{:nrepl {:port 7002}
:source-paths ["src/cljs" "src/cljc"]
:dependencies [[binaryage/devtools "1.0.3"]
[nrepl "1.1.2"]
[cider/cider-nrepl "0.48.0"]
[cljs-ajax "0.8.4"]
[metosin/reitit "0.6.0"]
[reagent "1.2.0"]]
...
clj -M:dev:cider
2024-06-15 08:37:08,523 [main] DEBUG org.jboss.logging - Logging Provider: org.jboss.logging.Slf4jLoggerProvider
2024-06-15 08:37:20,728 [main] INFO org.xnio - XNIO version 3.8.8.Final
2024-06-15 08:37:20,829 [main] DEBUG org.xnio - Creating worker:null, pool size:64, max pool size:64, keep alive:60000, io threads:8, stack size:0
2024-06-15 08:37:20,842 [main] INFO org.jboss.threads - JBoss Threads version 3.5.0.Final
shadow-cljs - server version: 2.28.8 running at
just not sure why you pasted deps.edn clj
output, but shadow-cljs.edn
not being setup to use it
:source-paths
and :dependencies
in shadow-cljs.edn do absolutely nothing if you are using deps.edn
i launch shadow from user.clj but thought shadow still looks up shadow-cljs.edn
for settings. no?
it looks up shadow-cljs.edn for settings correct. just not :source-paths
or :dependencies
since deps.edn
is in charge of those when running via clj
git diff f8565dc63468df997e9e41b9ea28834675b45a88..3d81bff7b3fd907b9d38d8e2d7d8b8fe54381363 -- src/main/shadow/cljs/devtools/server/repl_impl.clj
diff --git a/src/main/shadow/cljs/devtools/server/repl_impl.clj b/src/main/shadow/cljs/devtools/server/repl_impl.clj
index a0f7a848..e0053260 100644
--- a/src/main/shadow/cljs/devtools/server/repl_impl.clj
+++ b/src/main/shadow/cljs/devtools/server/repl_impl.clj
@@ -89,7 +89,7 @@
(loop []
;; wait until told to read
(when (some? (<!! read-lock))
- (let [{:keys [eof?] :as next} (repl/dummy-read-one input-stream)]
+ (let [{:keys [eof?] :as next} (repl/dummy-read-one input-stream {:read-cond :allow :features #{:cljs}})]
(if eof?
(async/close! stdin)
;; don't recur in case stdin was closed while in blocking read
@@ -210,7 +210,7 @@
(recur)))
(do (>!! to-relay
- {:op :obj-as-str
+ {:op :obj-str
:to from
:oid ex-oid})
guess it fails internally then because it tried to call a function with an invalid arity
user> (re-matches #"." nil)
Execution error (TypeError) at (:1).
re-matches must match against a string.
:repl/exception!
user> (map #(re-matches #"." %) (repeat 1000 nil))
The result object failed to print. It is available via *1 if you want to interact with it.
The exception was:
TypeError: re-matches must match against a string.
:repl/print-error!
user> *1
The result object failed to print. It is available via *1 if you want to interact with it.
The exception was:
TypeError: re-matches must match against a string.
:repl/print-error!
user>
user> (doall (map #(re-matches #"." %) (repeat 1000 nil)))
Execution error (TypeError) at (<cljs repl>:1).
re-matches must match against a string.
:repl/exception!
so first the eval happens, which completes without error. then it attempts to print on a subsequent steps, which then blows up.