Hello world without statue
First of all, we will create a simple webpage that displays "Hello, <name>" with wasm_bindgen but without statue (and bundler). This will help you understand the basics of Rust and WebAssembly for browsers.
Go to an empty directory:
mkdir wb-hello-world
cd wb-hello-world
wb-hello-world/index.html
In this empty directory, create the index.html with the following contents:
<html>
  <head>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
  </head>
  <body>
    <script type="module">
      import init from './wb-hello-world/pkg/wb_hello_world.js';
      init();
    </script>
    <h1>Hello, friend!</h1>
    <input type="text" placeholder="Enter your name" id="name-input">
  </body>
</html>
This is a simple HTML file that has
- An ES6module that imports thehello_worldpackage and calls theinitfunction,
- An h1element that in the absence of JS or WASM displays"Hello, friend!",
- An inputelement that allows the user to enter their name.
If we were to run a tree command in the wb-hello-world directory, we would see the following:
index.html
wb-hello-world/wb-hello-world
Ensure that you have wasm-pack installed:
wasm-pack --version
With wasm-pack installed, create a new Rust WASM project:
wasm-pack new wb-hello-world
If you were to run a tree command in the wb-hello-world directory again, you would see the following:
index.html
wb-hello-world
    ├───src
    │   ├───lib.rs
    │   └───utils.rs
    ├───Cargo.toml
    .................
    └───tests
What matters to us is that the nested wb-hello-world directory is a Rust library that we can build into a WASM package.
wb-hello-world/wb-hello-world/Cargo.toml
Find the [dependencies] section in the Cargo.toml file of the Rust library:
[dependencies]
wasm-bindgen = "0.2.84"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.7", optional = true }
and add more dependencies:
[dependencies]
wasm-bindgen = "0.2.84"
console_error_panic_hook = { version = "0.1.7", optional = true }
[dependencies.web-sys]
version = "0.3.4"
features = [
  'Document',
  'Element',
  'HtmlElement',
  'HtmlInputElement',
  'Window',
  'EventTarget',
  'InputEvent'
]
Each of them will play a role in our project:
- wasm-bindgenwill allow us to expose Rust functions to JavaScript.
- console_error_panic_hookwill provide better debugging of panics, if any occur, by logging them with- console.error.
- web-syswill provide bindings to the Web APIs.
wb-hello-world/wb-hello-world/src/lib.rs
Replace the contents of the lib.rs file with the following:
#![allow(unused)] fn main() { mod utils; use utils::set_panic_hook; use wasm_bindgen::prelude::*; #[wasm_bindgen(start)] pub fn start() { set_panic_hook(); let window = web_sys::window().unwrap(); let document = window.document().unwrap(); let header = document.query_selector("h1").unwrap().unwrap(); let name_input = document.get_element_by_id("name-input").unwrap() .dyn_into::<web_sys::HtmlInputElement>().unwrap(); { let closure = Closure::<dyn FnMut(_)>::new(move |event: web_sys::InputEvent| { let input_element = event.target().unwrap().dyn_into::<web_sys::HtmlInputElement>().unwrap(); let name = input_element.value(); header.set_inner_html(&format!("Hello, {name}!")); }); name_input.add_event_listener_with_callback("input", closure.as_ref().unchecked_ref()).unwrap(); closure.forget(); } } }
This code does the following:
- Adds wb-hello-world/wb-hello-world/src/utils.rsas a module calledutils.
- Imports the set_panic_hookfunction from theutilsmodule.
- Imports the wasm_bindgenprelude.
- Defines a startfunction that will be called when the WASM module is loaded.
The start function does the following:
- Calls the set_panic_hookfunction to set up better debugging of panics.
- Gets the windowanddocumentobjects from the Web APIs.
- Queries the h1element and thename-inputelement. Sinceweb_sysdoesn't have a dedicated type forh1elements, we keep the type of theheadervariable asweb_sys::Element.
- Creates a closure that is then added as a listener to the inputevent on thename_inputelement. The closure updates theh1element with the text"Hello, <name>!"where<name>is the value of thename_inputelement. This pattern is described in the officialwasm-bindgenguide.
Build the project
Once you have the Cargo.toml and lib.rs files set up, build the project:
cd wb-hello-world
wasm-pack build --target web
cd ..
This will create a pkg directory in the wb-hello-world directory, and the pkg directory will contain the wb_hello_world.js file, among others.
Run the project
Note: You cannot directly open
index.htmlin 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 8080If 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