Should ESM integration provide Modules rather than Instances?
There's been some feedback provided recently that toolchains and bundlers wouldn't be able to take advantage of the current Wasm/ESM integration proposal design, because it focuses on providing instantiated Wasm modules to interoperate in the JS module graph.
Instead, it could be possible for the default mode of exporting Wasm modules into the JS module graph to be an uninstantiated module (a related idea was suggested in https://github.com/WebAssembly/esm-integration/issues/14, which was about optionally exposing a special export for the module object). In that case, importing a module and instantiating a Wasm module using ESM might look like:
import mod from "foo.wasm" assert { type: "webassembly" };
WebAssembly.instantiate(mod, importObject);
This uses an import assertion requiring that the import is an uninstantiated "webassembly" module (this would be required for the same reason that it's required for JSON and CSS modules; they have different execution behavior than JS modules).
Alternatively, this could be done using the import reflection proposal (https://github.com/tc39/proposal-import-reflection), which would provide more flexibility to provide both (instantiated and uninstantiated) behaviors by using different reflection types. This would allow the current Wasm/ESM behavior to be either kept or deferred to the future.
I'd be interested in feedback from people working in toolchains and bundlers what would work better, and if there are cases where the original behavior of instantiating modules would be more useful.
I think the default should definitely be instantiated, that's sort of the point of hooking up the esm graph in the first place. The reflection proposal feels like the correct solution to this to me (if a solution is needed at all? I would personally use compileStreaming instead of import if i wanted a module and not an instance)
There's been some feedback provided recently that toolchains and bundlers wouldn't be able to take advantage of the current Wasm/ESM integration proposal design
Do you have a link to that feedback? It would be good to get more specifics.
I think the feedback is not different than what was discussed in #44. However, as folks are starting to implement ESM-integration natively in engines and embeddings, it's important to ask: would this feature be practically adoptable by toolchains today and, if not, what would be adoptable today to get us out of the current state of using fetch() and/or base64-encoded strings (which are bundler/import-maps/toolchain-unfriendly). I think the root observation in #44, which I've come around to agreeing with, is that: every core wasm module is, in practice, wrapped by JS glue code that wants to control the WebAssembly.instantiate() call itself and thus only the ESM-import-a-WebAssembly.Module feature that would be immediately practically adoptable.
In the future, extensions to core wasm and/or the component-model should make the currently-proposed ESM-integration semantics (where instantiation is performed automatically by the ESM loader) more-imminently adoptable, so you could think of the proposed change of plan here as more of a sequencing change than a directional change.
Also, practically speaking, I think the actual spec and implementation work needed for ESM-importing a WebAssembly.Module is roughly a subset of ESM-integration, so much of the existing work and thought (viz., how the wasm gets loaded via the ESM loader) can be reused if we made this plan adjustment.
@takikawa
import mod from "foo.wasm" assert { type: "webassembly" }; WebAssembly.instantiate(mod, importObject);
Is this example intentionally using instantiate over instantiateStreaming? If so, would there be a downside to doing so compared to current toolchain code that uses instantiateStreaming today?
@lukewagner
I think the root observation in https://github.com/WebAssembly/esm-integration/issues/44, which I've come around to agreeing with, is that: every core wasm module is, in practice, wrapped by JS glue code that wants to control the WebAssembly.instantiate() call itself and thus only the ESM-import-a-WebAssembly.Module feature that would be immediately practically adoptable.
+1, that is the case in Emscripten. We could easily adopt ESM integration if we still instantiate, but not otherwise.
Do we have any information about what Wasm toolchains who want modules rather than instances would gain vs using compileStreaming?
Ideally toolchains would use instantiateStreaming, since that's more efficient than compileStreaming.
In that case, perhaps toolchains want something even more general, a way to fetch arbitrary resources:
import response from "./foo.wasm" assert { type: "fetch" };
WebAssembly.instantiateStreaming(response, importObject);
The semantics would be identical to fetch (it returns a Response object), except the URL is resolved according to the ESM machinery (instead of being global like fetch).
This would be useful for more than just Wasm: it can be used to fetch images, text, JSON, etc.
The benefit of this over fetch is that it is statically analyzable (so bundlers know which files need to be included in the bundle, and it can be integrated with HTTP/2 server push), and URLs are resolved relative to the JS module.
i don't think this is a bad idea but it is definitely an increase in the scope of esm and this proposal. you might be interested in stuff like https://github.com/tc39/proposal-asset-references. i think there may have been another proposal similar to that one but i don't remember the specifics.
i don't think this is a bad idea but it is definitely an increase in the scope of esm and this proposal
I agree that it is outside of the scope of esm-integration. However, if a more general mechanism is created (in a different proposal), is there still a use case for "import as Module"? If not, then we can leave esm-integration as-is and toolchains can rely on the more general fetch mechanism instead.
Sorry if I am late, sort of lost track of the discussion.
I think the root observation in #44, which I've come around to agreeing with, is that: every core wasm module is, in practice, wrapped by JS glue code that wants to control the WebAssembly.instantiate() call itself and thus only the ESM-import-a-WebAssembly.Module feature that would be immediately practically adoptable.
+1, that is the case in Emscripten. We could easily adopt ESM integration if we still instantiate, but not otherwise.
Same in the case of Cheerp, WebAssembly modules' instances are in the general case stateful and tightly coupled to some JS state, so it would be handy only controlling instantiation (via import reflections for example).
This is now supported by the source phase in the current Phase 3 proposal.