Synchronous Instantiation

View full source code

This example shows how to synchronously initialize a WebAssembly module as opposed to asynchronously. In most cases, the default way of asynchronously initializing a module will suffice. However, there might be use cases where you'd like to lazy load a module on demand and synchronously compile and instantiate it. Note that this only works off the main thread and since compilation and instantiation of large modules can be expensive you should only use this method if it's absolutely required in your use case. Otherwise you should use the default method.

For this deployment strategy bundlers like Webpack are not required. For more information on deployment see the dedicated documentation.

First let's take a look at our tiny lib:


# #![allow(unused_variables)]
#fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(value: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    log(&format!("Hello, {}!", name));
}

#}

Next, let's have a look at the index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      /**
       * First off we spawn a Web Worker. That's where our lib will be used. Note that
       * we set the `type` to `module` to enable support for ES modules.
       */
      const worker = new Worker("/worker.js", { type: "module" });

      /**
       * Here we listen for messages from the worker.
       */
      worker.onmessage = ({ data }) => {
        const { type } = data;

        switch (type) {
          case "FETCH_WASM": {
            /**
             * The worker wants to fetch the bytes for the module and for that we can use the `fetch` API.
             * Then we convert the response into an `ArrayBuffer` and transfer the bytes back to the worker.
             *
             * @see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
             * @see https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects
             */
            fetch("/pkg/synchronous_instantiation_bg.wasm")
              .then((response) => response.arrayBuffer())
              .then((bytes) => {
                worker.postMessage(bytes, [bytes]);
              });
            break;
          }
          default: {
            break;
          }
        }
      };
    </script>
  </body>
</html>

Otherwise the rest of the magic happens in worker.js:

import * as wasm from "./pkg/synchronous_instantiation.js";

self.onmessage = ({ data: bytes }) => {
  /**
   * When we receive the bytes as an `ArrayBuffer` we can use that to
   * synchronously initialize the module as opposed to asynchronously
   * via the default export. The synchronous method internally uses
   * `new WebAssembly.Module()` and `new WebAssembly.Instance()`.
   */
  wasm.initSync(bytes);

  /**
   * Once initialized we can call our exported `greet()` functions.
   */
  wasm.greet("Dominic");
};

/**
 * Once the Web Worker was spawned we ask the main thread to fetch the bytes
 * for the WebAssembly module. Once fetched it will send the bytes back via
 * a `postMessage` (see above).
 */
self.postMessage({ type: "FETCH_WASM" });

And that's it! Be sure to read up on the deployment options to see what it means to deploy without a bundler.