Course contentsShow
← Rust
Lesson 1840 of 1,91016. Specialized Runtime TargetsFree lesson

Wasm-Bindgen Architectural Patterns

Build clean interfaces between Rust logic and the browser's DOM using code-generation tools.

In this lesson, you will learn how to design clean, maintainable boundaries between Rust logic and the browser's DOM by leveraging code-generation patterns.

When building complex WebAssembly applications, it is tempting to pass raw JavaScript objects into Rust or manually manipulate the DOM from every function. This leads to "spaghetti glue" code that is hard to debug and even harder to refactor. The architectural pattern for scaling this is the Facade Pattern implemented via trait-based interfaces.

Instead of letting your domain logic know about the DOM, you define a trait representing the operations you need from the environment. You then implement that trait for a struct that interacts with the browser using wasm-bindgen.

Consider this clean architectural split:

// 1. The pure logic (Domain layer)
pub trait UIRenderer {
    fn update_status(&self, message: &str);
}

// 2. The Wasm-Bindgen glue (Infrastructure layer)
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct BrowserRenderer;

impl UIRenderer for BrowserRenderer {
    fn update_status(&self, message: &str) {
        let window = web_sys::window().unwrap();
        let document = window.document().unwrap();
        let element = document.get_element_by_id("status").unwrap();
        element.set_inner_text(message);
    }
}

By decoupling the logic from the specific web-sys calls, you keep your business rules testable in pure Rust environments. The wasm-bindgen code acts merely as a thin translation layer—a "driver"—that bridges the gap between your Rust types and the browser's JavaScript objects.

When you need to interact with the DOM, avoid passing web_sys::HtmlElement instances deep into your business logic. Instead, map these complex types to simple Rust enums or structs that represent the "state" of your UI. Use code generation via #[wasm_bindgen] to expose only the necessary entry points, treating the rest of your application as a standard Rust crate that has no idea it is running in a browser. This inversion of control ensures that your Rust core remains robust, regardless of how frequently the underlying DOM API changes.

Key Takeaway: Decouple your business logic from the DOM by using trait-based interfaces, treating wasm-bindgen strictly as an infrastructure layer rather than an integral part of your domain models.