Status Update - Inspect, cljs_eval

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.10.5"}}}' -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. :wink:

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.

small-teaser

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.

33 Likes

Very exciting work @thheller! I’m going to take it for spin today. Also, is it possible for inspect to become a chrome dev tool extension or HUD that can show over the app?

2 Likes

awesome work!,
is there a way to hook the send form to repl in cursive to wrap it in a tap> ?

1 Like

Like this? :stuck_out_tongue:

4 Likes

Thats one thing I’d like to have as well. Haven’t quite figured out how to best do that yet for nREPL. Its easy with just a regular clojure.main REPL so I might just do that.

Ideally I’d want the Inspect UI directly in my Cursive window and I have that sort of working via an extra IntelliJ plugin. Pretty much the same as above, just opening a webview. Just not enough time to make an actual proper Plugin for this.

2 Likes

@thheller you can try this:
add a repl command, check execute command and then in the text area (tap> ~top-level-form).

2 Likes

This is incredible! Thank you so much, Thomas.
Time to introduce our Angular devs to re-frame via shadow-cljs. Yay!

1 Like

Looks great!

One very minor question, why the use of underscore in js/cljs_eval ?

js/cljs-eval also works. I just used cljs_eval because of the JS example where cljs-eval would be cljs minus eval which wouldn’t exactly work :wink:

Wow! Is this available already?

No, don’t have time to figure out all the extension related bits. The really basic version I have working from the screenshot you can download from https://code.thheller.com/data/shadow-ext.zip.

Just extract the files and use the “Load unpacked extension” from chrome to load the directory. It is really basic with barely any code. Not a priority for me but if anyone wants to work on this let me know. Basically all it does is looking for shadow-cljs code on the inspected page and if it finds something open the UI in an iframe.

Very cool!

(One feature request — is there a possibility to get rainbow parentheses in the inspect values?)

Unlikely that I’ll work on that. I don’t like rainbow parens personally but if someone wants to work on it I may add it behind a config flag or so.

In general I’d welcome any help on the UI front. I do have a couple plans for things I want to add but overall design and UX is definitely not my preferred area. Happy to work on the code but getting it to look nice is beyond my capabilities. :stuck_out_tongue:

Great updates @thheller!

Last time I used shadow inspect I think it doesn’t remember my eval history, is that supported now? Sometimes it’s pretty common to repeatedly cycle among a few eval expressions to weight and compare them.

Not supported but I’ll probably add it eventually. Feel free to open an issue to be notified when its done.

This is amazing, great job! I’ve dreamt about having js/cljs-eval, and you just went ahead and made it. Thank you so much!

1 Like

Just tried both inspect and cljs-eval, works like a charm! :slight_smile:

1 Like

Exciting work @thheller!

Today, I spent some time implementing initial support for cljs_eval in Dirac REPL:

What is loopback mode?

We can make use cljs_eval without any prior Dirac setup. If Dirac REPL is opened on a page which has no Dirac runtime but has this cljs_eval available, we can at least offer some sugar on top of cljs_eval. When you type “some cljs code” into Dirac REPL and hit ENTER, it will be effectively turned into await cljs_eval("some cljs code"). Shadow-cljs then does compilation, evaluates generated javascript and you should hopefully see some results back in the console. To switch current namespace enter (in-ns 'your.new.namespace) , this is handled specially by Dirac REPL.

I refer to this mode of operation as “loopback REPL”. Because there is no Dirac nREPL backend or runtime support. Dirac talks to page’s context to do cljs evaluations. Some Dirac features are not supported. When this mode is active, you should see a purple “loopback” indicator when the REPL prompt is empty.

1 Like

I assume this won’t work when paused at a breakpoint, right? Anyway, really great to see some shadow-cljs + diract progress!