Hello Wasm Js
Here's how to create sync and async function in Rust which return String, JsValue, JsError and call it from HTML, Javascript in test via headless browser.
Fun facts
-
🚧
println!not working withwasm-bindgen-testcontext because--nocapturewon't passthrough.💡 We have to use
console_log!inwasmcontext. -
🚧
println!("{js_error:#?}")andconsole_log!("{js_error:#?}")not working becauseJsErrordidn't implementDebug.💡 We have to convert
JsErrortoJsValueas workaround. -
🚧
#[tokio::test]will breakwasm-bindgen-test.💡 We need
#[cfg(not(target_arch = "wasm32"))]aboveRustcontext when needed.
Structure
Cargo.toml
[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["katopz <[email protected]>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.83"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.7", optional = true }
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
wee_alloc = { version = "0.4.5", optional = true }
# use for `async fn`.
wasm-bindgen-futures = { version = "0.4.33", default-features = false }
serde-wasm-bindgen = "0.6.1"
[dev-dependencies]
wasm-bindgen-test = "0.3.33"
# for conversion
serde-wasm-bindgen = "0.6.1"
[profile.release]
opt-level = "z" # Optimize for size.
strip = true # Automatically strip symbols from the binary.
lto = true # Enable Link Time Optimization (LTO)
codegen-units = 1 # Reduce Parallel Code Generation Units to Increase Optimization
panic = "abort" # Reduce panic code
utils.rs
#![allow(unused)] fn main() { pub fn set_panic_hook() { // When the `console_error_panic_hook` feature is enabled, we can call the // `set_panic_hook` function at least once during initialization, and then // we will get better error messages if our code ever panics. // // For more details see // https://github.com/rustwasm/console_error_panic_hook#readme #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); } }
lib.rs
#![allow(unused)] fn main() { mod utils; use utils::set_panic_hook; use wasm_bindgen::prelude::*; // Use `wee_alloc` as global allocator when feature is enabled #[cfg(feature = "wee_alloc")] #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; #[wasm_bindgen] pub fn greet(something: &str) -> String { // Hook when panic (optional) set_panic_hook(); // Return string format!("Hello {something}") } #[wasm_bindgen] pub async fn async_greet(something: &str) -> Result<String, JsError> { // Hook when panic (optional) set_panic_hook(); // Return result string Ok(format!("Hello {something}")) } #[wasm_bindgen] pub async fn async_greet_js_value(something: &str) -> Result<JsValue, JsError> { // Hook when panic (optional) set_panic_hook(); // Return result string Ok(JsValue::from_str(format!("Hello {something}").as_str())) } #[wasm_bindgen] pub fn greet_js_error() -> Result<JsValue, JsError> { let js_error = JsError::new("hello error!"); // `wasm_bindgen::JsError` doesn't implement `std::fmt::Debug` // 😱 uncomment below 👇 and you'll get the error 👆 // println!("{js_error:#?}"); Err(js_error) } }
tests/web.rs
#![allow(unused)] fn main() { //! Test suite for the Web and headless browsers. #![cfg(target_arch = "wasm32")] extern crate wasm_bindgen_test; use wasm_bindgen::*; use wasm_bindgen_test::*; // Running Tests in Headless Browsers wasm_bindgen_test_configure!(run_in_browser); // Import function to test. use hello_wasm::*; use serde_wasm_bindgen::from_value; #[wasm_bindgen_test] fn test_greet() { // Call greet and get an output. let output_string = greet("world"); // Validate. assert_eq!("Hello world".to_string(), output_string); } #[wasm_bindgen_test] async fn test_async_greet() { // Call greet and get an output. let output_string = async_greet("world").await.ok().unwrap(); console_log!("{output_string}"); // Validate. assert_eq!("Hello world".to_string(), output_string); } #[wasm_bindgen_test] async fn test_async_greet_js_value() { // Call greet and get an output. let output_value = async_greet_js_value("world").await.ok().unwrap(); // Validate. let output_string = from_value::<String>(output_value).ok().unwrap(); assert_eq!("Hello world".to_string(), output_string); } #[wasm_bindgen_test] async fn test_async_greet_js_error() { // Call greet and get an error. let js_error = greet_js_error(); // `wasm_bindgen::JsError` cannot be formatted using `{:?}` because it doesn't implement `Debug` // 😱 uncomment below 👇 will get above error 👆 // console_log!("{js_error:?}"); // Convert JsError to JsValue. let js_value = JsValue::from(js_error.err()); // And now we can Debug. console_log!("{js_value:?}"); // Validate. assert!(format!("{js_value:?}").contains("hello error!")); } }
tests/index.html
<script type="module">
import init, { greet, async_greet, async_greet_js_value } from './hello_wasm.js'
init().then(async () => {
const p1 = document.createElement('p')
p1.innerText = greet('world')
document.body.appendChild(p1)
let text = await async_greet('world')
const p2 = document.createElement('p')
p2.innerText = text
document.body.appendChild(p2)
let text_js_value = await async_greet_js_value('world')
const p3 = document.createElement('p')
p3.innerText = text_js_value
document.body.appendChild(p3)
})
</script>
To test
wasm-pack test --headless --firefox
cp ./tests/index.html ./pkg
npx live-server ./pkg