Hey everyone,
quite a few things have happened in shadow-cljs I and wanted to highlight some of the recent additions/changes. All features mentioned here are available in 2.10.5+.
Inspect
The main feature I worked on for the past few months is Inspect. Mostly because I cannot work without it myself anymore. The power it provides over regular prn
or some.log/debug
is just too addicting. A regular REPL unfortunately isn’t sufficient since the values I’m working with (eg. shadow-cljs build-state) are quite often way too big to print entirely.
A lot has changed since the initial announcement but the basic principle is still the same. You tap>
something anywhere in your code and it shows up in the UI letting you fully inspect and browse it. Since this never does a full print you can send arbitrary large values.
The implementation has changed quite a bit and there is now only one Inspect tab and all tapped values go there. All shadow-cljs REPL enabled builds (eg. :browser, :react-native, :node-script, :node-library, ...
) also have this automatically enabled. No further setup required. Just tap>
away and the UI will show it.
If you have never used Inspect before you can try exploring a really large value. For example the entire internal shadow-cljs state.
npx create-cljs-project inspect-test
cd inspect-test
npx shadow-cljs clj-repl
(tap> shadow.cljs.devtools.server.runtime/instance-ref)
Or if you prefer clj
and don’t want to deal with npm
clj -Sdeps '{:deps{thheller/shadow-cljs{:mvn/version"2.11.24"}}}' -m shadow.cljs.devtools.cli clj-repl
(tap> shadow.cljs.devtools.server.runtime/instance-ref)
The URL for the UI should have been printed on startup, it will usually be http://localhost:9630 but may be increasing from that in case there is already another shadow-cljs instance running.
shadow-cljs - server version: <version> running at http://localhost:9630
In the running clj-repl
you can also call (shadow/node-repl)
or (shadow/browser-repl)
to get a CLJS REPL and (tap> :hello)
from there. It will show up in the same place as above. Same is true for any of your builds using tap>
anywhere. I encourage you to try it. This is still really early stage but I use it for everything and it works reasonably well.
Another feature Inspect gained is the ability to eval code. Sometimes it might just be faster to type in some code to get what you want. So any value you select in the Inspect window now also has a small “eval” area at the bottom that takes any CLJ or CLJS code (dependent on where the value was tapped). ctrl+enter or shift+enter will eval the code. There is a special $o
value you can use to get the actual value you currently looking at in the Inspect window. Say if you want to store the value in a def you can just call (def foo $o)
and later access it via foo
. This is not a REPL in the traditional sense but provides much of the same power.
Not all values are currently displayed properly in the UI and one big missing piece is lazy sequences. So if you eval (range)
for example it won’t be displayed. You also can’t display it as EDN since the UI aborts printing for any values above 1mb. An infinite seq like that quickly reached that point. Since there is eval you can however just call (vec (take 50 $o))
. I’ll add support for more seqs eventually but this is good enough for now. Avoid tapping lazy-seqs and you won’t run into this much but at least it won’t blow up your REPL if you do.
The eval
functionality sort of required rewriting the REPL implemention for all CLJS REPLs so everything is based on the shadow.remote
architecture now which might be interesting for tool authors at some point. I’ll post more about this when I feel it is ready.
cljs_eval
As a bonus from the above it is now possible to eval CLJS code from the runtime itself without having to fallback to self-hosted CLJS. While the watch
for a build is running you’ll have a js/cljs_eval
fn at your disposal. This might be interesting for tools like re-frame-10x, fulcro-inspect or any other dev-related things that are typically injected via :preloads
. You may also just use it from the browser console.
It will always return a Promise
. Mostly since its more convenient to use from the browser console since you can directly await
it. From CLJS you may also specify more options like the :ns
which this should eval in.
(-> (js/cljs_eval {:code "::foo" :ns 'your.app})
(.then tap>)
(.catch tap>))
This is provided as a global function so consumers don’t have to depend on any shadow-cljs namespaces which may be hard to remove in :advanced
builds. Of course this is no replacement for full self-hosted CLJS and only available during development but it might be useful for some dev stuff you really need eval
at runtime for. Curious to see what this will be used for. Let me know if you have any questions.