Without a Bundler - The `wasm-bindgen` Guide

# Without a Bundler

View full source code

This example shows how the `--target web` flag can be used load code in a browser directly. For this deployment strategy bundlers like Webpack are not required. For more information on deployment see the dedicated documentation.

First, you'll need to add `web-sys` to your Cargo.toml.

    [dependencies.web-sys]
    version = "0.3.4"
    features = [
      'Document',
      'Element',
      'HtmlElement',
      'Node',
      'Window',
    ]

Then, let's take a look at the code and see how when we're using `--target web` we're not actually losing any functionality!

    use wasm_bindgen::prelude::*;

    // Called when the Wasm module is instantiated
    #[wasm_bindgen(start)]
    fn main() -> Result<(), JsValue> {
        // Use `web_sys`'s global `window` function to get a handle on the global
        // window object.
        let window = web_sys::window().expect("no global `window` exists");
        let document = window.document().expect("should have a document on window");
        let body = document.body().expect("document should have a body");

        // Manufacture the element we're gonna append
        let val = document.create_element("p")?;
        val.set_inner_html("Hello from Rust!");

        body.append_child(&val)?;

        Ok(())
    }

    #[wasm_bindgen]
    pub fn add(a: u32, b: u32) -> u32 {
        a + b
    }

Otherwise the rest of the deployment magic happens in `index.html`:

    <html>
      <head>
        <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
      </head>
      <body>
        <!-- Note the usage of `type=module` here as this is an ES6 module -->
        <script type="module">
          // Use ES module import syntax to import functionality from the module
          // that we have compiled.
          //
          // Note that the `default` import is an initialization function which
          // will "boot" the module and make it ready to use. Currently browsers
          // don't support natively imported WebAssembly as an ES module, but
          // eventually the manual initialization won't be required!
          import init, { add } from './pkg/without_a_bundler.js';

          async function run() {
            // First up we need to actually load the Wasm file, so we use the
            // default export to inform it where the Wasm file is located on the
            // server, and then we wait on the returned promise to wait for the
            // Wasm to be loaded.
            //
            // It may look like this: `await init('./pkg/without_a_bundler_bg.wasm');`,
            // but there is also a handy default inside `init` function, which uses
            // `import.meta` to locate the Wasm file relatively to js file.
            //
            // Note that instead of a string you can also pass in any of the
            // following things:
            //
            // * `WebAssembly.Module`
            //
            // * `ArrayBuffer`
            //
            // * `Response`
            //
            // * `Promise` which returns any of the above, e.g. `fetch("./path/to/wasm")`
            //
            // This gives you complete control over how the module is loaded
            // and compiled.
            //
            // Also note that the promise, when resolved, yields the Wasm module's
            // exports which is the same as importing the `*_bg` module in other
            // modes
            await init();

            // And afterwards we can use all the functionality defined in wasm.
            const result = add(1, 2);
            console.log(`1 + 2 = ${result}`);
            if (result !== 3)
              throw new Error("wasm addition doesn't work!");
          }

          run();
        </script>
      </body>
    </html>

> Note: You cannot directly open `index.html` in your web browser due to CORS limitations. Instead, you can set up a quick development environment using Python's built-in HTTP server:

>     wasm-pack build --target web
>     python3 -m http.server 8080

> If you don't have Python installed, you can also use miniserve which is installable via Cargo:

>     cargo install miniserve
>     miniserve . --index "index.html" -p 8080

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

## Using the older `--target no-modules`

View full source code

The older version of using `wasm-bindgen` without a bundler is to use the `--target no-modules` flag to the `wasm-bindgen` CLI.

While similar to the newer `--target web`, the `--target no-modules` flag has a few caveats:

- It does not support local JS snippets
- It does not generate an ES module
- It does not support `--split-linked-modules` outside of a document, e.g. inside a worker

With that in mind the main difference is how the wasm/JS code is loaded, and here's an example of loading the output of `wasm-pack` for the same module as above.

    <html>
      <head>
        <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
      </head>
      <body>
        <!-- Include the JS generated by `wasm-pack build` -->
        <script src='pkg/without_a_bundler_no_modules.js'></script>

        <script>
          // Like with the `--target web` output the exports are immediately
          // available but they won't work until we initialize the module. Unlike
          // `--target web`, however, the globals are all stored on a
          // `wasm_bindgen` global. The global itself is the initialization
          // function and then the properties of the global are all the exported
          // functions.
          //
          // Note that the name `wasm_bindgen` can be configured with the
          // `--no-modules-global` CLI flag
          const { add } = wasm_bindgen;

          async function run() {
            await wasm_bindgen();

            const result = add(1, 2);
            console.log(`1 + 2 = ${result}`);
          }

          run();
        </script>
      </body>
    </html>