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>