dotnet / interactive

.NET Interactive combines the power of .NET with many other languages to create notebooks, REPLs, and embedded coding experiences. Share code, explore data, write, and learn across your apps in ways you couldn't before.
MIT License
2.89k stars 384 forks source link

HMR (hot module reloading) in HTML .NET Interactive Kernel #1240

Closed PawelStadnicki closed 3 years ago

PawelStadnicki commented 3 years ago

I have pretty advanced application in .NET Interactive so to speed up the experiments I wanted to use HMR but it doesn't work (at least for me).

Applying HMR to .NET interactive may seems to be exaggeration because such an experiments are rather short in nature but believe me that I have a reason for that :)

In simplification: I'm doing some advanced JS in one VS Code cell, under the cover I bundle it with webpack and put final content into HTML kernel. It works great except HMR. I need to execute HTML cell (like full reload) to have it affected.

My HTML cell is more or less like this:

<div id="myComponent"></div>
<script src="http://localhost:[first free port]/myComponent.bundle.js"></script>

When I extract exactly the same html markup and put it to some index.html file, HMR works like a charm and I can see everything in chrome dev tools.

Are there any internals that may block HMR in .NET Interactive? Is there any way to see logs why it may not work ( I do not see anything special in VS Code output window)?

jonsequitur commented 3 years ago

To debug JavaScript inside the web outputs in VS Code, you can open the command pallet and run Developer: Open Webview Developer Tools.

I'm not aware of anyone else trying to use HMR in .NET Interactive yet but it sounds like an interesting use case. If you can't figure out what's going on and can provide a small repro, we can probably help out.

PawelStadnicki commented 3 years ago

thanks @jonsequitur ! it helped me to find the root cause: I missed webpack config entry that allows to bypass more CORS actions (disableHostCheck: true).

Summarizing: HMR works with .NET interactive :) I have a PoC that translates F# to JS with Fable (via my own F# Kernel Extension) so F# code communicates between the F# and JS cells.

I wanted to achieve more that I can do with Plotly.NET I process the geojson open data in F# (F# Kernel) and visualize it with F# (but on JS/HTML kernel so I canhave full control). The visualization is based on Azure Maps, turfjs and d3js

jonsequitur commented 3 years ago

Glad this helped!

In case it's useful for Fable as well, we're in the process of making JavaScript cells and the JavaScript kernel more like the other "server-side" kernels, programmatically. So you can do this with JavaScript:

image

PawelStadnicki commented 3 years ago

That is very interesting, more than great in my scenario. Can't wait for it

jonsequitur commented 3 years ago

The JavaScript kernel example I gave works right now. A possible next step here would be to have kernels like this execute entirely in the browser, which in the case of Fable might include transpilation. Would that be of interest?

PawelStadnicki commented 3 years ago

I'm not sure why the mentioned sample cannot work in the browser or what we mean by running in the browser here. It is standalone website or .NET Interactive run for instance in the Codespaces?

I will describe shortly how I tackle the Fable in the .NET Interactive. Please note that my knowledge about extending .NET Interactive is fairly limited. I had at least two options:

So I'm still looking for best experience. Do you see any issues that may come over with Github Codespaces usage here ? I don't have access to the preview so can't verify it.

My work is going to be 100% open source, probably need one more weekend to finish the PoC and publish it

jonsequitur commented 3 years ago

I'm not sure why the mentioned sample cannot work in the browser or what we mean by running in the browser here. It is standalone website or .NET Interactive run for instance in the Codespaces?

.NET Interactive works today in CodeSpaces. Code execution still happens on the server in that case.

What we mean by execution in the browser (and I guess it would be clearer to say frontend, since this also applies to VS Code) is a little different. It would be like a single-page architecture, where the kernel code of JavaScript that's loaded statically, after which point there's no communication with a server needed to execute notebook cells.

jonsequitur commented 3 years ago

We can select Fable as a kernel from the drop down list in visual studio code so it seems to be a good UX experience. I didn't opt for that initially as it requires some knowledge which I could gain first with the second option and later apply that knowledge here if second option is not enough.

We're hoping that eventually the kernel can provide the information dynamically to the frontend so that no VS Code extension modifications will be needed to bring in additional languages, and the work can be done entirely in the custom kernel.

jonsequitur commented 3 years ago

A few general thoughts, not guaranteed to be useful:

The architecture you're describing is similar to what we've discussed for a Try .NET kernel. The idea there is that some parts execute on the server (e.g. completions, hover text) and other parts in the frontend (browser or VS Code). We've even discussed the possibility that inheriting from CSharpKernel and overriding HandleAsync(SubmitCode, InvocationContext) could work. In this approach, the overridden SubmitCode handling, instead of executing the code, would return a compiled assembly to the frontend, which would run it using Blazor WebAssembly. The server-side kernel could aggregate the compiled state, allowing completions to work, or it could potentially be round-tripped by the frontend kernel if you want a stateless server.

Making it a separate kernel instead of a magic command means it won't share state with the "other" F# kernel, which might make it easier to add functionality over time. Ultimately, implementing a kernel is possibly easier than implementing a magic command anyway. But I get that the VS Code extension part makes the magic command nicer for now because of code coloring and other editor features.

Another thing to consider is that sending commands to the JavaScriptKernel as in my screen shot above is more "correct" than using Display and ScriptContent, because that way, the server-side code can await the result of the computation that ran in the frontend, respond to failures, etc., and the user will see better errors and so on.

PawelStadnicki commented 3 years ago

We're hoping that eventually the kernel can provide the information dynamically to the frontend

I think that indeed would be much needed. VS Code is a one story, the second is that I expect many developers to write custom extensions/kernels for blogging, education and other creative ways

PawelStadnicki commented 3 years ago

which would run it using Blazor WebAssembly.

I'm interested with Blazor as well but I'm leaving it for the coming months. From one hand Fable/F# is build around js ecosystem and it works very well. From the other hand Blazor is/will be very supported from MS and well adopted, I assume the Try.NET / Try.js will use it. I don't need anything specifically from Blazor except ASP.NET hosting which I could get for free in that case. But who knows, maybe I will have some performance reason to use some port of F# on WASM where Fable still has no focus right now (they experiment with sveltejs )

Another thing to consider is that sending commands to the JavaScriptKernel as in my screen shot above is more "correct" than using Display and ScriptContent

Agree, I will try that

Making it a separate kernel instead of a magic command means it won't share state with the "other" F# kernel,

hmm,why? Actually I do not use F# kernel directly in my magic command , just take the code from SubmitCode command and put it into fable cli. If I created it as a kernel rather than magic command why I wouldn't have coloring end editor facilities ? I assume my FableKernel could just inherit/extend the F# Kernel but maybe you mean some additional work from the frontend side (vscode or any other Monaco Editor page) is required for that.

jonsequitur commented 3 years ago

If I created it as a kernel rather than magic command why I wouldn't have coloring end editor facilities ? I assume my FableKernel could just inherit/extend the F# Kernel but maybe you mean some additional work from the frontend side (vscode or any other Monaco Editor page) is required for that.

This is because aspects of the VS Code experience are driven by the VS Code extension's view of what language a cell is. This includes code coloring, commenting, and other language-specific features provided by VS Code.