dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.99k stars 4.66k forks source link

[browser] Support for web extension constraints #80045

Open elringus opened 1 year ago

elringus commented 1 year ago

Support for web extension constraints

The requirements for web extension are very strict: they have to be bundled as a single JS file, can't use any imports or node/browser APIs, etc. That would also refine various APIs, as, for example, new JSInterop requires loading modules for imported function at runtime from C#, which is not allowed in a VS Code web extension (#87365).

From https://github.com/dotnet/runtime/issues/91558 Here is the link to the patch I'm using to strip all the offenders: https://github.com/elringus/bootsharp/blob/main/src/cs/Bootsharp.Publish/Pack/ModulePatcher/InternalPatcher.cs After the patch, bundlers are only complaining about module and process unresolved externals (tested with webpack, rollup, esbuild, vite) and the bundled runtime is working in browsers, node, deno, bun and VS Code web extensions.

Regarding module and process — would be optimal to guard them as well, though I wasn't able to find a straightforward way to do that (conditional usage still trigger bundlers). As a workaround, I'm marking them as global (eg, -g process,module in rollup), which seem to satisfy the bundlers.

dotnet-issue-labeler[bot] commented 1 year ago

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

ghost commented 1 year ago

Tagging subscribers to 'arch-wasm': @lewing See info in area-owners.md if you want to be subscribed.

Issue Details
After reading https://devblogs.microsoft.com/dotnet/use-net-7-from-any-javascript-app-in-net-7/ and checking the samples I had an impression the intended use case is to run it on the main browser thread only, but it seems to be working fine under worker threads as well (#77946). Is this something just happen to work for now, but can break in the future, or worker environment is officially supported and will be maintained under the `browser-wasm` runtime (ie, wasm module and associated JS wrapper won't use `window` or other APIs not available for workers)?
Author: Elringus
Assignees: -
Labels: `question`, `arch-wasm`, `untriaged`
Milestone: -
am11 commented 1 year ago

Glad to see dotnet wasm working in constrained / sandbox environments as well! 8-)

In .NET 7, dotnet.js and dotnet.wasm were modularized and support for non-browser environments was added (e.g. node.js, v8 and for deno). Prior to .NET 7, there were only a handful of DOM / window usages in the core implementation, which were refactored in .NET 7 with guarded conditions like if (typeof window.foo === 'function') window.foo; /* else useSomethingElse() */ as part of the modularization work.

I think (hope) moving forward, we won't regress it by (re)introducing unconditional dependencies on DOM or window and therefore keep supporting sandbox environment such as Web Workers. Perhaps we can add some tests to preserve this desired state.

elringus commented 1 year ago

I think (hope) moving forward, we won't regress it by (re)introducing unconditional dependencies on DOM or window and therefore keep supporting sandbox environment such as Web Workers.

That's exactly what I'm concerned about, especially with all the docs and samples only mention running either on main browser thread or under node. Would be great to have an official statement somewhere (eg, https://learn.microsoft.com/en-us/aspnet/core/client-side/dotnet-interop) and associated tests covering workers (or constrained environments in general).

I'm currently using a patched .NET 6 fork to make it compatible with both node and worker environments. The prospect of migrating to stock runtime is exciting, but I need to be sure it won't suddenly break at some point.

radical commented 1 year ago

/cc @pavelsavara

Kuinox commented 1 year ago

but it seems to be working fine under worker threads as well (https://github.com/dotnet/runtime/issues/77946).

Hi, I opened this issue, yes, currently I successfuly run a .NET compiler toolchain in a webworker.
Out of the box, it works on chrome, but not on firefox, because Firefox doesn't support ES modules in webworkers yet, and .NET dropped support for cjs modules. To work around this, I use vite to convert dotnet.js into CJS:

await viteBuild(defineConfig({
    build: {
        lib: {
            entry: path.resolve(binFolder, 'dotnet.js'),
            name: 'dotnet',
            fileName: 'dotnet',
            formats: ['umd']
        },
        rollupOptions: {
            external: ['dotnet.wasm'],
            output: {
                esModule: false,
                format: 'cjs',
                compact: true
            }
        },
        outDir: outDir
    }
}));

Now in my webworker I import the modified dotnet.js, and an odd startup:

importScripts('./dotnet.js');
async function main() {
    const monoCfg = await firstMessagePromise;
    const dotnet = self.dotnet.dotnet;
    dotnet.moduleConfig.configSrc = null;
    const { setModuleImports, getAssemblyExports, } = await dotnet
        .withConfig(
            monoCfg
        )
        .create();
    await dotnet.run();
}
main();

And voilà.

elringus commented 1 year ago

I think an ultimate test for this would be making a sample (similar to the TodoMVC and React mentioned in the devblog article) of a VS Code web extension built using the new JSInterop and compiled for wasm target. The requirements for web extension are very strict: they have to be bundled as a single JS file, can't use any imports or node/browser APIs, etc. That would also refine various APIs, as, for example, new JSInterop requires loading modules for imported function at runtime from C#, which is not allowed in a VS Code web extension (#87365).

Kuinox commented 1 year ago

I ran some experiments (locally, didnt deployed it) for https://playground.draco-lang.org/, I inlined the imports with esbuild. There is still imports, but no network requests.

elringus commented 1 year ago

I ran some experiments (locally, didnt deployed it) for https://playground.draco-lang.org/, I inlined the imports with esbuild. There is still imports, but no network requests.

Do you have [JSImport] methods there? If so, how do you use JSHost.ImportAsync? In my tests it didn't work when I've attempted importing the main js file; the application just hang.

Kuinox commented 1 year ago

Yes I do use [JSImport] methods, but I don't use JSHost.ImportAsync.

elringus commented 1 year ago

Yes I do use [JSImport] methods, but I don't use JSHost.ImportAsync.

Ah, I wasn't aware of setModuleImports; thought that the only way to import a function from JS is to use JSHost.ImportAsync. Thanks for the insight!

Though, I still think VS Code extension would be an ultimate sample for constraint environments, as it's running in a very restricted web worker. Using general browser web worker won't cover all use cases.

pavelsavara commented 1 year ago

Running the runtime's main on the web worker is something I didn't try yet. Is this issue about that, should we make it more explicit ?

Work on threads (via web workers) in in progress. For extensions, we mostly fixed CSP.

Regarding bundling all of it into single file, I think external solutions like webpack are way to go, to include also user code. For the start it would be good to see if our current code is preventing webpack in some way. And report it in https://github.com/dotnet/runtime/issues/86162

elringus commented 1 year ago

Running the runtime's main on the web worker is something I didn't try yet. Is this issue about that, should we make it more explicit ?

More specifically running it in constraint environments, live VS Code's web extensions; there are other constraint enthronement cases, like the deno runtime or plug-ins for various web apps, but I thought a VS Code extension sample would be more appropriate/useful for the community. There are VS Code extensions written in C# and depending on a standalone .NET runtime installed on the machine (eg, C# LSP); because of that they are not compatible with the new web extension format. Making a sample would help them more confidently migrate to the web format.

pavelsavara commented 1 year ago

Well, LSP and Roslyn are probably better off on CoreCLR, with it's OS level file system access and native JIT performance.

Kuinox commented 1 year ago

Running the runtime's main on the web worker is something I didn't try yet.

I can tell you it works.
I have a demo using it, it's availabe here..
The source are here: https://github.com/Draco-lang/Compiler/tree/main/src/Draco.Editor.Web/app
Most of the time was spent into bundling it, and figuring out which API to use.

elringus commented 1 year ago

Well, LSP and Roslyn are probably better off on CoreCLR, with it's OS level file system access and native JIT performance.

Sure, but it won't work in https://vscode.dev this way. I had an impression vscode is pushing extensions to support web.

pavelsavara commented 8 months ago

Re-reading it now, there are few updates

elringus commented 8 months ago

https://github.com/dotnet/runtime/issues/91558 can probably be merged into this issue?