├── .gitignore ├── README.md ├── package.json ├── public ├── bundle.css ├── bundle.js ├── favicon.png ├── global.css └── index.html ├── scripts └── wbg_to_wasi.js ├── src ├── App.svelte ├── greeting │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── js-wasi │ └── jsWasi.js ├── main.js └── rust-wasi │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ └── main.rs ├── webpack.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /public/bundle.* 3 | /public/wasm/ 4 | 5 | /src/rust-wasi*js 6 | 7 | .DS_Store 8 | 9 | target/ 10 | Cargo.lock 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Svelte Rust/WASI in Browser Example 2 | 3 | This example uses Rust Web Assembly compiled for WASI (the Web Assembly 4 | System Interface) running in the browser using [WasmerJS]("https://github.com/wasmerio/wasmer-js), and uses `wasm-bindgen` to make it easy to pass data from JavaScript to Rust and vice versa. 5 | 6 | Rust is compiled for target `wasm32-wasi` and bindings are generated using 7 | `wasm-bindgen` plus a small amount of post-processing to adapt the bindings for 8 | WASI. 9 | 10 | For a non-Rust example and Svelte + Wasmer/WASI template see [simple-svelte-wasmer-webpack](https://github.com/happybeing/simple-svelte-wasmer-webpack) which was the starting point for this project. 11 | 12 | ## Features 13 | - A Svelte WASM/WASI app with Rust subsystem (using target `wasm32-wasi`) 14 | - JavaScript and Rust both accessing the WasmerJS/wasmFS filesystem 15 | - Calling Rust from JavaScript and vice versa using `wasm-bindgen+` 16 | - Passing and returning JavaScript and Rust native types with no mucking about 17 | - Passing and returning JavaScript objects and arrays to/from Rust structs 18 | 19 | **Help!** I'd like to add more examples of passing variables using `wasm-bindgen` so if you know how to do something I'm not showing yet please open an issue or PR (see Rust [main.rs](https://github.com/happybeing/svelte-wasi-with-rust/blob/main/src/rust-wasi/src/main.rs)). 20 | 21 | Note: `wasm-bindgen+` indicates a small amount of post-processing to make the `wasm-bindgen` output suitable for use with WasmerJS in the browser. 22 | # How This Works 23 | Most of the work is done by WasmerJS and `wasm-bindgen` (CLI because `wasm-pack` does not support WASI at this time). It was tricky to work out how to do this, but the solution is straightforward. 24 | 25 | The main difficulty is that the JavaScript bindings generated by `wasm-bindgen` import the corresponding `wasm-bindgen` WASM output and this fails because the imports needed by the WASM are not available when the WASI is initialised. 26 | 27 | ## Modified Build Process 28 | A script (scripts/wbg_to_wasi.js) has been provided which creates a modified version of the JavaScript generated by `wasm-bindgen`. You don't need to invoke the script yourself because it is included in the build commands which handle everything. So the following is just to document how it works. 29 | 30 | In the following example the `wasm-bindgen` generated JavaScript is in rust-wasi_bg.js, which the script copies to a new file rust-wasi_bg_wasi.js with changes. Firstly the import of the WASM file: 31 | ```javascript 32 | import * as wasm from './rust-wasi_bg.wasm'; 33 | ``` 34 | is replaced by a way to provide the imports after the bindings module has loaded: 35 | ```javascript 36 | let wasm; 37 | export function setBindingsWasm(w){ wasm = w; } 38 | ``` 39 | Secondly, imported JavaScript module names are changed to a relative path (because you can't specify a relative path with the `#[wasm_bindgen]` macro). So a line such as: 40 | ```javascript 41 | import { js_test_n } from 'test'; 42 | ``` 43 | is replaced with: 44 | ```javascript 45 | import { js_test_n } from './js-wasi/test.js'; 46 | ``` 47 | If necessary you can customise the path by editing the command which invokes the script (see package.json). I suspect there's a better way to handle this second modification - PR's welcome! 48 | 49 | ## Implemention in the App 50 | Although this repository implements the app using Svelte, it follows the WasmerJS example documentation closely and should be simple to apply in any web framework. 51 | 52 | IMPORTANT: your Rust `main()` function must be empty if you want to call Rust functions from JavaScript under Wasmer WASI. As in: 53 | ```rust 54 | main(){} 55 | ``` 56 | 57 | To make use of this, your web app does three things (see for example [src/App.svelte](https://github.com/happybeing/svelte-wasi-with-rust/blob/main/src/App.svelte)). 58 | 59 | First import the modified bindings as `wasm`: 60 | ```javascript 61 | import * as wasm from './rust-wasi_bg_wasi.js'; 62 | ``` 63 | 64 | Provide modified bindings along with any other imports (in this case a `test` module, 'test.js') when initialising the Web Assembly. Note that you must use the name of the original bindings module './rust-wasi_bg.js': 65 | ```javascript 66 | imports = {test,...{'./rust-wasi_bg.js': await import('./rust-wasi_bg_wasi')},...imports}; 67 | 68 | let instance = await WebAssembly.instantiate(wasmModule, { 69 | ...imports 70 | }); 71 | ``` 72 | Finally, provide the wasm module to the JavaScript bindings immediately after the call to `wasi.start(...)`: 73 | ```javascript 74 | wasi.start(instance); 75 | wasm.setBindingsWasm(instance.exports); 76 | ``` 77 | 78 | That's it. From there on you can make calls to WASM via the bindings using the `wasm` import './rust-wasi_bg_wasi.js'. For example: 79 | ```javascript 80 | wasm.rust_print_bg_n(256); // Call Rust, print number to stdout 81 | ``` 82 | # Setup 83 | 84 | ## Prerequisites 85 | As well as NodeJS and Rust you need the Rust `wasm32-wasi` target: 86 | ```bash 87 | rustup target add wasm32-wasi 88 | ``` 89 | And the `wasm-bindgen` CLI: 90 | ```bash 91 | cargo install wasm-bindgen-cli 92 | ``` 93 | **Note:** make sure `wasm-bindgen --version` matches the version of the `wasm-bingen` module in `Cargo.toml` (/src/rust-rust-wasi/Cargo.toml). If the versions don't match after doing `cargo install wasm-bindgen-cli && wasm-bindgen --version`, modify the version referred to in `Cargo.toml` to match the CLI. 94 | 95 | You should only need the first and second parts of the version to match, so for example `wasm-bindgen --version` of 'wasm-bindgen 0.2.69' should work fine with Cargo.toml 'wasm-bindgen = "^0.2"'). 96 | ## Get the Code 97 | If you don't have `yarn` use `npm run` instead of `yarn` in the following: 98 | ```bash 99 | git clone https://github.com/happybeing/svelte-wasi-with-rust 100 | cd svelte-wasi-with-rust 101 | yarn && yarn dev-wasm-bindgen && yarn dev 102 | ``` 103 | Once the development build completes you can visit the app at localhost:8080. 104 | 105 | # Builds 106 | ## Release Builds 107 | To build for release: 108 | ```bash 109 | yarn build 110 | ``` 111 | To test, use `yarn serve public` and visit `localhost:5000` 112 | 113 | To deploy, upload everything in /public 114 | ## Development Builds 115 | 116 | The App code is in `src/App.svelte` with Rust and JavaScript subsystems in `src/rust-wasi` and `src/js-wasi`. 117 | 118 | To re-build the wasm and serve the result: 119 | ```bash 120 | yarn dev-wasm-bindgen 121 | yarn dev 122 | ``` 123 | 124 | If you have `inotifywait` (e.g. on Linux) you can use `yarn dev` and `yarn watch-wasm-bindgen` together, and changes to any part of the app including the Rust subsystem will automatically re-build everything and reload the browser. 125 | 126 | To do this, in one terminal watch and re-build the app with: 127 | ```bash 128 | yarn dev 129 | ``` 130 | Then in another terminal, watch and re-build the Rust subsystem with: 131 | ```bash 132 | yarn watch-wasm-bindgen 133 | ``` 134 | 135 | If you're using VSCode, we recommend installing the offical Svelte extension as well as the offical Rust extension. If you are using other editors, your may need to install a plugin in order to get syntax highlighting and intellisense for both Svelte and Rust. 136 | 137 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-wasi-with-rust", 3 | "version": "1.1.0", 4 | "scripts": { 5 | "build": "rm -f public/wasm/* && yarn build-wasm-bindgen && yarn build-app", 6 | "build-app": "cross-env NODE_ENV=production webpack", 7 | "build-wasm-bindgen": "yarn build-wasm && wasm-bindgen --no-typescript src/rust-wasi/target/wasm32-wasi/release/rust-wasi.wasm --browser --out-dir . && yarn wbg-to-wasi && mv *.wasm public/wasm/ && mv rust-* src", 8 | "build-wasm": "cd src/rust-wasi/ && cargo build --release --target=wasm32-wasi && mkdir -p ../../public/wasm", 9 | "dev": "webpack-dev-server --watch-content-base --content-base public", 10 | "dev-wasm": "cd src/rust-wasi/ && cargo build --target=wasm32-wasi && mkdir -p ../../public/wasm", 11 | "dev-wasm-bindgen": "yarn dev-wasm && wasm-bindgen --no-typescript src/rust-wasi/target/wasm32-wasi/debug/rust-wasi.wasm --debug --keep-debug --out-dir . && yarn wbg-to-wasi && mv *.wasm public/wasm/ && mv rust-* src", 12 | "watch-wasm-bindgen": "while true ; do inotifywait -qm --event modify @src/rust-wasi/target -r --format '' src/rust-wasi/ | yarn dev-wasm-bindgen; done", 13 | "wbg-to-wasi": "./scripts/wbg_to_wasi.js --path ./js-wasi/ rust-wasi_bg.js rust-wasi_bg_wasi.js" 14 | }, 15 | "devDependencies": { 16 | "@wasmer/wasi": "^0.12.0", 17 | "@wasmer/wasmfs": "^0.12.0", 18 | "cross-env": "^5.2.0", 19 | "css-loader": "^2.1.1", 20 | "mini-css-extract-plugin": "^0.6.0", 21 | "serve": "^11.0.0", 22 | "style-loader": "^0.23.1", 23 | "svelte": "^3.0.0", 24 | "svelte-loader": "2.13.3", 25 | "webpack": "^4.30.0", 26 | "webpack-cli": "^3.3.0", 27 | "webpack-dev-server": "^3.3.1", 28 | "yargs": "^16.2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /public/bundle.css: -------------------------------------------------------------------------------- 1 | main.svelte-lgrwi{text-align:left;padding:1em;max-width:240px;margin:0 auto}h1.svelte-lgrwi{text-align:center;color:#ff3e00;text-transform:uppercase;font-size:4em;font-weight:100}@media(min-width: 640px){main.svelte-lgrwi{max-width:none}} 2 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happybeing/svelte-wasi-with-rust/db8cc829f0087c66b208e6ebd29307d94716362a/public/favicon.png -------------------------------------------------------------------------------- /public/global.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | color: #333; 9 | margin: 0; 10 | padding: 8px; 11 | box-sizing: border-box; 12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 13 | } 14 | 15 | a { 16 | color: rgb(0,100,200); 17 | text-decoration: none; 18 | } 19 | 20 | a:hover { 21 | text-decoration: underline; 22 | } 23 | 24 | a:visited { 25 | color: rgb(0,80,160); 26 | } 27 | 28 | label { 29 | display: block; 30 | } 31 | 32 | input, button, select, textarea { 33 | font-family: inherit; 34 | font-size: inherit; 35 | padding: 0.4em; 36 | margin: 0 0 0.5em 0; 37 | box-sizing: border-box; 38 | border: 1px solid #ccc; 39 | border-radius: 2px; 40 | } 41 | 42 | input:disabled { 43 | color: #ccc; 44 | } 45 | 46 | input[type="range"] { 47 | height: 0; 48 | } 49 | 50 | button { 51 | background-color: #f4f4f4; 52 | outline: none; 53 | } 54 | 55 | button:active { 56 | background-color: #ddd; 57 | } 58 | 59 | button:focus { 60 | border-color: #666; 61 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /scripts/wbg_to_wasi.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const fs = require("fs"); 5 | const yargs = require("yargs"); 6 | 7 | const options = yargs 8 | .option("p", { alias: "path", describe: "prefix applied JavaScript module imports", default: "./", type: "string", demandOption: false }) 9 | .command("* [options] ", "create WASI compatible wasm-bindgen javascript", () => {}, (argv) => { 10 | // console.log(JSON.stringify(argv)); 11 | 12 | fs.readFile(argv.infile, 'utf8', function (err,data) { 13 | if (err) { 14 | return console.log(err); 15 | } 16 | 17 | // Replace wasm import with a setter function 18 | let initWasi = "let wasm;\nexport function setBindingsWasm(w){ wasm = w; }"; 19 | data = data.replace(/import \* as wasm from \'.\/.*\.wasm\'\;/, initWasi); 20 | 21 | // Change value of module_name to path 22 | data = data.replace(/(import .* from ')(.*)(')/g, '$1' + argv.path + '$2.js$3'); 23 | 24 | fs.writeFile(argv.outfile, data, 'utf8', function (err) { 25 | if (err) return console.log(err); 26 | }); 27 | }); 28 | }) 29 | .help() 30 | .argv; 31 | 32 | -------------------------------------------------------------------------------- /src/App.svelte: -------------------------------------------------------------------------------- 1 | 185 | 186 |

Svelte WasmerJS/WASI Example

187 | 188 |
189 | 190 |

This example uses Rust Web Assembly compiled for WASI (the Web Assembly 191 | System Interface) running in the browser using WasmerJS.

192 | 193 |

Rust is compiled for target wasm32-wasi and bindings are generated using 194 | wasm-bindgen plus a small amount of post-processing to adapt the bindings for 195 | WASI. Source code is at 196 | svelte-wasi-with-rust.

197 | 198 |

Features demonstrated:

199 |
    200 |
  • A Svelte WASM/WASI app with Rust subsystem (using target wasm32-wasi)
  • 201 |
  • JavaScript and Rust both accessing the WasmerJS/wasmFS filesystem
  • 202 |
  • Calling Rust from JavaScript and vice versa using wasm-bindgen+
  • 203 |
  • Passing and returning JavaScript and Rust native types with no mucking about
  • 204 |
  • Passing and returning JavaScript objects and arrays to/from Rust structs
  • 205 |
206 | 207 |

Note: wasm-bindgen+ indicates a small amount of post-processing to make 208 | the wasm-bindgen output suitable for use with WasmerJS in the browser.

209 | 210 |

Check the browser console and the content below for test output.

211 |

Content from WASI

212 |

stdout:

213 |

{#each output as line} {line}
{/each}

214 |
215 | 216 | -------------------------------------------------------------------------------- /src/greeting/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "svelte-wasm-terminal-test-greeting" 3 | version = "0.1.0" 4 | authors = ["Mark "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | wee_alloc = "^0.4" 15 | wasm-bindgen = "^0.2" 16 | console_error_panic_hook = { version = "^0.1", optional = true } 17 | 18 | [dev-dependencies] 19 | wasm-bindgen-test = "^0.3" 20 | 21 | [profile.release] 22 | opt-level = "s" -------------------------------------------------------------------------------- /src/greeting/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | #[cfg(feature = "wee_alloc")] 4 | #[global_allocator] 5 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 6 | 7 | #[wasm_bindgen] 8 | pub fn greet() -> String { 9 | "Hello SVELTE from RUST".into() 10 | } -------------------------------------------------------------------------------- /src/js-wasi/jsWasi.js: -------------------------------------------------------------------------------- 1 | export function js_test() { 2 | console.log("hello from js_test()") 3 | } 4 | 5 | export function js_test_n(n) { 6 | console.log("hello from js_test_n(): ", n) 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import App from './App.svelte'; 2 | 3 | const init = async() => { 4 | 5 | const app = new App({ 6 | target: document.body, 7 | props: { 8 | } 9 | }); 10 | }; 11 | 12 | init(); -------------------------------------------------------------------------------- /src/rust-wasi/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | rust-wasi.wasm 13 | wapm.lock -------------------------------------------------------------------------------- /src/rust-wasi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-wasi" 3 | version = "0.1.0" 4 | description = "Demo/template for Rust/WASI on WasmerJS using wasm-bindgen" 5 | authors = ["happybeing"] 6 | edition = "2018" 7 | 8 | [dependencies] 9 | js-sys = "0.3.46" 10 | log = "0.4.11" 11 | num-integer = "0.1.44" 12 | serde = { version = "1.0.120", features = ["derive"] } 13 | serde_json = "1.0.61" 14 | sha3 = "0.9.1" 15 | structopt = "0.2" 16 | wasm-bindgen = "^0.2" 17 | # web-sys = "0.3.46" 18 | 19 | # [dependencies.web-sys] 20 | # version = "0.3.6" 21 | # features = [ 22 | # 'Document', 23 | # 'Element', 24 | # 'HtmlElement', 25 | # 'Node', 26 | # 'Window', 27 | # ] -------------------------------------------------------------------------------- /src/rust-wasi/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Wasmer, Inc. and its affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/rust-wasi/README.md: -------------------------------------------------------------------------------- 1 | # Rust WASI Example 2 | 3 | A Rust/WASI example which compiles to target `wasm32-wasi` and demonstrates the use of `wasm-bindgen` with WasmerJS. 4 | 5 | It imports and can call JavaScript from src/js-wasi and can be called from the application JavaScript using bindings generated by `wasm-bindgen`. 6 | 7 | This is a component in a larger app, as demonstrated in https://github.com/happybeing/svelte-wasi-with-rust. See the main README or visit that project for more. 8 | -------------------------------------------------------------------------------- /src/rust-wasi/src/main.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | use std::fs; 4 | use std::io::Read; 5 | use structopt::StructOpt; 6 | 7 | // Note: For WebAssembly to call Rust exports (above), main() must be empty. 8 | // You can do one or the other, but not both. 9 | fn main() {} 10 | 11 | // #[wasm_bindgen] 12 | // pub fn runtest() -> Result<(), JsValue> { 13 | // // set_panic_hook(); 14 | 15 | // println!("runtest called!"); 16 | // // // ... 17 | // // let p: web_sys::Node = document.create_element("p")?.into(); 18 | // // p.set_text_content(Some("Hello from Rust, WebAssembly, and Webpack!")); 19 | // // // ... 20 | // Ok(()) 21 | // } 22 | 23 | // ------------------------------------------------------------- 24 | // Misc tests 25 | 26 | #[wasm_bindgen(module = "jsWasi")] 27 | extern "C" { 28 | fn js_test(); 29 | fn js_test_n(n: u32); 30 | } 31 | 32 | 33 | #[wasm_bindgen] 34 | pub fn rust_js_test() { 35 | println!("Rust calling js_test()..."); 36 | js_test(); 37 | 38 | println!("Rust calling js_test_n(123)..."); 39 | js_test_n(123); 40 | } 41 | 42 | #[wasm_bindgen] 43 | pub fn rust_print_bg() { 44 | println!("Hello, world BG!"); 45 | } 46 | 47 | #[wasm_bindgen] 48 | pub fn rust_print_bg_n(n: u32) { 49 | println!("From rust_print_bg_n(n): {}", n); 50 | } 51 | 52 | #[wasm_bindgen] 53 | pub fn rust_say(s: String) -> String { 54 | let r = String::from("hello "); 55 | return r + &s; 56 | } 57 | 58 | // ------------------------------------------------------------- 59 | // wasm-bindgen example by github.com/ibaryshnikov 60 | // (https://github.com/ibaryshnikov/rust-workshop-21-dec-2018) 61 | 62 | #[wasm_bindgen] 63 | pub fn add(a: i32, b: i32) -> i32 { 64 | a + b 65 | } 66 | 67 | #[wasm_bindgen(js_name = doubleList)] 68 | pub fn double_list(list: &[i32]) -> Vec { 69 | list.iter().map(|x| x * 2).collect() 70 | } 71 | 72 | // #[wasm_bindgen(js_name = addElement)] 73 | // pub fn add_element(name: String) -> Result<(), JsValue> { 74 | // let window = web_sys::window().expect("should have a window"); 75 | // let document = window.document().expect("should have a document"); 76 | // let body = document.body().expect("should have a body"); 77 | 78 | // let p = document.create_element("p")?; 79 | // p.set_inner_html(&format!("New element with name: {}", name)); 80 | 81 | // body.append_child(&p)?; 82 | 83 | // Ok(()) 84 | // } 85 | 86 | // ------------------------------------------------------------- 87 | // Examples from Getting started with Rust functions in Node.js 88 | // Article: https://www.secondstate.io/articles/getting-started-with-rust-function/ 89 | // Source: https://github.com/second-state/wasm-learning/tree/master/nodejs/functions 90 | // More: https://cloud.secondstate.io/server-side-webassembly/getting-started 91 | 92 | use serde::{Serialize, Deserialize}; 93 | 94 | #[derive(Serialize, Deserialize, Debug)] 95 | struct Point { 96 | x: f32, 97 | y: f32 98 | } 99 | 100 | #[derive(Serialize, Deserialize, Debug)] 101 | struct Line { 102 | points: Vec, 103 | valid: bool, 104 | length: f32, 105 | desc: String 106 | } 107 | 108 | #[wasm_bindgen] 109 | pub fn create_line (p1: &str, p2: &str, desc: &str) -> String { 110 | let point1: Point = serde_json::from_str(p1).unwrap(); 111 | let point2: Point = serde_json::from_str(p2).unwrap(); 112 | let length = ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt(); 113 | 114 | let valid = if length == 0.0 { false } else { true }; 115 | let line = Line { points: vec![point1, point2], valid: valid, length: length, desc: desc.to_string() }; 116 | return serde_json::to_string(&line).unwrap(); 117 | } 118 | 119 | #[wasm_bindgen] 120 | pub fn say(s: &str) -> String { 121 | let r = String::from("hello "); 122 | return r + s; 123 | } 124 | 125 | #[wasm_bindgen] 126 | pub fn obfusticate(s: String) -> String { 127 | (&s).chars().map(|c| { 128 | match c { 129 | 'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char, 130 | 'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char, 131 | _ => c 132 | } 133 | }).collect() 134 | } 135 | 136 | use num_integer::lcm; 137 | use sha3::{Digest, Sha3_256, Keccak256}; 138 | 139 | #[wasm_bindgen] 140 | pub fn lowest_common_denominator(a: i32, b: i32) -> i32 { 141 | let r = lcm(a, b); 142 | return r; 143 | } 144 | 145 | #[wasm_bindgen] 146 | pub fn sha3_digest(v: Vec) -> Vec { 147 | return Sha3_256::digest(&v).as_slice().to_vec(); 148 | } 149 | 150 | #[wasm_bindgen] 151 | pub fn keccak_digest(s: &[u8]) -> Vec { 152 | return Keccak256::digest(s).as_slice().to_vec(); 153 | } 154 | 155 | // ------------------------------------------------------------- 156 | // wasm-bindgen book example 157 | // (https://rustwasm.github.io/docs/wasm-bindgen/reference/types/imported-js-types.html) 158 | 159 | #[wasm_bindgen] 160 | #[derive(Copy, Clone)] 161 | pub enum NumberEnum { 162 | Foo = 0, 163 | Bar = 1, 164 | Qux = 2, 165 | } 166 | 167 | #[wasm_bindgen] 168 | #[derive(Copy, Clone)] 169 | pub enum StringEnum { 170 | Foo = "foo", 171 | Bar = "bar", 172 | Qux = "qux", 173 | } 174 | 175 | #[wasm_bindgen] 176 | #[derive(Copy, Clone)] 177 | pub struct Struct { 178 | pub number: NumberEnum, 179 | pub string: StringEnum, 180 | } 181 | 182 | #[wasm_bindgen(module = "test")] 183 | extern "C" { 184 | pub type SomeJsType; 185 | } 186 | 187 | #[wasm_bindgen] 188 | pub fn imported_type_by_value(x: SomeJsType) { 189 | /* ... */ 190 | } 191 | 192 | #[wasm_bindgen] 193 | pub fn imported_type_by_shared_ref(x: &SomeJsType) { 194 | /* ... */ 195 | } 196 | 197 | #[wasm_bindgen] 198 | pub fn return_imported_type() -> SomeJsType { 199 | // let jsType = SomeJsType::new(); 200 | // return jsType; 201 | unimplemented!() 202 | } 203 | 204 | #[wasm_bindgen] 205 | pub fn take_option_imported_type(x: Option) { 206 | /* ... */ 207 | } 208 | 209 | #[wasm_bindgen] 210 | pub fn return_option_imported_type() -> Option { 211 | unimplemented!() 212 | } 213 | 214 | // ------------------------------------------------------------- 215 | // Tests based on the WasmerJS H9Q+ Rust example 216 | // 217 | 218 | fn print_hello_world() { 219 | println!("Hello, world!"); 220 | } 221 | 222 | fn print_nintey_nine_bottles_of_beer_on_the_wall() { 223 | for i in (1..100).rev() { 224 | println!("{} bottles of beer on the wall,{} bottles of beer.Take one down, pass it around,{} bottles of beer on the wall.", i, i, i - 1); 225 | } 226 | } 227 | 228 | fn h9q_run(source_code: String) { 229 | #[allow(unused_variables)] 230 | let mut accumulator = 0; 231 | 232 | for c in source_code.chars() { 233 | match c { 234 | 'H' => print_hello_world(), 235 | '9' => print_nintey_nine_bottles_of_beer_on_the_wall(), 236 | 'Q' => println!("{}", source_code), 237 | '+' => accumulator += 1, 238 | _ => (), 239 | } 240 | } 241 | } 242 | 243 | #[derive(Debug, StructOpt)] 244 | struct Opt { 245 | /// Evaluate HQ9+ source code 246 | #[structopt(short = "e", long = "eval")] 247 | source_code: Option, 248 | /// Evaluate HQ9+ source code from a file 249 | #[structopt(short = "f", long = "file")] 250 | source_file: Option, 251 | /// Force evaluation of the `H` instruction 252 | #[structopt(short = "h", long = "hello-world")] 253 | hello_world: bool, 254 | /// Force evaluation of the `9` instruction 255 | #[structopt(short = "9", long = "nintey-nine-bottles")] 256 | ninety_nine_bottles: bool, 257 | } 258 | 259 | #[wasm_bindgen] 260 | pub fn h9q_string(source_code: String) { 261 | h9q_run(source_code); 262 | } 263 | 264 | #[wasm_bindgen] 265 | pub fn h9q_file(src_file: String) { 266 | // let opt = Opt::from_string(¶ms); 267 | 268 | // if opt.hello_world { 269 | // print_hello_world(); 270 | // return; 271 | // } 272 | 273 | // if opt.ninety_nine_bottles { 274 | // print_nintey_nine_bottles_of_beer_on_the_wall(); 275 | // return; 276 | // } 277 | 278 | // if let Some(src) = opt.source_code { 279 | // h9q_run(src); 280 | // return; 281 | // } 282 | 283 | // if let Some(src_file) = opt.source_file { 284 | match fs::File::open(&src_file) { 285 | Ok(mut src_file_handle) => { 286 | let mut buf = String::new(); 287 | src_file_handle 288 | .read_to_string(&mut buf) 289 | .expect(&format!("Failed to read data from file: {}", &src_file)); 290 | h9q_run(buf); 291 | }, 292 | Err(e) => { 293 | println!("Error: {}", e); 294 | eprintln!( 295 | "Error: please pass HQ9+ source code via the `-e` flag or as a file via the `-f` flag" 296 | ); 297 | // ::std::process::exit(-1); 298 | } 299 | } 300 | } 301 | 302 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 2 | const path = require('path'); 3 | 4 | const mode = process.env.NODE_ENV || 'development'; 5 | const prod = mode === 'production'; 6 | 7 | module.exports = { 8 | entry: { 9 | bundle: ['./src/main.js'] 10 | }, 11 | resolve: { 12 | alias: { 13 | svelte: path.resolve('node_modules', 'svelte') 14 | }, 15 | extensions: ['.mjs', '.js', '.svelte'], 16 | mainFields: ['svelte', 'browser', 'module', 'main'] 17 | }, 18 | output: { 19 | path: __dirname + '/public', 20 | filename: '[name].js', 21 | chunkFilename: '[name].[id].js' 22 | }, 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.svelte$/, 27 | use: { 28 | loader: 'svelte-loader', 29 | options: { 30 | emitCss: true, 31 | hotReload: true 32 | } 33 | } 34 | }, 35 | { 36 | test: /\.css$/, 37 | use: [ 38 | /** 39 | * MiniCssExtractPlugin doesn't support HMR. 40 | * For developing, use 'style-loader' instead. 41 | * */ 42 | prod ? MiniCssExtractPlugin.loader : 'style-loader', 43 | 'css-loader' 44 | ] 45 | } 46 | ] 47 | }, 48 | mode, 49 | plugins: [ 50 | new MiniCssExtractPlugin({ 51 | filename: '[name].css' 52 | }) 53 | ], 54 | devtool: prod ? false: 'source-map' 55 | }; 56 | --------------------------------------------------------------------------------