Skip to content

Instantly share code, notes, and snippets.

@kaczor6418
Created January 16, 2021 22:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kaczor6418/7a9892365b2123cc0c594255e9ca858e to your computer and use it in GitHub Desktop.
Save kaczor6418/7a9892365b2123cc0c594255e9ca858e to your computer and use it in GitHub Desktop.
How to connect WebAssembly-Rust with TypeScript (WebComponents)

I noticed there is heavy traffic in my repository with minimal code needed to reproduce a Can not use web-assembly rust impl error. I decided to create this gist to show how I am connecting WASM-Rust with TypeScript.

What you will need:

How to set up project

Set-up

Web - TypeScript part

  1. Initialize npm project
    npm init -y
    
  2. Install the following dependencies
    npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin ts-loader ypescript
    
  3. Create index.js in and export your App (this will be an entry point of your application)
  4. Create bootstrap.ts file in which you will import your index.js asynchronously. We have to load the app asynchronously because .wasm files have to be loaded asynchronously
  5. Create webpack.config.js. Here we have to use experimetns: syncWebAssembly to load our .wasm files
  6. Add serve and build script to your package.json

WebAssembly - Rust part

  1. In root of your project create wasm project using wasm-pack
    wasm-pack new name-of-package
    
  2. Go to package directory
    cd ./name-of-package
    
  3. Run wasm-pack build to build your wasm package

Link wasm-package with TypeScript code

  1. Install your wasm package (if you publish your wasm package then you can install it via npm)
    npm install wasm-package-name/pkg
    
  2. Make sure that you can find this dependecy in your package.json
    "dependencies": {
      "wasm-package-name": "file:./wasm-package-name/pkg"
    },
    
  3. Make sure you have "moduleResolution": "node" in your tsconfig.json

If you want to see ready ready example I preapread a reposityry which you can find here: web-assembly-rust-typescript-template

import { KKMath } from 'kk-math';
const template: string = `
<h1>WebAssembly - Rust + TypeScript</h1>
<span></span>
<button>Calculate expression</button>
<p></p>
`;
export class App extends HTMLElement {
public static TAG: string = `kk-app`;
public readonly shadowRoot!: ShadowRoot;
public readonly expression: HTMLSpanElement;
public readonly evaluatorButton: HTMLButtonElement;
public readonly score: HTMLParagraphElement;
private valueA: number = 11.5;
private valueB: number = 13.5;
private kkMath: KKMath;
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = template;
this.evaluatorButton = this.shadowRoot.querySelector('button')!
this.expression = this.shadowRoot.querySelector('span')!;
this.score = this.shadowRoot.querySelector('p')!;
this.kkMath = KKMath.new(this.valueA, this.valueB);
this.expression.textContent = `${this.valueA} + ${this.valueB}`
this.evaluatorButton.addEventListener('click', () => this.score.textContent = this.kkMath.add().toString());
}
}
customElements.define(App.TAG, App);
import('./index').catch(e => console.error('Error importing `index.js`:', e));
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>WebAssembly - Rust + TypeScript Template</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
</head>
<body>
<kk-app></kk-app>
</body>
</html>
export { App } from './App';
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]
pub struct KKMath {
value_a: f64,
value_b: f64
}
#[wasm_bindgen]
impl KKMath {
pub fn new(value_a: f64, value_b: f64) -> KKMath {
return KKMath {
value_a,
value_b
};
}
pub fn add(&self) -> f64 {
return self.value_a + self.value_b;
}
}
{
"name": "web-assembly-rust-typescript-template",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"serve": "webpack serve",
"build": "webpack"
},
"author": "",
"license": "ISC",
"dependencies": {
"kk-math": "file:./kk-math/pkg"
},
"devDependencies": {
"html-webpack-plugin": "^4.5.1",
"ts-loader": "^8.0.14",
"typescript": "^4.1.3",
"webpack": "^5.15.0",
"webpack-cli": "^4.3.1",
"webpack-dev-server": "^3.11.2"
}
}
{
"compilerOptions":{
"target":"esnext",
"module":"esnext",
"lib":[
"esnext",
"dom"
],
"sourceMap":true,
"strict":true,
"noImplicitAny":true,
"strictNullChecks":true,
"strictFunctionTypes":true,
"strictBindCallApply":true,
"strictPropertyInitialization":true,
"noImplicitThis":true,
"alwaysStrict":true,
"noUnusedLocals":true,
"noUnusedParameters":true,
"noImplicitReturns":true,
"noFallthroughCasesInSwitch":true,
"moduleResolution":"node",
"esModuleInterop":true,
"experimentalDecorators":true,
"emitDecoratorMetadata":true,
"skipLibCheck":true,
"forceConsistentCasingInFileNames":true
}
}
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'eval-source-map',
entry: './src/bootstrap.ts',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
experiments: {
syncWebAssembly: true
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
include: [path.resolve(__dirname, 'src')],
},
],
},
resolve: {
extensions: ['.ts', '.js'],
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
},
plugins: [
new HTMLWebpackPlugin({
template: path.resolve(__dirname, 'src/index.html'),
}),
],
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment