Skip to content

--browser doesn’t work for browsers #1326

Closed
@surma

Description

@surma

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.

cc @flaki @ashleygwilliams

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions