How to Build Rust‑Powered WebAssembly Apps that Run in the Browser

This guide walks you through the complete workflow of writing Rust code, compiling it to WebAssembly, and integrating the resulting module with JavaScript and HTML to create a browser‑based demo that checks whether a number is even, while covering setup, tooling, and best‑practice tips.

JD Cloud Developers
JD Cloud Developers
JD Cloud Developers
How to Build Rust‑Powered WebAssembly Apps that Run in the Browser

Purpose of the guide

Show a workflow that combines Rust, WebAssembly, JavaScript, HTML and CSS to build multi‑language programs.

Explain how to design APIs that exploit the strengths of Rust/WebAssembly and JavaScript.

Demonstrate debugging of WebAssembly modules compiled from Rust.

What is WebAssembly?

WebAssembly (wasm) is a portable, compact binary format that runs at near‑native speed. It has two representations: the human‑readable .wat text format (S‑expression syntax) and the binary .wasm format, which can be converted back and forth with standard tools.

Environment preparation

You need the standard Rust toolchain: rustup, rustc and cargo. Installation instructions are available at https://www.rust-lang.org/tools/install.

Learning resources

Rust and WebAssembly documentation.

Official wasm-bindgen website.

Demo project

The following Rust + JavaScript project demonstrates a WebAssembly module that receives a number from a web page, determines if it is even, and displays the result.

1. Install wasm-pack

cargo install wasm-pack

2. Create a new Rust library

cargo new --lib my_demo
cd my_demo

Directory layout after creation:

3. Configure Cargo.toml

[package]
authors = ["The wasm-demo Developers"]
edition = "2024"
name = "wasm-in-web-worker"
publish = false
version = "0.0.0"

[lib]
crate-type = ["cdylib"]

[dependencies]
console_error_panic_hook = { version = "0.1.6", optional = true }
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ['console','Document','HtmlElement','HtmlInputElement','MessageEvent','Window','Worker'] }

Enable the required web-sys features in the features section as needed.

4. Write the Rust code (src/lib.rs)

// Import standard library and wasm‑bindgen modules
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use web_sys::{console, HtmlElement, HtmlInputElement, MessageEvent, Worker};

// Define a struct that stores a number and can test evenness
#[wasm_bindgen]
pub struct NumberEval {
    number: i32,
}

#[wasm_bindgen]
impl NumberEval {
    // Create a new instance with number = 0
    pub fn new() -> NumberEval {
        NumberEval { number: 0 }
    }

    // Store the provided number and return true if it is even
    pub fn is_even(&mut self, number: i32) -> bool {
        self.number = number;
        self.number % 2 == 0
    }

    // Return the last stored number
    pub fn get_last_number(&self) -> i32 {
        self.number
    }
}

// Entry point called when the Wasm module loads
#[wasm_bindgen]
pub fn startup() {
    // Create a Web Worker instance
    let worker_handle = Rc::new(RefCell::new(Worker::new("./worker.js").unwrap()));
    console::log_1(&"Created a new worker from within Wasm".into());
    setup_input_oninput_callback(worker_handle);
}

// Set up the oninput callback for the text field
fn setup_input_oninput_callback(worker: Rc<RefCell<web_sys::Worker>>) {
    let document = web_sys::window().unwrap().document().unwrap();
    #[allow(unused_assignments)]
    let mut persistent_callback_handle = get_on_msg_callback();
    let callback = Closure::new(move || {
        console::log_1(&"oninput callback triggered".into());
        let document = web_sys::window().unwrap().document().unwrap();
        let input_field = document
            .get_element_by_id("inputNumber")
            .expect("#inputNumber should exist")
            .dyn_ref::<HtmlInputElement>()
            .expect("#inputNumber should be a HtmlInputElement");
        match input_field.value().parse::<i32>() {
            Ok(number) => {
                let worker_handle = &*worker.borrow();
                let _ = worker_handle.post_message(&number.into());
                persistent_callback_handle = get_on_msg_callback();
                worker_handle.set_onmessage(Some(persistent_callback_handle.as_ref().unchecked_ref()));
            }
            Err(_) => {
                document
                    .get_element_by_id("resultField")
                    .expect("#resultField should exist")
                    .dyn_ref::<HtmlElement>()
                    .expect("#resultField should be a HtmlInputElement")
                    .set_inner_text("");
            }
        }
    });
    document
        .get_element_by_id("inputNumber")
        .expect("#inputNumber should exist")
        .dyn_ref::<HtmlInputElement>()
        .expect("#inputNumber should be a HtmlInputElement")
        .set_oninput(Some(callback.as_ref().unchecked_ref()));
    callback.forget();
}

// Create a callback that handles messages from the worker
fn get_on_msg_callback() -> Closure<dyn FnMut(MessageEvent)> {
    Closure::new(move |event: MessageEvent| {
        console::log_2(&"Received response: ".into(), &event.data());
        let result = if event.data().as_bool().unwrap() { "even" } else { "odd" };
        let document = web_sys::window().unwrap().document().unwrap();
        document
            .get_element_by_id("resultField")
            .expect("#resultField should exist")
            .dyn_ref::<HtmlElement>()
            .expect("#resultField should be a HtmlInputElement")
            .set_inner_text(result);
    })
}

5. Build the project

wasm-pack build --target no-modules

Typical --target options are shown below:

6. Use the generated Wasm in a web page

Create index.html and include the compiled .wasm bundle:

<html>
<head>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="wrapper">
        <h1>Interaction with Wasm Web Worker</h1>
        <input type="text" id="inputNumber">
        <div id="resultField"></div>
    </div>
    <script src='./pkg/wasm_in_web_worker.js'></script>
    <script src="./index.js"></script>
</body>
</html>

JavaScript glue code ( index.js) loads the Wasm module and calls the exported startup function:

// index.js
const { startup } = wasm_bindgen;
async function run_wasm() {
    await wasm_bindgen(); // load the Wasm file
    console.log('index.js loaded');
    startup(); // create the worker and set up callbacks
}
run_wasm();

The worker script ( worker.js) receives numbers, uses the Rust struct to test evenness, and posts the result back:

// worker.js
importScripts('./pkg/wasm_in_web_worker.js');
console.log('Initializing worker');
const { NumberEval } = wasm_bindgen;
async function init_wasm_in_worker() {
    await wasm_bindgen('./pkg/wasm_in_web_worker_bg.wasm');
    const num_eval = NumberEval.new();
    self.onmessage = async event => {
        const worker_result = num_eval.is_even(event.data);
        self.postMessage(worker_result);
    };
}
init_wasm_in_worker();

7. Serve the files

Start any HTTP server, for example with Python: python3 -m http.server Open http://localhost:8000 in a browser to see the demo.

8. Project directory overview

9. Related tools

wasm-bindgen

: bridges Rust and JavaScript, allowing high‑level interaction. wasm-bindgen-futures: connects Rust futures with JavaScript promises. js-sys: raw bindings to JavaScript global objects (e.g., Object, Function). web-sys: raw bindings to Web APIs such as DOM manipulation, timers, WebGL, etc.

10. Application scenarios

Performance‑intensive tasks (image/video processing, large‑scale data analysis, ML inference).

Game development – porting engines or writing custom game logic.

Cryptography and security – memory‑safe Rust code for client‑side encryption.

IoT and edge computing – running Wasm on supported devices.

Desktop apps via Electron or Tauri.

File compression/decompression.

Real‑time communication with low latency.

Custom renderers for graphics editors or data visualisation.

Porting existing Rust libraries to the web.

Replacing legacy plugins (Flash, Java applets) with Wasm.

Conclusion

The guide provides a practical path for integrating Rust‑compiled WebAssembly into JavaScript projects, highlighting performance, safety, and concurrency benefits while acknowledging the learning curve, tooling integration challenges, and debugging limitations.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptRustWebAssemblywasm-pack
JD Cloud Developers
Written by

JD Cloud Developers

JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.