Frontend Development 16 min read

Integrating Rust with WebAssembly and JavaScript: A Step‑by‑Step Guide

This guide explains how to set up a Rust toolchain, compile Rust code to WebAssembly, use wasm‑bindgen and wasm‑pack, write the necessary Rust and JavaScript glue code, and run the resulting Wasm module in a web page with a Web Worker, providing a complete workflow for front‑end developers.

JD Tech Talk
JD Tech Talk
JD Tech Talk
Integrating Rust with WebAssembly and JavaScript: A Step‑by‑Step Guide

Purpose

The article aims to demonstrate a workflow for developing multi‑language programs using Rust, WebAssembly, JavaScript, HTML, and CSS; to design APIs that leverage the strengths of Rust/Wasm and JavaScript; and to show how to debug Wasm modules compiled from Rust.

What Is WebAssembly?

WebAssembly (Wasm) is a portable, compact binary format that runs at near‑native speed. It has a text representation .wat (S‑expression based) and a binary representation .wasm . Tools exist to convert between the two.

Environment Preparation

Install the standard Rust toolchain (rustup, rustc, cargo) following the official instructions at rust-lang.org/tools/install .

Learning Resources

Refer to the official Rust‑WebAssembly documentation and the wasm‑bindgen website for further reading.

Demo Project

The demo builds a Rust + JavaScript project that creates a WebAssembly module interacting with a Web Worker to determine whether a user‑entered number is even.

1. Install wasm‑pack

cargo install wasm-pack

2. Create a new Rust library

cargo new --lib my_demo
cd my_demo

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'] }

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

// Import necessary crates
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use web_sys::{console, HtmlElement, HtmlInputElement, MessageEvent, Worker};

#[wasm_bindgen]
pub struct NumberEval { number: i32 }

#[wasm_bindgen]
impl NumberEval {
    pub fn new() -> NumberEval { NumberEval { number: 0 } }
    pub fn is_even(&mut self, number: i32) -> bool { self.number = number; self.number % 2 == 0 }
    pub fn get_last_number(&self) -> i32 { self.number }
}

#[wasm_bindgen]
pub fn startup() {
    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);
}

fn setup_input_oninput_callback(worker: Rc
>) {
    let document = web_sys::window().unwrap().document().unwrap();
    let mut persistent_callback_handle = get_on_msg_callback();
    let closure = Closure::new(move || {
        console::log_1(&"oninput callback triggered".into());
        let document = web_sys::window().unwrap().document().unwrap();
        let input = document.get_element_by_id("inputNumber").expect("#inputNumber should exist").dyn_ref::
().expect("#inputNumber should be a HtmlInputElement");
        match input.value().parse::
() {
            Ok(num) => {
                let worker_ref = &*worker.borrow();
                let _ = worker_ref.post_message(&num.into());
                persistent_callback_handle = get_on_msg_callback();
                worker_ref.set_onmessage(Some(persistent_callback_handle.as_ref().unchecked_ref()));
            }
            Err(_) => {
                document.get_element_by_id("resultField").expect("#resultField should exist").dyn_ref::
().expect("#resultField should be a HtmlInputElement").set_inner_text("");
            }
        }
    });
    document.get_element_by_id("inputNumber").expect("#inputNumber should exist").dyn_ref::
().expect("#inputNumber should be a HtmlInputElement").set_oninput(Some(closure.as_ref().unchecked_ref()));
    closure.forget();
}

fn get_on_msg_callback() -> Closure
{
    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::
().expect("#resultField should be a HtmlInputElement").set_inner_text(result);
    })
}

5. Build the project

wasm-pack build --target no-modules

6. Use in a web page

Create index.html that loads the generated .wasm and JavaScript glue code.

<html>
<head>
    <meta charset="utf-8" />
    <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>

Write index.js to load the Wasm module and call startup() :

// index.js
const { startup } = wasm_bindgen;
async function run_wasm() {
    await wasm_bindgen();
    console.log('index.js loaded');
    startup();
}
run_wasm();

Write worker.js to receive numbers, evaluate evenness using the Rust struct, and post 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 result = num_eval.is_even(event.data);
        self.postMessage(result);
    };
}
init_wasm_in_worker();

7. Serve the files

Start a simple HTTP server, e.g., python3 -m http.server , and open http://localhost:8000 in a browser.

Application Scenarios

Rust + WebAssembly can be used for performance‑critical tasks, game development, cryptography, IoT/edge computing, desktop apps via Electron/Tauri, file compression, real‑time communication, custom renderers, porting existing Rust libraries, and as a safer alternative to legacy plugins.

Conclusion

The guide provides a practical workflow for integrating Rust‑compiled WebAssembly into JavaScript projects, highlighting performance, type safety, memory safety, concurrency, modern toolchains, ecosystem growth, and cross‑platform compatibility, while also noting learning‑curve and tooling challenges.

frontendJavaScriptRustWebAssemblywasm-pack
JD Tech Talk
Written by

JD Tech Talk

Official JD Tech public account delivering best practices and technology innovation.

0 followers
Reader feedback

How this landed with the community

login 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.