This document is a note for myself to how to initialize a WebAssembly project with Rust and wasm-pack.
Used Tools
- 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>