Here is a minimal example.
Make a new project. Use these files:
lib.rs:
use std::sync::Arc;
use js_sys::Promise;
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
extern crate web_sys;
// A macro to provide `println!(..)`-style syntax for `console.log` logging.
#[macro_export]
macro_rules! console_log {
( $( $t:tt )* ) => {
web_sys::console::log_1(&format!( $( $t )* ).into());
}
}
#[wasm_bindgen]
pub struct MyApi {
pub(crate) api: Arc<tokio::sync::RwLock<ApiImpl>>,
}
#[wasm_bindgen]
pub struct ApiImpl {
pub(crate) name: String,
pub(crate) some_stuff: Arc<tokio::sync::RwLock<u32>>,
}
#[wasm_bindgen]
impl ApiImpl {
pub fn new(name: String) -> Self {
Self {
name,
some_stuff: Arc::new(RwLock::new(0)),
}
}
pub fn get_name(&self) -> String {
self.name.clone()
}
pub async fn do_stuff(&self) -> u32 {
let mut some_stuff = self.some_stuff.write().await;
*some_stuff += 1;
*some_stuff
}
pub async fn get_stuff(&self) -> u32 {
let some_stuff = self.some_stuff.read().await;
*some_stuff
}
pub fn add_game_to_cart(
&mut self,
game_item_dbid: Gid,
// game_group_dbid: Option<Gid>,
// source: PlayerActionSource,
) -> Result<String, String> {
if game_item_dbid.data == 0 {
Err("game_group_dbid is none".to_string())
} else {
Ok("ok".to_string())
}
}
}
#[wasm_bindgen]
impl MyApi {
pub fn new(name: String) -> Self {
Self {
api: Arc::new(RwLock::new(ApiImpl::new(name))),
}
}
pub async fn get_name(&self) -> String {
let api = self.api.read().await;
api.get_name()
}
pub async fn do_stuff(&self) -> u32 {
let mut api = self.api.write().await;
api.do_stuff().await
}
pub async fn get_stuff(&self) -> u32 {
let api = self.api.read().await;
console_log!("get_stuff");
api.get_stuff().await
}
pub fn add_game_to_cart(
&mut self,
game_item_dbid: Gid,
// game_group_dbid: Option<Gid>,
// source: PlayerActionSource,
) -> Promise {
console_log!("add_game_to_cart");
let api = self.api.clone();
console_log!("add_game_to_cart");
future_to_promise(async move {
let mut api = api.write().await;
let result = api
.add_game_to_cart(game_item_dbid) //, game_group_dbid, source)
.to_js_result()?;
let serializer = serde_wasm_bindgen::Serializer::json_compatible();
let result = result.serialize(&serializer);
Ok(result?)
})
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, tsify::Tsify)]
#[tsify(from_wasm_abi)]
#[serde(tag = "kind", content = "payload")]
#[serde(rename_all = "snake_case")]
pub enum PlayerActionSource {
Unknown,
Aig,
Navigation,
Search { search_string: String },
}
#[derive(
Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Clone, serde::Serialize, serde::Deserialize,
)]
#[cfg_attr(
any(target_arch = "wasm32", target_os = "macos"),
derive(tsify::Tsify),
tsify(from_wasm_abi, into_wasm_abi)
)]
pub struct Gid {
pub data: u128,
}
use wasm_bindgen::JsValue;
pub type JsResult<T> = Result<T, JSError>;
pub trait ToJSResultTrait<T>: Sized {
fn to_js_result(self) -> JsResult<T>;
fn to_js_result_msg(self, msg: &str) -> JsResult<T> {
match self.to_js_result() {
Ok(value) => Ok(value),
Err(err) => Err(JSError {
message: format!("{}: {}", msg, err.message),
}),
}
}
}
impl<T> ToJSResultTrait<T> for Option<T> {
fn to_js_result(self) -> JsResult<T> {
match self {
Some(value) => Ok(value),
None => Err(JSError {
message: "Option is None".to_string(),
}),
}
}
}
impl<T> ToJSResultTrait<T> for Result<T, String> {
fn to_js_result(self) -> JsResult<T> {
match self {
Ok(value) => Ok(value),
Err(err) => Err(JSError { message: err }),
}
}
}
impl<T> ToJSResultTrait<T> for Result<T, reqwest::Error> {
fn to_js_result(self) -> JsResult<T> {
match self {
Ok(value) => Ok(value),
Err(err) => Err(JSError {
message: err.to_string(),
}),
}
}
}
impl From<JSError> for JsValue {
fn from(error: JSError) -> JsValue {
serde_wasm_bindgen::to_value(&error).unwrap()
}
}
use wasm_bindgen::prelude::wasm_bindgen;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, tsify::Tsify)]
#[tsify(from_wasm_abi)]
pub struct JSError {
pub message: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, tsify::Tsify)]
#[tsify(from_wasm_abi)]
pub struct JSAnalyticItem {
pub order_id: String,
pub action_type: String,
pub test_id: Option<i16>,
pub object: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, tsify::Tsify)]
#[tsify(from_wasm_abi)]
#[serde(tag = "variant", content = "data")]
#[serde(rename_all = "snake_case")]
pub enum JSStreamStatus {
Start,
Retry(i8),
GiveUp,
Error(String),
}
Cargo.toml
[workspace]
resolver = "2"
[package]
name = "learn-rust-wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = "0.1.77"
cfg-if = "1.0.0"
futures = "0.3.30"
getrandom = {version = "0.2.8", features = ["js"] }
rand = "0.8.5"
reqwest = "0.11.23"
serde = "1.0.195"
serde-wasm-bindgen = "0.6.3"
serde_json = "1.0.111"
serde_yaml = "0.9.30"
tokio = { version = "1.35.1", features = ["macros", "rt", "sync"] }
tokio-stream = "0.1.14"
tracing = "0.1.40"
tsify = "0.4.5"
wasm-bindgen = "0.2.90"
wasm-bindgen-futures = "0.4.40"
console_error_panic_hook = { version = "0.1.6", optional = true }
wee_alloc = { version = "0.4.5", optional = true }
js-sys = "0.3.67"
tracing-wasm = "0.2.1"
wasm-streams = "0.4.0"
ws_stream_wasm = "0.7.4"
web-sys = {version = "0.3", features = [ "console", "ReadableStream", "BinaryType", "Blob", "ErrorEvent", "FileReader", "MessageEvent", "ProgressEvent", "WebSocket", ]}
.cargo/config.toml
[build]
target = "wasm32-unknown-unknown"
rustflags = ["--cfg", "tokio_unstable"]
rustdocflags = ["--cfg", "tokio_unstable"]
web/index.js
import { MyApi } from "learn_rust_wasm";
export async function debug_all() {
// console.log('----------------- debug_wasm() -----------------');
// await debug_wasm();
}
export function getApi() {
let api = MyApi.new("lol");
debugger;
return api;
}
let x = getApi();
window.api = x;
console.log(await window.api.get_name());
console.log(await window.api.do_stuff());
web/bootstrap.js
// A dependency graph that contains any wasm must all be imported
// asynchronously. This `bootstrap.js` file does the single async import, so
// that no one else needs to worry about it again.
import("./index.js")
.catch(e => console.error("Error importing `index.js`:", e));
let api = import("learn_rust_wasm");
web/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello wasm-pack!</title>
</head>
<body>
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
<script src="./bootstrap.js"></script>
</body>
</html>
web/package.json
{
"name": "create-wasm-app",
"version": "0.1.0",
"description": "create an app to consume rust-generated wasm packages",
"main": "index.js",
"bin": {
"create-wasm-app": ".bin/create-wasm-app.js"
},
"scripts": {
"build": "webpack --config webpack.config.js",
"start": "webpack-dev-server --mode development"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rustwasm/create-wasm-app.git"
},
"keywords": [
"webassembly",
"wasm",
"rust",
"webpack"
],
"author": "Ashley Williams <[email protected]>",
"license": "(MIT OR Apache-2.0)",
"bugs": {
"url": "https://github.com/rustwasm/create-wasm-app/issues"
},
"homepage": "https://github.com/rustwasm/create-wasm-app#readme",
"devDependencies": {
"copy-webpack-plugin": "^5.0.0",
"hello-wasm-pack": "^0.1.0",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"learn_rust_wasm": "file:../pkg"
}
}
web/webpack.config.js
const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require('path');
module.exports = {
entry: "./bootstrap.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bootstrap.js",
},
mode: "development",
plugins: [
new CopyWebpackPlugin(['index.html'])
],
experiments: {
asyncWebAssembly: true,
},
devtool: 'source-map'
};
then run:
wasm-pack build; (cd web && npm i && npm run start)
When you go to http://localhost:8080
and then run this, it will fail in this way:
>>> window.api.add_game_to_cart({})
Uncaught Error: missing field `data` at line 1 column 2
__wbindgen_throw learn_rust_wasm_bg.js:537
add_game_to_cart learn_rust_wasm_bg.js:384
<anonymous> debugger eval code:1
[learn_rust_wasm_bg.js:537](webpack://create-wasm-app/pkg/learn_rust_wasm_bg.js)
__wbindgen_throw learn_rust_wasm_bg.js:537
<anonymous> 3456e6f9d69608745039.module.wasm:91904
<anonymous> 3456e6f9d69608745039.module.wasm:65348
add_game_to_cart learn_rust_wasm_bg.js:384
<anonymous> debugger eval code:1
>>> window.api.add_game_to_cart({})
Uncaught Error: recursive use of an object detected which would lead to unsafe aliasing in rust
__wbindgen_throw learn_rust_wasm_bg.js:537
add_game_to_cart learn_rust_wasm_bg.js:384
<anonymous> debugger eval code:1
[learn_rust_wasm_bg.js:537](webpack://create-wasm-app/pkg/learn_rust_wasm_bg.js)
>>> window.api.get_stuff({})
Uncaught Error: recursive use of an object detected which would lead to unsafe aliasing in rust
__wbindgen_throw learn_rust_wasm_bg.js:537
__wbg_adapter_22 learn_rust_wasm_bg.js:216
real learn_rust_wasm_bg.js:201
__wbg_queueMicrotask_118eeb525d584d9a learn_rust_wasm_bg.js:437
__wbg_adapter_49 learn_rust_wasm_bg.js:227
cb0 learn_rust_wasm_bg.js:501
__wbg_new_1d93771b84541aa5 learn_rust_wasm_bg.js:506
get_stuff learn_rust_wasm_bg.js:376
<anonymous> debugger eval code:1
[learn_rust_wasm_bg.js:537](webpack://create-wasm-app/pkg/learn_rust_wasm_bg.js)