WebAssembly (WASM) Integration
Propa facilitates the integration of WebAssembly (WASM) modules, allowing you to run high-performance code (e.g., written in Rust, C++, Go) directly in the browser. This is particularly useful for computationally intensive tasks.
Propa provides WasmModule and TypedWasmModule classes, along with helper functions loadWasm and loadTypedWasm, to simplify loading and interacting with WASM modules.
Building Your WASM Module
Typically, you'll use tools like wasm-pack for Rust or Emscripten for C/C++ to compile your code to WebAssembly. For this guide, we'll focus on a Rust example using wasm-pack.
1. Rust Project Setup (wasm/Cargo.toml) Ensure wasm-bindgen is a dependency:
[package]
name = "my-wasm-lib"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
[dependencies.web-sys]
version = "0.3"
features = [
"console",
]2. Rust Code (wasm/src/lib.rs)
use wasm_bindgen::prelude::*;
// Optional: For logging from Rust to the browser console
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
log(&format!("Rust says: Adding {} and {}", a, b));
a + b
}
#[wasm_bindgen]
pub fn concatenate_strings(s1: &str, s2: &str) -> String {
format!("{} {}", s1, s2)
}
#[wasm_bindgen]
pub fn sum_array(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}3. Build with wasm-pack Navigate to your Rust WASM project directory (wasm/) and run:
wasm-pack build --target web --out-dir pkgThis command generates:
- A
.wasmfile. - A JavaScript glue file (
.js). - TypeScript definitions (
.d.ts) in thepkgdirectory.
These generated files allow for type-safe interaction from your TypeScript code.
Integrating WASM in Propa
You can use loadTypedWasm for a type-safe experience, leveraging the TypeScript definitions generated by wasm-pack.
TypeScript Integration (.tsx component):
import { h, ComponentLifecycle, reactive } from '@salernoelia/propa';
// Import the init function and exported functions/types from your WASM package
// The path '../../wasm/pkg/wasm' assumes your 'pkg' directory is two levels up.
// Adjust the path based on your project structure.
// The '.js' extension might be omitted depending on your module resolver setup.
import initWasmModule, { add, concatenate_strings, sum_array } from '../../wasm/pkg/my_wasm_lib'; // Or ...pkg/wasm if that's the output name
// (Optional but recommended) Define an interface for your WASM module's exports
// This can be useful if you don't import functions directly or for more complex scenarios.
// However, with direct imports from the wasm-pack output, this might be redundant.
interface MyWasmExports {
add: (a: number, b: number) => number;
concatenate_strings: (s1: string, s2: string) => string;
sum_array: (numbers: Int32Array) => number; // Note: wasm-bindgen often maps slices to TypedArrays
}
function WasmCalculator() {
const result = reactive('Loading WASM...');
const wasmReady = reactive(false);
// Store a reference to the typed module if needed, or use imported functions directly
// let wasmInstance: MyWasmExports | null = null; // Example if not using direct imports
ComponentLifecycle.onMount(async () => {
try {
// Initialize the WASM module.
// The init function is usually the default export from the JS glue file.
await initWasmModule(); // This loads and compiles the .wasm file
// At this point, the imported functions (add, concatenate_strings) are ready to use.
wasmReady.value = true;
console.log('WASM module loaded successfully!');
// Perform calculations using the directly imported WASM functions
const sum = add(10, 20);
const combined = concatenate_strings("Hello", "WASM!");
const arrSum = sum_array(new Int32Array([1, 2, 3, 4]));
result.value = `Sum: ${sum}, Combined: "${combined}", Array Sum: ${arrSum}`;
} catch (error) {
console.error('Failed to load WASM module:', error);
const errorMessage = error instanceof Error ? error.message : String(error);
result.value = `Error loading WASM: ${errorMessage}`;
}
});
const performRandomAdd = () => {
if (wasmReady.value) {
const num1 = Math.floor(Math.random() * 100);
const num2 = Math.floor(Math.random() * 100);
const sum = add(num1, num2); // Use directly imported function
result.value = `Random sum: ${num1} + ${num2} = ${sum}`;
} else {
result.value = 'WASM not ready!';
}
};
return (
<div>
<h3>WASM Status: {wasmReady.value ? 'Ready' : 'Loading...'}</h3>
<p>Result: {result}</p>
<button onClick={performRandomAdd} disabled={!wasmReady.value}>
Calculate Random Sum
</button>
</div>
);
}
// To use this component:
// const appRoot = document.getElementById('app');
// if (appRoot) appRoot.appendChild(<WasmCalculator />);Using loadTypedWasm (Alternative)
If you prefer to use Propa's loadTypedWasm helper (perhaps for modules not generated by wasm-pack or for a more centralized loading pattern), you would do it like this:
import { h, ComponentLifecycle, reactive, loadTypedWasm } from '@salernoelia/propa';
// Define the interface for your WASM module functions for type safety
interface MyWasmModule {
add: (a: number, b: number) => number;
concatenate_strings: (s1: string, s2: string) => string;
// Add other WASM functions here
}
function WasmCalculatorWithHelper() {
const result = reactive('Loading WASM...');
const wasmReady = reactive(false);
let wasmApi: MyWasmModule | null = null;
ComponentLifecycle.onMount(async () => {
try {
// Path to the JS glue file generated by wasm-pack (or your WASM tool)
// This path needs to be resolvable by Vite's import mechanism.
// For wasm-pack, it's typically 'path/to/your/pkg/your_wasm_lib.js'
const wasmModule = await loadTypedWasm<MyWasmModule>('../../wasm/pkg/my_wasm_lib.js');
wasmApi = wasmModule.getTypedModule(); // Access the typed functions
wasmReady.value = true;
console.log('WASM module loaded successfully using loadTypedWasm!');
if (wasmApi) {
const sum = wasmApi.add(10, 20);
const combined = wasmApi.concatenate_strings("Hello", "Propa!");
result.value = `Sum: ${sum}, Combined: "${combined}"`;
}
} catch (error) {
console.error('Failed to load WASM module:', error);
result.value = `Error loading WASM: ${error instanceof Error ? error.message : String(error)}`;
}
});
// ... rest of the component ...
return (
<div>
<h3>WASM Status (Helper): {wasmReady.value ? 'Ready' : 'Loading...'}</h3>
<p>Result: {result}</p>
{/* ... buttons ... */}
</div>
);
}Important Considerations for loadTypedWasm and wasm-pack: The wasm-pack generated JS file often exports an init function (usually default export) and named exports for your WASM functions. Propa's loadWasm and loadTypedWasm expect the wasmPath to resolve to a module that, when imported, either directly is the WASM instance or has a default export function that initializes and returns/sets up the WASM instance. The initWasmModule() approach shown first is generally more straightforward with wasm-pack's output as it directly uses the generated JS module.
Propa's WASM integration, especially when combined with tools like wasm-pack, provides a powerful and type-safe way to enhance your web applications with the performance of WebAssembly.
