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

What will it take for <script type="module" src="foo.wasm"> to be useful? #53

Open
trusktr opened this issue Aug 5, 2021 · 7 comments

Comments

@trusktr
Copy link

trusktr commented Aug 5, 2021

Hello, first I want to say that I am relatively new here. I have been interested in WebAssembly for a long time, but also like many other web developers I haven't been interested in jumping ship from TypeScript/JavaScript to another language and ecosystem in order to use WebAssembly, especially on one-man projects where the benefit will not outweigh the cost, especially when it comes to developer experience, maintainability, and ecosystem.

But then AssemblyScript happened. This is a great thing for WebAssembly because...

...many web developers like me who do not wish to move to other languages like C/C++ or Rust are discovering AssemblyScript and getting really interested in actually using WebAssembly this way. AssemblyScript is bringing WebAssembly to these people, rather than them having to leave their language ecosystem to go to WebAssembly.

This has placed AssemblyScript as one of the top three languages for WebAssembly already, despite how new it is. At this pace it may not be a surprise if it gets to the number one spot.

As I heard @linclark say at Wasm Summit in person (summarized, not quoted): get users first. Without them, the system is meaningless.

I believe AssemblyScript is a excellent tool for this purpose, but it needs some kindling and consideration.

I've been making asdom, DOM bindings for AssemblyScript. They are hand-rolled bindings, as I haven't figured how auto-generated bindings can be possible yet without sacrificing performance in so many edge cases.

The following is a recording of the first DOM-centric web app (WIP) written entirely in AssemblyScript (after importing asdom's JS glue code for DOM APIs):

aspkg-html.mp4

The video shows Custom Elements being manipulated based on and Location/History routing (all of it written in AssemblyScript).

The code looks as familiar as you would expect, with customElements.define(), connectedCallback, disconnectedCallback, etc:

https://github.com/aspkg/website/blob/master/assembly/elements/MainContent.ts

Once JSX and tagged template string functions are in place, things are going to get interesting (think React- or Solidjs-style libraries in AssemblyScript), and I would imagine this will drive even more people to WebAssembly via AssemblyScript.

Back to the main question

The idea of <script type="module" src="foo.wasm"> seems very nice if such Wasm modules will have access to browser APIs without requiring help from JavaScript. If they don't have that access, then that approach will not be very useful for anything because the Wasm modules will be black boxes that run something unobservable inside of them without access to external APIs.

I'm imagining the module loads, and there need not be auto-generated glue, or hand-written glue, just Wasm modules with access to browser APIs. People in other languages can write an entire web app without toolchains needed to generate bindings (assuming the module does not depend on anything else except built-in browser APIs, fully self-contained), and just stick the Wasm module in via script tag, fulfilling <script>'s multi-language destiny.

What other proposals exist (or are needed) for the <script> form to be useful (namely without JavaScript in the picture)?

Is there already something in place that defines how Wasm modules can handle the dynamic non-statically-analyzable nature of anything JS can export? (that's still not as nice as being able to use web APIs directly without JavaScript being anywhere in the picture).

@Pauan
Copy link

Pauan commented Aug 5, 2021

It's possible for the Wasm module to import a .js module which exports the browser APIs. That sort of glue code will be needed for quite a long time (an unknown number of years).

Of course it's possible for languages to auto-generate this JS glue code (which is what Rust does), which makes the experience quite smooth for users.

Interface types will help a tiny bit, but by itself it won't solve the problem, since Wasm still has no way to import the browser APIs, so you still need glue code.

So it will require something like built-in modules in order for Wasm to import browser APIs without any JS glue code.

@trusktr
Copy link
Author

trusktr commented Aug 5, 2021

Looks like browsers need an additional "built-in modules" like thing, but for DOM with a dom: prefix (that one seems to be just for language std lib, with a js: prefix, usable in any worker for example).

After that is in place, how would a Wasm module be able import any sort of JS API and magically use it without knowing the types of those interfaces (they are so dynamic, they could be mutating objects of any form, with mutating prototypes, can take and return any type of such values, etc).

EDIT: Would Type Imports be required? That means each toolchain would need to at least generate (or get from somewhere common) all the needed types. Or maybe the browser can provide those types?

@devsnek
Copy link
Member

devsnek commented Aug 5, 2021

the wasm module can specify literally (import "./glue.mjs" "foo") and that goes through normal esm specifier resolution, so you can just have a glue.mjs file sitting on your server next to foo.wasm.

@trusktr
Copy link
Author

trusktr commented Aug 5, 2021

How can I read/write any property and call any method on foo? Is the "Type Imports" spec needed for that? And if so and I understand correctly, the type definitions would need to be provided (instead of glue code)?

@devsnek
Copy link
Member

devsnek commented Aug 5, 2021

presumably the glue has to provide that functionality too. for example, a very verbose rust js-sys setup might look like this:

(module
  (import "./glue.mjs" "createString" (adapter_func $createString (param string) (result externref)))
  (import "./glue.mjs" "ReflectGet" (func $ReflectGet (param externref) (param externref) (result externref)))
  (global $globalThis (import "./glue.mjs" "globalThis") externref)

  (data (i32.const 0) "Array")

  (adapter_func $liftString (param i32) (param i32) (result string)
    ;; ????
    (call_adapter $createString)
  )

  (func $main
    (i32.const 0) ;; Array
    (i32.const 5) ;; len=5
    (call $liftString)
    (global.get $globalThis)
    (call $ReflectGet)
    ;; globalThis.Array is on the stack now
  )
)

I'm not entirely sure what "????" is (or if any of the adapter_func stuff is correct) but I think this is reasonably representative of what you would need to do?

@trusktr
Copy link
Author

trusktr commented Aug 5, 2021

That's also using externref, which seems to mean that if we want to access, for example, globalThis.document.body.firstElementChild.nextElementSibling, we'd have to make the glue code for that too (improt functions for each level of access).

For example in asdom this is the Location class, calling out to all the imported functions in order to create the sense of an actual "class" for the AS developer.

This sort of thing would still need to be done with externref, if I imagine it correctly. And if I understand Type Imports, then the externref occurrences in your example could be replaced with types that (I hope) allow for properties and methods to be defined and accessed, without needing to make wrapper classes? Or would we still need to create such wrapper ("mirror") classes in our chosen language?

@Pauan
Copy link

Pauan commented Aug 5, 2021

If you want to do extremely dynamic things, that's what Reflect is for. Rust uses Reflect to look up and set arbitrary string keys on JS objects.

Technically speaking you would only need glue code for Reflect, and then you could do basically everything. But the performance wouldn't be great.

In practice Rust just manually defines type bindings for JS APIs. And it auto-generates type bindings from WebIDL. By the way, all of this uses externref, which is the correct way to use arbitrary JS values.

We had experimented with using TypeScript bindings (e.g. from DefinitelyTyped). They have extensive and well-maintained bindings. But Rust's type system is too different from TypeScript, so we gave up on that. Maybe AssemblyScript would have better success.

Or would we still need to create such wrapper ("mirror") classes in our chosen language?

Nobody knows, since the proposals haven't been fully fleshed out or standardized yet. We don't even have basic interface types yet, the kinds of things you're asking for are quite advanced and will take a lot of work to spec.

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

3 participants