This document is a note for myself to how to initialize a WebAssembly project with Rust and wasm-pack.

Used Tools

  1. wasm-pack: For building and packaging Rust code as a WASM module.

Steps

First of all, we need to install the required wasm-pack following the official guide.

Then, we can create a new Rust lib project with wasm-pack:

wasm-pack new my-project

The project structure is almost the same as a normal Rust project. The only difference is that the src/lib.rs file is the entry point of the project. The following is the default content of the src/lib.rs file:

// src/lib.rs

mod utils;

use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[wasm_bindgen]
extern {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet() {
    alert("Hello, my-project!");
}

Then, we can build the project with wasm-pack build, e.g.,

wasm-pack build --target web

The build result is in the pkg directory. We can then copy the pkg directory to your web project and use it.

Using in a pure HTML+CSS+JS project

For example, if we are building a pure HTML+CSS+JS project with a Rust WASM module, we can build the module using the wasm-pack tool and then copy the resulting pkg directory to the working directory of our HTML+CSS+JS project. This pkg directory will contain the compiled WASM module along with the necessary JavaScript files to interact with it.

<html>
    <head>
        <!-- ... -->
        <script type="module" src="./wasm.js"></script>
    </head>
    <!-- ... -->
</html>
// wasm.js
import init, { greet } from "./pkg/my_project.js";

async function run() {
    await init("./pkg/my_project_bg.wasm");
    greet();
}

run();

Using the following command to start a simple HTTP server:

python -m http.server 8000

Then, we can visit the page at http://localhost:8000 and see the alert message.

Using in a Vue project

Vue project, we can call the greet function in the App.vue file:

<!-- App.vue -->

<template>
  <!-- ... -->
</template>

<script>
import { greet } from "./pkg/my_project.js";

export default {
  name: "App",
  methods: {
    async step1() {
      await init();
      greet();
    },
  },
  mounted() {
    this.step1();
  },
};
</script>

A More Complex Example

Assuming we have a function named calculation defined in the lib.rs file of a Rust WASM project, which takes a list of JSON objects and an integer as input, and returns a JSON Value as the result.


// ...
#[derive(Serialize, Deserialize, Debug)]
struct DataStruct {
    // ...
}

#[wasm_bindgen]
pub fn calculation(json_list: JsValue, num: usize) -> JsValue {
    // ...
    let pass_in_json:Vec<DataStruct> = match serde_wasm_bindgen::from_value(json_list) {
        Ok(v) => v,
        Err(e) => {
            log(&format!("Error: {:?}", e));
            return JsValue::NULL;
        }
    };

    // ...

    serde_wasm_bindgen::to_value(&result).unwrap()
}

Assuming the calculation function might take a while to produce the final result, we can use the async/await syntax to wait for the result to be computed before proceeding with the execution of other code.

<!-- App.vue -->

<template>
  <!-- ... -->
</template>

<script>
import { greet } from "./pkg/my_project.js";

export default {
  name: "App",
  // ...
  methods: {
    async step1() {
      await init();
      let result = calculation(this.json_list, this.num);
      // ...
    },
    step2(data) {
      //...
    },
  },
  mounted() {
    this.step1().then(() => {
      this.step2();
    });
  },
};
</script>