Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

There are too many JS runtimes, don't know which to eval in. #314

Closed
kurtharriger opened this issue Jun 14, 2018 · 8 comments
Closed

There are too many JS runtimes, don't know which to eval in. #314

kurtharriger opened this issue Jun 14, 2018 · 8 comments

Comments

@kurtharriger
Copy link

I have use case where it would be useful to have multiple connected runtimes:

Our module supports collaborative editing and thus testing requires multiple browser sessions. However, once more than one session connects I can no longer use the repl due to the above error.

I do realize if there are multiple sessions you generally don't need to evaluate an expression in both windows and there is ambiguity as to which return value to print in the response. In our case it would be fine if it just selected the first socket that connected, so my workaround is to start a repl manually in one session when/if I want one.

  global.startRepl = () => {
      require("../cljs-dev/shadow.cljs.devtools.client.browser");
    }

But, I have no way to switch sessions other than closing that browser window. Perhaps each session when connected could write a session id to the console. Then, by default, when multiple sessions are connected it is sent to the first session. If you want to switch, then perhaps you quit and rerun nrepl-select with an additional session id argument.

It would would also be very useful in our case to broadcast a command to all connected sessions. This would make reproducing issues where multiple users typing concurrently a breeze. This might be awkward as a "repl" but perhaps a maybe another function like (repl-send [session-id|:all] expression) that would return a sequence of promises from each runtime.

@thheller
Copy link
Owner

Yeah this is a known problem and I don't have a clean solution yet. Everything for calling nrepl-select with a runtime-id is already in place it is just not easily accessible yet. I will definitely add a broadcast option as well.

I have a very rough prototype available if you run shadow-cljs clj-repl and then (shadow/repl-next :app). If only one runtime is connected you get that runtime. When multiple runtimes are connected it prints the list of connected runtimes and lets you select one.

[5:0]~shadow.user=> (shadow/repl-next :browser)
There are 3 connected runtimes, please select one by typing the number and pressing enter.
Type x or q to quit
[0 {:runtime-type :browser, :user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.17 Safari/537.36", :remote-addr "0:0:0:0:0:0:0:1"}]
[1 {:runtime-type :browser, :user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134", :remote-addr "0:0:0:0:0:0:0:1"}]
[2 {:runtime-type :browser, :user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0", :remote-addr "0:0:0:0:0:0:0:1"}]
2
Connecting to REPL runtime: 46e2c0db-fa1e-467a-b917-de9865ef911c
[5:1]~demo.browser=> (js/alert "foo")
nil

This kind of interaction is very easy to do in a normal REPL since it basically just starts a second REPL and waits for a number or x or q and then drops you into whatever else you selected. This is next to impossible in nREPL without adding additional nREPL middleware that all editors then need to support.

@beders
Copy link

beders commented Sep 11, 2018

@kurtharriger Found another way around this issue. You can close the shadow-cljs connection from a browser console if you already started two sessions.

shadow.cljs.devtools.client.browser.socket_ref.state.close()

And your REPL will pick the left-over runtime.

@cdorrat
Copy link

cdorrat commented Jan 19, 2019

Maybe a good interim solution would just be to have it always use the first connected repl for evaluation. I love being able to see the app I'm developing live on multiple browsers and mobile devices simultaneously but I wont trade the repl for it.

@arichiardi
Copy link
Contributor

This kind of interaction is very easy to do in a normal REPL since it basically just starts a second REPL and waits for a number or x or q and then drops you into whatever else you selected. This is next to impossible in nREPL without adding additional nREPL middleware that all editors then need to support.

Sorry to barge in, interesting discussion and I would love to know more about what limitation is there.

Also, middleware is pluggable so you basically decide server side (client side is coming with side-loaders), which one to use, depending on the feature that your editor supports? If the editor does not support it, you can avoid loading that middleware. For instance you could have a shadiw-cljs-cursive middleware vs a shadiw-cljs-cider middleware.

In any case, many things are being addressed as we speak so this could be feasible soon. Actually the problem is that cider, for instance, does not have knowledge of a "shadow-cljs" connection so it cannot interpret its prompt - yet?

@thheller
Copy link
Owner

@arichiardi the difference between nREPL and normal REPL is that the normal REPL is stream based. It is just a stream of characters. Text goes in, Text comes out. The REPL client is not expected to do anything special and everything typed by the user is just passed along. Any function used at the REPL can then hijack that stream if it wants to Read until it has received what it wants. It will then drop down back to the original REPL and continue on. This is fundamentally not possible with a message based protocol where each message is interpreted by a fixed set of middleware that the client has to be aware of.

Ideally there would be a extra couple nREPL ops that deal with this kind of thing but there is not any as of yet.

There are a bunch of other ongoing discussions about related topics. None with suitable solutions AFAICT.

nrepl/nrepl#45
nrepl/piggieback#73
nrepl/piggieback#88

FWIW I have a working solution in the UI that is available when running the shadow-cljs server (default at http://localhost:9630). The REPL lets you directly select which runtime to connect to and has no issues with multiple browsers being connected. Of course that is not nREPL based but the basic ops required are just something to get a list of the connected runtimes and then some ID is passed along when starting a new REPL session.

@arichiardi
Copy link
Contributor

Yeah well message based protocols have all sorts of other advantages for editors of course, see for instance Server Language Protocols.

As you said the ops should suffice and because you can provide your middleware as part of the shadow jar this would not need to be in cider-nrepl for instance. Nowadays nRepl configuration can be choose globally as well (see here).

@thheller
Copy link
Owner

Yeah well message based protocols have all sorts of other advantages for editors of course

You are missing my point.

The message based protocols do have several advantages over a pure text stream but all features need to be supported by the client. So shadow-cljs can't really do much until clients like cider or Cursive support such features. With pure Text In/Out (socket REPL) the client doesn't need to support anything so shadow-cljs can simply inject the feature into the REPL stream without much issue.

If you have concrete requests for some nrepl middleware ops that cider would like to use I'm happy to add them.

@thheller
Copy link
Owner

@cdorrat your suggestions is reasonable and now the default in 2.7.16. The first connected runtime is used by default if none other is specified by the client.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants