Description
Motivation
The JavaScript generated when using --browser
is actually not usable in a browser directly, but has to be processed by a bundler. They problem lies in this import in the JavaScript:
import * as wasm from './mymodule';
Firstly, the imported file is missing the .wasm
file extension, which is required for import statements in the browser. Secondly, importing Wasm modules via ES6 import
statements is not support (but there is a proposal). Out of the box, only Webpack supports this kind of import. This has lead to the impression that wasm-bindgen
& wasm-pack
require Webpack.
If I don’t use Webpack I either have to teach my bundler about this kind of non-standard import or switch to --no-modules
, which generates code that works on a global variable. Globals are notoriously messy to reconcile with bundlers.
If I want to work without a bundler, --no-modules
is a decent choice, but if I have multiple, independent Wasm modules generated with wasm_bindgen
, these variables are going to clash.
I propose that the introduction of a new target --esm
.
(I am assuming that the browser
target cannot be renamed at this point. If it can, I’d rename --browser
to --webpack
in addition to introducing --esm
.)
Proposed Solution
There is a middle ground in the approaches between the browser
and the no-modules
target. The --esm
code could expose an async factory function that instantiates the Wasm module with the vanilla Wasm APIs and then returns an object with the wrapped functions. The code could look something like this (I’m obviously skipping and missing some details here, but happy to work on that with y’all if desired):
// mymodule.mjs
/*
* ...
* Unexported utility functions like
* `passArray8ToWasm`, `getArrayU8FromWasm` etc etc
* ...
*/
export function async instantiate(pathOrBuffer) {
let instance;
const imports = /* create imports object */
// Feature detection of loading mechanism
if(typeof pathOrBuffer === 'string' && WebAssembly.instantiateStreaming) {
({instance} = await WebAssembly.instantiateStreaming(fetch(pathOrBuffer), imports));
} else {
if(typeof pathOrBuffer === 'string') {
pathOrBuffer = await fetch(pathOrBuffer).then(r => r.arrayBuffer());
}
({instance} = await WebAssembly.instantiate(pathOrBuffer, imports));
}
return {
/*
* ...
* wrapped module functions
* ...
*/
// This is just an idea how the raw instance could be exposed
// without the risk of name clashes.
[rawInstance]: instance,
}
}
const rawInstance = new Symbol();
export rawInstance;
The upside of this code is that it works in browsers and Node out of the box. Also note the .mjs
file extension which is necessary for module code in Node.