├── .gitattributes ├── test ├── examples │ ├── C++ │ │ ├── .gitignore │ │ ├── README.md │ │ └── example.cpp │ ├── Rust │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── src │ │ │ └── lib.rs │ │ └── Cargo.lock │ ├── WAT │ │ ├── .gitignore │ │ ├── README.md │ │ └── example.wat │ ├── Zig │ │ ├── .gitignore │ │ ├── example.zig │ │ └── README.md │ ├── AssemblyScript │ │ ├── .gitignore │ │ ├── package.json │ │ ├── README.md │ │ ├── example.ts │ │ └── package-lock.json │ ├── zig.wasm │ ├── wat.wasm │ ├── as.wasm │ ├── cpp.wasm │ └── rust.wasm ├── node.js └── web.js ├── .github ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── question.md │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── test.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── loader.d.ts ├── dist └── loader.mjs ├── loader.js ├── prettier.config.js ├── .editorconfig ├── eslint.config.js ├── tsconfig.json ├── SECURITY.md ├── CHANGELOG.md ├── LICENSE ├── CONTRIBUTING.md ├── package.json ├── README.md └── lib └── node.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * -text 2 | -------------------------------------------------------------------------------- /test/examples/C++/.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm 2 | -------------------------------------------------------------------------------- /test/examples/Rust/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /test/examples/WAT/.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: tshemsedinov 2 | -------------------------------------------------------------------------------- /test/examples/Zig/.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | *.wasm* 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | .idea 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | .idea 5 | -------------------------------------------------------------------------------- /test/examples/AssemblyScript/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | *.wasm 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | test/**/*.ts 4 | -------------------------------------------------------------------------------- /loader.d.ts: -------------------------------------------------------------------------------- 1 | export function load(fileName: string, importObject?: object): Promise; 2 | -------------------------------------------------------------------------------- /test/examples/zig.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tshemsedinov/wasm-import/HEAD/test/examples/zig.wasm -------------------------------------------------------------------------------- /test/examples/AssemblyScript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "assemblyscript": "^0.27.29" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /dist/loader.mjs: -------------------------------------------------------------------------------- 1 | import { getLoader } from '../lib/node.js'; 2 | 3 | const load = getLoader({ browser: true }); 4 | 5 | export { load }; 6 | -------------------------------------------------------------------------------- /test/examples/wat.wasm: -------------------------------------------------------------------------------- 1 | asm```-example addCallbackexample subCallbacksumaddsub 2 |  j j k -------------------------------------------------------------------------------- /loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { getLoader } = require('./lib/node.js'); 4 | 5 | const load = getLoader({ browser: false }); 6 | 7 | module.exports = { load }; 8 | -------------------------------------------------------------------------------- /test/examples/as.wasm: -------------------------------------------------------------------------------- 1 | asm```-example addCallbackexample subCallbacksumaddsubmemory 2 |  j j k -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | printWidth: 80, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | tabWidth: 2, 8 | useTabs: false, 9 | semi: true, 10 | }; 11 | -------------------------------------------------------------------------------- /test/examples/WAT/README.md: -------------------------------------------------------------------------------- 1 | # WAT for Node.js WebAssembly 2 | 3 | - Install wabt 4 | 5 | ``` 6 | https://github.com/webassembly/wabt 7 | ``` 8 | 9 | - Compile to wasm 10 | 11 | ``` 12 | wat2wasm example.wat 13 | ``` 14 | -------------------------------------------------------------------------------- /test/examples/cpp.wasm: -------------------------------------------------------------------------------- 1 | asmdylink.0`````%env addCallbackenv subCallback[__wasm_call_ctors__wasm_apply_data_relocs__original_mainsumaddsubmain 2 | + A  j j k A -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{*.js,*.ts,*.json,*.yml}] 11 | indent_size = 2 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const init = require('eslint-config-metarhia'); 4 | 5 | module.exports = [ 6 | ...init, 7 | { 8 | files: ['dist/**/*.mjs'], 9 | languageOptions: { 10 | sourceType: 'module', 11 | }, 12 | }, 13 | ]; 14 | -------------------------------------------------------------------------------- /test/examples/AssemblyScript/README.md: -------------------------------------------------------------------------------- 1 | # AssemblyScript for Node.js WebAssembly 2 | 3 | - Install AssemblyScript 4 | 5 | ``` 6 | npm install assemblyscript 7 | ``` 8 | 9 | - Compile to wasm 10 | 11 | ``` 12 | npx asc example.ts --outFile example.wasm --optimize 13 | ``` 14 | -------------------------------------------------------------------------------- /test/examples/C++/README.md: -------------------------------------------------------------------------------- 1 | # C++ for Node.js WebAssembly 2 | 3 | - Install Emscripten 4 | 5 | ``` 6 | https://github.com/emscripten-core/emsdk.git 7 | ``` 8 | 9 | - Compile to wasm 10 | 11 | ``` 12 | em++ -std=c++11 example.cpp -Os -s WASM=1 -s SIDE_MODULE=1 -o example.wasm 13 | ``` 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "moduleResolution": "node", 5 | "strict": true, 6 | "noEmit": true, 7 | "baseUrl": ".", 8 | "preserveWatchOutput": true, 9 | "esModuleInterop": true 10 | }, 11 | "include": ["*.d.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /test/examples/rust.wasm: -------------------------------------------------------------------------------- 1 | asm```-example addCallbackexample subCallbackmemorysumaddsub 2 |  j k  j { producerslanguageRust processed-byrustc1.73.0 (cc66ad468 2023-10-03)walrus0.19.0 wasm-bindgen0.2.87 (f0a8ae3b9),target_features+mutable-globals+sign-ext -------------------------------------------------------------------------------- /test/examples/Rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | authors = ["Timur Shemsedinov "] 5 | edition = "2021" 6 | repository = "https://github.com/HowProgrammingWorks/WebAssembly" 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wasm-bindgen = "0.2" 13 | -------------------------------------------------------------------------------- /test/examples/Zig/example.zig: -------------------------------------------------------------------------------- 1 | extern fn addCallback(i32) void; 2 | extern fn subCallback(i32) void; 3 | 4 | export fn sum(a: i32, b: i32) i32 { 5 | return a + b; 6 | } 7 | 8 | export fn add(a: i32, b: i32) void { 9 | addCallback(a + b); 10 | } 11 | 12 | export fn sub(a: i32, b: i32) void { 13 | subCallback(a - b); 14 | } 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | - [ ] tests and linter show no problems (`npm t`) 8 | - [ ] tests are added/updated for bug fixes and new features 9 | - [ ] code is properly formatted (`npm run fix`) 10 | - [ ] description of changes is added in CHANGELOG.md 11 | - [ ] update .d.ts typings 12 | -------------------------------------------------------------------------------- /test/examples/Rust/README.md: -------------------------------------------------------------------------------- 1 | # Rust for Node.js WebAssembly 2 | 3 | - Install rust 4 | - For fedora: 5 | 6 | ``` 7 | sudo dnf install rust cargo 8 | ``` 9 | 10 | - Install wasm32-unknown-unknown 11 | - For fedora: 12 | 13 | ``` 14 | sudo dnf install rust-std-static-wasm32-unknown-unknown 15 | ``` 16 | 17 | - Install wasm-pack 18 | 19 | ``` 20 | cargo install wasm-pack 21 | ``` 22 | 23 | - See/edit `Cargo.toml` 24 | - Compole example: 25 | 26 | ``` 27 | wasm-pack build --target nodejs 28 | ``` 29 | -------------------------------------------------------------------------------- /test/examples/Rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use wasm_bindgen::prelude::*; 4 | 5 | #[wasm_bindgen(module = "example")] 6 | extern "C" { 7 | fn addCallback(res: i32); 8 | fn subCallback(res: i32); 9 | } 10 | 11 | #[wasm_bindgen] 12 | pub fn sum(a: i32, b: i32) -> i32 { 13 | return a + b; 14 | } 15 | 16 | #[wasm_bindgen] 17 | pub fn add(a: i32, b: i32) { 18 | addCallback(a + b) 19 | } 20 | 21 | #[wasm_bindgen] 22 | pub fn sub(a: i32, b: i32) { 23 | subCallback(a - b) 24 | } 25 | -------------------------------------------------------------------------------- /test/examples/C++/example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | return 0; 5 | } 6 | 7 | extern "C" { 8 | extern void addCallback(int res); 9 | extern void subCallback(int res); 10 | 11 | EMSCRIPTEN_KEEPALIVE int sum(int a, int b) { 12 | return a + b; 13 | } 14 | 15 | EMSCRIPTEN_KEEPALIVE void add(int a, int b) { 16 | int res = a + b; 17 | addCallback(res); 18 | } 19 | 20 | EMSCRIPTEN_KEEPALIVE void sub(int a, int b) { 21 | int res = a - b; 22 | subCallback(res); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 0.x | :white_check_mark: | 8 | 9 | ## Reporting a Vulnerability 10 | 11 | If you believe you have found a security vulnerability, let us know by sending 12 | email to [timur.shemsedinov@gmail.com](mailto:timur.shemsedinov@gmail.com) 13 | We will investigate that and do our best to quickly fix the problem. 14 | 15 | Please don't open an issue to or discuss this security vulnerability in a public 16 | place. Thanks for understanding! 17 | -------------------------------------------------------------------------------- /test/examples/AssemblyScript/example.ts: -------------------------------------------------------------------------------- 1 | declare namespace example { 2 | @external("example", "addCallback") 3 | export function addCallback(res: i32): void; 4 | 5 | @external("example", "subCallback") 6 | export function subCallback(res: i32): void; 7 | } 8 | 9 | export function sum(a: i32, b: i32): i32 { 10 | return a + b; 11 | } 12 | 13 | export function add(a: i32, b: i32): void { 14 | const res: i32 = a + b; 15 | example.addCallback(res); 16 | } 17 | 18 | export function sub(a: i32, b: i32): void { 19 | const res: i32 = a - b; 20 | example.subCallback(res); 21 | } 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Please don't open an issue to ask questions 4 | --- 5 | 6 | Issues on GitHub are intended to be related to problems and feature requests 7 | so we recommend not using this medium to ask them here grin. Thanks for 8 | understanding! 9 | 10 | If you have a question, please check out our support groups and channels for 11 | developers community: 12 | 13 | Telegram: 14 | 15 | - Channel for Metarhia community: https://t.me/metarhia 16 | - Group for Metarhia technology stack community: https://t.me/metaserverless 17 | - Group for NodeUA community: https://t.me/nodeua 18 | -------------------------------------------------------------------------------- /test/examples/Zig/README.md: -------------------------------------------------------------------------------- 1 | # Zig for WebAssembly 2 | 3 | - Install zig compiler for your platform 4 | 5 | ``` 6 | https://ziglang.org/download/ 7 | ``` 8 | 9 | - Compile to wasm 10 | 11 | ``` 12 | zig build-lib example.zig -dynamic -target wasm32-freestanding -rdynamic 13 | ``` 14 | 15 | - Disclaimer 16 | 17 | The instruction from above is valid as of zig compiler version 0.11.0 and can 18 | be found [here](https://ziglang.org/documentation/0.11.0/#WebAssembly). 19 | The language is rapidly developing and so if you have a zig compiler of a different 20 | version and have issues with compiling to wasm, please address the docs. 21 | -------------------------------------------------------------------------------- /test/examples/WAT/example.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "example" "addCallback" (func $addCallback (param i32))) 3 | (import "example" "subCallback" (func $subCallback (param i32))) 4 | (func $sum (param $lhs i32) (param $rhs i32) (result i32) 5 | local.get $lhs 6 | local.get $rhs 7 | i32.add) 8 | (export "sum" (func $sum)) 9 | (func $add (param $lhs i32) (param $rhs i32) 10 | local.get $lhs 11 | local.get $rhs 12 | i32.add 13 | call $addCallback) 14 | (export "add" (func $add)) 15 | (func $sub (param $lhs i32) (param $rhs i32) 16 | local.get $lhs 17 | local.get $rhs 18 | i32.sub 19 | call $subCallback) 20 | (export "sub" (func $sub)) 21 | ) 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: usage example or test. 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected. 17 | 18 | **Screenshots** 19 | If applicable, add screenshots to help explain your problem. 20 | 21 | **Desktop (please complete the following information):** 22 | 23 | - OS: [e.g. Fedora 30 64-bit] 24 | - Node.js version [e.g. 14.15.1] 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased][unreleased] 4 | 5 | - Example and test for Zig 6 | - Build wasm for all examples in CI before tests 7 | - Support promises 8 | - Re-use 'loader' logic for client and server 9 | 10 | ## [0.3.0][] - 2023-08-12 11 | 12 | - Support in-place callbacks 13 | - Avoid compiling WASM module twice in 'load' function 14 | 15 | ## [0.2.0][] - 2023-08-07 16 | 17 | - Loader for web 18 | - Tests for node.js and web 19 | - Tests for Rust, WAT, and WebAssembly 20 | 21 | ## [0.1.0][] - 2023-08-07 22 | 23 | - Loader for node.js 24 | 25 | [unreleased]: https://github.com/tshemsedinov/wasm-import/compare/v0.3.0...HEAD 26 | [0.3.0]: https://github.com/tshemsedinov/wasm-import/compare/v0.2.0...v0.3.0 27 | [0.2.0]: https://github.com/tshemsedinov/wasm-import/compare/v0.1.0...v0.2.0 28 | [0.1.0]: https://github.com/tshemsedinov/wasm-import/releases/tag/v0.1.0 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2024 WASM Import contributors 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 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | - [Issues](#issues) 4 | - [Pull Requests](#pull-requests) 5 | 6 | ## Issues 7 | 8 | There are two reasons to open an issue: 9 | 10 | - Bug report 11 | - Feature request 12 | 13 | For bug reports please describe the bug with a clear and concise description, 14 | steps to reproduce the behavior (usage example or test), expected behavior, 15 | provide OS and Node.js version, you can upload screenshots and any additional 16 | context for better understanding. 17 | 18 | Please don't open an issue to ask questions. 19 | 20 | Issues on GitHub are intended to be related to problems and feature requests 21 | so we recommend not using this medium to ask them here grin. Thanks for 22 | understanding! 23 | 24 | If you have a question, please check out our support groups and channels for 25 | developers community: 26 | 27 | Telegram: 28 | 29 | - Channel for Metarhia community: https://t.me/metarhia 30 | - Group for Metarhia technology stack community: https://t.me/metaserverless 31 | - Group for NodeUA community: https://t.me/nodeua 32 | 33 | ## Pull Requests 34 | 35 | Before open pull request please follow checklist: 36 | 37 | - [ ] tests and linter show no problems (`npm t`) 38 | - [ ] tests are added/updated for bug fixes and new features 39 | - [ ] code is properly formatted (`npm run fix`) 40 | - [ ] description of changes is added in CHANGELOG.md 41 | - [ ] update .d.ts typings 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasm-import", 3 | "version": "0.3.0", 4 | "author": "Timur Shemsedinov ", 5 | "description": "WASM Loader for Node.js and Web", 6 | "license": "MIT", 7 | "keywords": [ 8 | "wasm", 9 | "wasi", 10 | "WebAssembly", 11 | "require", 12 | "import", 13 | "loader" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/tshemsedinov/wasm-import" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/tshemsedinov/wasm-import/issues", 21 | "email": "timur.shemsedinov@gmail.com" 22 | }, 23 | "homepage": "https://metarhia.com", 24 | "funding": { 25 | "type": "patreon", 26 | "url": "https://www.patreon.com/tshemsedinov" 27 | }, 28 | "main": "loader.js", 29 | "types": "loader.d.ts", 30 | "browser": { 31 | "./loader.js": "./dist/loader.mjs" 32 | }, 33 | "files": [ 34 | "dist/", 35 | "lib/", 36 | "loader.d.ts" 37 | ], 38 | "readmeFilename": "README.md", 39 | "scripts": { 40 | "test": "npm run lint && npm run types && metatests test/", 41 | "types": "tsc -p tsconfig.json", 42 | "lint": "eslint . && prettier --check \"**/*.js\" \"**/*.json\" \"**/*.md\" \"**/*.ts\"", 43 | "fix": "eslint . --fix && prettier --write \"**/*.js\" \"**/*.json\" \"**/*.md\" \"**/*.ts\"" 44 | }, 45 | "engines": { 46 | "node": "18 || 20" 47 | }, 48 | "devDependencies": { 49 | "@types/node": "^22.5.1", 50 | "eslint": "^9.10.0", 51 | "eslint-config-metarhia": "^9.0.7", 52 | "metatests": "^0.9.0", 53 | "prettier": "^3.3.3", 54 | "typescript": "^5.5.4", 55 | "undici": "^6.12.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WASM Import 2 | 3 | [![ci status](https://github.com/tshemsedinov/wasm-import/workflows/Testing%20CI/badge.svg)](https://github.com/tshemsedinov/wasm-import/actions?query=workflow%3A%22Testing+CI%22+branch%3Amaster) 4 | [![snyk](https://snyk.io/test/github/tshemsedinov/wasm-import/badge.svg)](https://snyk.io/test/github/tshemsedinov/wasm-import) 5 | [![npm version](https://badge.fury.io/js/wasm-import.svg)](https://badge.fury.io/js/wasm-import) 6 | [![npm downloads/month](https://img.shields.io/npm/dm/wasm-import.svg)](https://www.npmjs.com/package/wasm-import) 7 | [![npm downloads](https://img.shields.io/npm/dt/wasm-import.svg)](https://www.npmjs.com/package/wasm-import) 8 | 9 | ``` 10 | npm i wasm-import 11 | ``` 12 | 13 | See MDN docs how to pass callbacks to wasm via `importObject`: 14 | https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Instance 15 | 16 | ```js 17 | load( 18 | fileName: string, // File name or URL 19 | importObject?: object // See MDN docs 20 | ): Promise; 21 | ``` 22 | 23 | ## Example 24 | 25 | ```js 26 | import { load } from 'wasm-import'; 27 | // Alternative: const { load } = require('wasm-import'); 28 | 29 | const example = await load('example.wasm'); 30 | 31 | example.instance.exports.add(3, 7, (result) => { 32 | console.log({ result, expected: 10 }); 33 | }); 34 | 35 | // Promises supported as well 36 | const result = await example.instance.exports.add(3, 7); 37 | console.log({ result, expected: 10 }); 38 | ``` 39 | 40 | ## License & Contributors 41 | 42 | Copyright (c) 2023-2024 [WASM Import contributors](https://github.com/tshemsedinov/wasm-import/graphs/contributors). 43 | WASM Import is [MIT licensed](./LICENSE). 44 | -------------------------------------------------------------------------------- /test/examples/AssemblyScript/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AssemblyScript", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "assemblyscript": "^0.27.29" 9 | } 10 | }, 11 | "node_modules/assemblyscript": { 12 | "version": "0.27.29", 13 | "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.27.29.tgz", 14 | "integrity": "sha512-pH6udb7aE2F0t6cTh+0uCepmucykhMnAmm7k0kkAU3SY7LvpIngEBZWM6p5VCguu4EpmKGwEuZpZbEXzJ/frHQ==", 15 | "dependencies": { 16 | "binaryen": "116.0.0-nightly.20240114", 17 | "long": "^5.2.1" 18 | }, 19 | "bin": { 20 | "asc": "bin/asc.js", 21 | "asinit": "bin/asinit.js" 22 | }, 23 | "engines": { 24 | "node": ">=16", 25 | "npm": ">=7" 26 | }, 27 | "funding": { 28 | "type": "opencollective", 29 | "url": "https://opencollective.com/assemblyscript" 30 | } 31 | }, 32 | "node_modules/binaryen": { 33 | "version": "116.0.0-nightly.20240114", 34 | "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0-nightly.20240114.tgz", 35 | "integrity": "sha512-0GZrojJnuhoe+hiwji7QFaL3tBlJoA+KFUN7ouYSDGZLSo9CKM8swQX8n/UcbR0d1VuZKU+nhogNzv423JEu5A==", 36 | "bin": { 37 | "wasm-opt": "bin/wasm-opt", 38 | "wasm2js": "bin/wasm2js" 39 | } 40 | }, 41 | "node_modules/long": { 42 | "version": "5.2.3", 43 | "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", 44 | "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" 45 | } 46 | }, 47 | "dependencies": { 48 | "assemblyscript": { 49 | "version": "0.27.29", 50 | "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.27.29.tgz", 51 | "integrity": "sha512-pH6udb7aE2F0t6cTh+0uCepmucykhMnAmm7k0kkAU3SY7LvpIngEBZWM6p5VCguu4EpmKGwEuZpZbEXzJ/frHQ==", 52 | "requires": { 53 | "binaryen": "116.0.0-nightly.20240114", 54 | "long": "^5.2.1" 55 | } 56 | }, 57 | "binaryen": { 58 | "version": "116.0.0-nightly.20240114", 59 | "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0-nightly.20240114.tgz", 60 | "integrity": "sha512-0GZrojJnuhoe+hiwji7QFaL3tBlJoA+KFUN7ouYSDGZLSo9CKM8swQX8n/UcbR0d1VuZKU+nhogNzv423JEu5A==" 61 | }, 62 | "long": { 63 | "version": "5.2.3", 64 | "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", 65 | "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fsp = require('node:fs').promises; 4 | 5 | const CALLBACK_LEN = 'Callback'.length; 6 | const calls = new Map(); 7 | 8 | const parseFunctionName = (cbName) => cbName.slice(0, -CALLBACK_LEN); 9 | 10 | const prepareImports = (byteCode) => { 11 | const imports = WebAssembly.Module.imports(byteCode); 12 | const expected = {}; 13 | for (const entry of imports) { 14 | if (entry.kind !== 'function') continue; 15 | let module = expected[entry.module]; 16 | if (!module) module = expected[entry.module] = {}; 17 | const exportedFunctionName = parseFunctionName(entry.name); 18 | calls.set(exportedFunctionName, []); 19 | module[entry.name] = (...args) => { 20 | const callbacks = calls.get(exportedFunctionName); 21 | if (!callbacks) return; 22 | const callback = callbacks.shift(); 23 | if (callback) callback(...args); 24 | }; 25 | } 26 | return expected; 27 | }; 28 | 29 | const prepareExports = (instance) => { 30 | const exports = {}; 31 | for (const [name, fn] of Object.entries(instance.exports)) { 32 | if (typeof fn !== 'function') { 33 | exports[name] = fn; 34 | continue; 35 | } 36 | exports[name] = (...args) => { 37 | const callbacksExpected = calls.get(name); 38 | if (!callbacksExpected) return fn(...args); 39 | const callbackProvided = typeof args.at(-1) === 'function'; 40 | if (callbackProvided) { 41 | callbacksExpected.push(args.pop()); 42 | return fn(...args); 43 | } 44 | return new Promise((resolve) => { 45 | callbacksExpected.push(resolve); 46 | fn(...args); 47 | }); 48 | }; 49 | } 50 | 51 | return exports; 52 | }; 53 | 54 | const compileInBrowserEnv = async (fileName) => { 55 | const response = await fetch(fileName); 56 | const buffer = await response.arrayBuffer(); 57 | const compiled = await WebAssembly.compile(buffer); 58 | return compiled; 59 | }; 60 | 61 | const compileInServerEnv = async (fileName) => { 62 | const buffer = await fsp.readFile(fileName); 63 | const compiled = await WebAssembly.compile(buffer); 64 | return compiled; 65 | }; 66 | 67 | const load = async (fileName, importObject = {}, opts = { browser: false }) => { 68 | const compiled = opts.browser 69 | ? await compileInBrowserEnv(fileName) 70 | : await compileInServerEnv(fileName); 71 | const expected = prepareImports(compiled); 72 | const imports = { ...importObject, ...expected }; 73 | const instance = await WebAssembly.instantiate(compiled, imports); 74 | const exports = prepareExports(instance); 75 | return { instance: { exports }, module: compiled }; 76 | }; 77 | 78 | const getLoader = 79 | (opts) => 80 | (fileName, importObject = {}) => 81 | load(fileName, importObject, opts); 82 | 83 | module.exports = { load, getLoader }; 84 | -------------------------------------------------------------------------------- /test/examples/Rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bumpalo" 7 | version = "3.13.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 16 | 17 | [[package]] 18 | name = "example" 19 | version = "0.1.0" 20 | dependencies = [ 21 | "wasm-bindgen", 22 | ] 23 | 24 | [[package]] 25 | name = "log" 26 | version = "0.4.19" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" 29 | 30 | [[package]] 31 | name = "once_cell" 32 | version = "1.18.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 35 | 36 | [[package]] 37 | name = "proc-macro2" 38 | version = "1.0.66" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 41 | dependencies = [ 42 | "unicode-ident", 43 | ] 44 | 45 | [[package]] 46 | name = "quote" 47 | version = "1.0.32" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" 50 | dependencies = [ 51 | "proc-macro2", 52 | ] 53 | 54 | [[package]] 55 | name = "syn" 56 | version = "2.0.28" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" 59 | dependencies = [ 60 | "proc-macro2", 61 | "quote", 62 | "unicode-ident", 63 | ] 64 | 65 | [[package]] 66 | name = "unicode-ident" 67 | version = "1.0.11" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 70 | 71 | [[package]] 72 | name = "wasm-bindgen" 73 | version = "0.2.87" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" 76 | dependencies = [ 77 | "cfg-if", 78 | "wasm-bindgen-macro", 79 | ] 80 | 81 | [[package]] 82 | name = "wasm-bindgen-backend" 83 | version = "0.2.87" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" 86 | dependencies = [ 87 | "bumpalo", 88 | "log", 89 | "once_cell", 90 | "proc-macro2", 91 | "quote", 92 | "syn", 93 | "wasm-bindgen-shared", 94 | ] 95 | 96 | [[package]] 97 | name = "wasm-bindgen-macro" 98 | version = "0.2.87" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" 101 | dependencies = [ 102 | "quote", 103 | "wasm-bindgen-macro-support", 104 | ] 105 | 106 | [[package]] 107 | name = "wasm-bindgen-macro-support" 108 | version = "0.2.87" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" 111 | dependencies = [ 112 | "proc-macro2", 113 | "quote", 114 | "syn", 115 | "wasm-bindgen-backend", 116 | "wasm-bindgen-shared", 117 | ] 118 | 119 | [[package]] 120 | name = "wasm-bindgen-shared" 121 | version = "0.2.87" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" 124 | -------------------------------------------------------------------------------- /test/node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const { load } = require('..'); 5 | 6 | const PATH = './test/examples/'; 7 | 8 | metatests.test('Rust WASM (node): wasm-pack', async (test) => { 9 | const fileName = PATH + 'rust.wasm'; 10 | const example = await load(fileName); 11 | 12 | { 13 | const res = example.instance.exports.sum(3, 7); 14 | test.strictEqual(res, 10); 15 | } 16 | 17 | { 18 | example.instance.exports.add(1, 1, (res) => { 19 | test.strictEqual(res, 2); 20 | }); 21 | const res = await example.instance.exports.add(1, 1); 22 | test.strictEqual(res, 2); 23 | } 24 | 25 | { 26 | example.instance.exports.add(2, 2, (res) => { 27 | test.strictEqual(res, 4); 28 | }); 29 | const res = await example.instance.exports.add(2, 2); 30 | test.strictEqual(res, 4); 31 | } 32 | 33 | { 34 | example.instance.exports.sub(10, 5, (res) => { 35 | test.strictEqual(res, 5); 36 | }); 37 | const res = await example.instance.exports.sub(10, 5); 38 | test.strictEqual(res, 5); 39 | } 40 | 41 | { 42 | example.instance.exports.sub(20, 10, (res) => { 43 | test.strictEqual(res, 10); 44 | }); 45 | const res = await example.instance.exports.sub(20, 10); 46 | test.strictEqual(res, 10); 47 | } 48 | 49 | test.end(); 50 | }); 51 | 52 | metatests.test('WAT WASM (node): wabt/wat2wasm', async (test) => { 53 | const fileName = PATH + 'wat.wasm'; 54 | const example = await load(fileName); 55 | 56 | { 57 | const res = example.instance.exports.sum(3, 7); 58 | test.strictEqual(res, 10); 59 | } 60 | 61 | { 62 | example.instance.exports.add(1, 1, (res) => { 63 | test.strictEqual(res, 2); 64 | }); 65 | const res = await example.instance.exports.add(1, 1); 66 | test.strictEqual(res, 2); 67 | } 68 | 69 | { 70 | example.instance.exports.add(2, 2, (res) => { 71 | test.strictEqual(res, 4); 72 | }); 73 | const res = await example.instance.exports.add(2, 2); 74 | test.strictEqual(res, 4); 75 | } 76 | 77 | { 78 | example.instance.exports.sub(10, 5, (res) => { 79 | test.strictEqual(res, 5); 80 | }); 81 | const res = await example.instance.exports.sub(10, 5); 82 | test.strictEqual(res, 5); 83 | } 84 | 85 | { 86 | example.instance.exports.sub(20, 10, (res) => { 87 | test.strictEqual(res, 10); 88 | }); 89 | const res = await example.instance.exports.sub(20, 10); 90 | test.strictEqual(res, 10); 91 | } 92 | 93 | test.end(); 94 | }); 95 | 96 | metatests.test('AssemblyScript WASM (node)', async (test) => { 97 | const fileName = PATH + 'as.wasm'; 98 | const example = await load(fileName); 99 | 100 | { 101 | const res = example.instance.exports.sum(3, 7); 102 | test.strictEqual(res, 10); 103 | } 104 | 105 | { 106 | example.instance.exports.add(1, 1, (res) => { 107 | test.strictEqual(res, 2); 108 | }); 109 | const res = await example.instance.exports.add(1, 1); 110 | test.strictEqual(res, 2); 111 | } 112 | 113 | { 114 | example.instance.exports.add(2, 2, (res) => { 115 | test.strictEqual(res, 4); 116 | }); 117 | const res = await example.instance.exports.add(2, 2); 118 | test.strictEqual(res, 4); 119 | } 120 | 121 | { 122 | example.instance.exports.sub(10, 5, (res) => { 123 | test.strictEqual(res, 5); 124 | }); 125 | const res = await example.instance.exports.sub(10, 5); 126 | test.strictEqual(res, 5); 127 | } 128 | 129 | { 130 | example.instance.exports.sub(20, 10, (res) => { 131 | test.strictEqual(res, 10); 132 | }); 133 | const res = await example.instance.exports.sub(20, 10); 134 | test.strictEqual(res, 10); 135 | } 136 | 137 | test.end(); 138 | }); 139 | 140 | metatests.test('C++ WASM (node)', async (test) => { 141 | const fileName = PATH + 'cpp.wasm'; 142 | const example = await load(fileName); 143 | 144 | { 145 | const res = example.instance.exports.sum(3, 7); 146 | test.strictEqual(res, 10); 147 | } 148 | 149 | { 150 | example.instance.exports.add(1, 1, (res) => { 151 | test.strictEqual(res, 2); 152 | }); 153 | const res = await example.instance.exports.add(1, 1); 154 | test.strictEqual(res, 2); 155 | } 156 | 157 | { 158 | example.instance.exports.add(2, 2, (res) => { 159 | test.strictEqual(res, 4); 160 | }); 161 | const res = await example.instance.exports.add(2, 2); 162 | test.strictEqual(res, 4); 163 | } 164 | 165 | { 166 | example.instance.exports.sub(10, 5, (res) => { 167 | test.strictEqual(res, 5); 168 | }); 169 | const res = await example.instance.exports.sub(10, 5); 170 | test.strictEqual(res, 5); 171 | } 172 | 173 | { 174 | example.instance.exports.sub(20, 10, (res) => { 175 | test.strictEqual(res, 10); 176 | }); 177 | const res = await example.instance.exports.sub(20, 10); 178 | test.strictEqual(res, 10); 179 | } 180 | 181 | test.end(); 182 | }); 183 | 184 | metatests.test('Zig WASM (node)', async (test) => { 185 | const fileName = PATH + 'zig.wasm'; 186 | const example = await load(fileName); 187 | 188 | { 189 | const res = example.instance.exports.sum(3, 7); 190 | test.strictEqual(res, 10); 191 | } 192 | 193 | { 194 | example.instance.exports.add(1, 1, (res) => { 195 | test.strictEqual(res, 2); 196 | }); 197 | const res = await example.instance.exports.add(1, 1); 198 | test.strictEqual(res, 2); 199 | } 200 | 201 | { 202 | example.instance.exports.add(2, 2, (res) => { 203 | test.strictEqual(res, 4); 204 | }); 205 | const res = await example.instance.exports.add(2, 2); 206 | test.strictEqual(res, 4); 207 | } 208 | 209 | { 210 | example.instance.exports.sub(10, 5, (res) => { 211 | test.strictEqual(res, 5); 212 | }); 213 | const res = await example.instance.exports.sub(10, 5); 214 | test.strictEqual(res, 5); 215 | } 216 | 217 | { 218 | example.instance.exports.sub(20, 10, (res) => { 219 | test.strictEqual(res, 10); 220 | }); 221 | const res = await example.instance.exports.sub(20, 10); 222 | test.strictEqual(res, 10); 223 | } 224 | 225 | test.end(); 226 | }); 227 | -------------------------------------------------------------------------------- /test/web.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('node:fs'); 4 | const http = require('node:http'); 5 | const path = require('node:path'); 6 | const timers = require('node:timers/promises'); 7 | const metatests = require('metatests'); 8 | global.fetch = global.fetch || require('undici').fetch; 9 | 10 | const CWD = process.cwd(); 11 | const DIST = 'file://' + path.join(CWD, './dist/loader.mjs'); 12 | const BASE = 'http://127.0.0.1:8000/examples/'; 13 | 14 | const PORT = 8000; 15 | const TEST_TIMEOT = 5000; 16 | 17 | const serve = async (req, res) => { 18 | const stream = fs.createReadStream('./test' + req.url); 19 | res.writeHead(200, { 'Content-Type': 'application/wasm' }); 20 | stream.pipe(res); 21 | }; 22 | 23 | const server = http.createServer(serve).listen(PORT); 24 | 25 | setTimeout(() => { 26 | server.close(); 27 | }, TEST_TIMEOT); 28 | 29 | metatests.test('Rust WASM (web): wasm-pack', async (test) => { 30 | const { load } = await import(DIST); 31 | await timers.setTimeout(100); 32 | 33 | const fileName = BASE + 'rust.wasm'; 34 | const example = await load(fileName); 35 | 36 | { 37 | const res = example.instance.exports.sum(3, 7); 38 | test.strictEqual(res, 10); 39 | } 40 | 41 | { 42 | example.instance.exports.add(1, 1, (res) => { 43 | test.strictEqual(res, 2); 44 | }); 45 | const res = await example.instance.exports.add(1, 1); 46 | test.strictEqual(res, 2); 47 | } 48 | 49 | { 50 | example.instance.exports.add(2, 2, (res) => { 51 | test.strictEqual(res, 4); 52 | }); 53 | const res = await example.instance.exports.add(2, 2); 54 | test.strictEqual(res, 4); 55 | } 56 | 57 | { 58 | example.instance.exports.sub(10, 5, (res) => { 59 | test.strictEqual(res, 5); 60 | }); 61 | const res = await example.instance.exports.sub(10, 5); 62 | test.strictEqual(res, 5); 63 | } 64 | 65 | { 66 | example.instance.exports.sub(20, 10, (res) => { 67 | test.strictEqual(res, 10); 68 | }); 69 | const res = await example.instance.exports.sub(20, 10); 70 | test.strictEqual(res, 10); 71 | } 72 | 73 | test.end(); 74 | }); 75 | 76 | metatests.test('WAT WASM (web): wabt/wat2wasm', async (test) => { 77 | const { load } = await import(DIST); 78 | await timers.setTimeout(100); 79 | 80 | const fileName = BASE + 'wat.wasm'; 81 | const example = await load(fileName); 82 | 83 | { 84 | const res = example.instance.exports.sum(3, 7); 85 | test.strictEqual(res, 10); 86 | } 87 | 88 | { 89 | example.instance.exports.add(1, 1, (res) => { 90 | test.strictEqual(res, 2); 91 | }); 92 | const res = await example.instance.exports.add(1, 1); 93 | test.strictEqual(res, 2); 94 | } 95 | 96 | { 97 | example.instance.exports.add(2, 2, (res) => { 98 | test.strictEqual(res, 4); 99 | }); 100 | const res = await example.instance.exports.add(2, 2); 101 | test.strictEqual(res, 4); 102 | } 103 | 104 | { 105 | example.instance.exports.sub(10, 5, (res) => { 106 | test.strictEqual(res, 5); 107 | }); 108 | const res = await example.instance.exports.sub(10, 5); 109 | test.strictEqual(res, 5); 110 | } 111 | 112 | { 113 | example.instance.exports.sub(20, 10, (res) => { 114 | test.strictEqual(res, 10); 115 | }); 116 | const res = await example.instance.exports.sub(20, 10); 117 | test.strictEqual(res, 10); 118 | } 119 | 120 | test.end(); 121 | }); 122 | 123 | metatests.test('AssemblyScript WASM (web)', async (test) => { 124 | const { load } = await import(DIST); 125 | await timers.setTimeout(100); 126 | 127 | const fileName = BASE + 'as.wasm'; 128 | const example = await load(fileName); 129 | 130 | { 131 | const res = example.instance.exports.sum(3, 7); 132 | test.strictEqual(res, 10); 133 | } 134 | 135 | { 136 | example.instance.exports.add(1, 1, (res) => { 137 | test.strictEqual(res, 2); 138 | }); 139 | const res = await example.instance.exports.add(1, 1); 140 | test.strictEqual(res, 2); 141 | } 142 | 143 | { 144 | example.instance.exports.add(2, 2, (res) => { 145 | test.strictEqual(res, 4); 146 | }); 147 | const res = await example.instance.exports.add(2, 2); 148 | test.strictEqual(res, 4); 149 | } 150 | 151 | { 152 | example.instance.exports.sub(10, 5, (res) => { 153 | test.strictEqual(res, 5); 154 | }); 155 | const res = await example.instance.exports.sub(10, 5); 156 | test.strictEqual(res, 5); 157 | } 158 | 159 | { 160 | example.instance.exports.sub(20, 10, (res) => { 161 | test.strictEqual(res, 10); 162 | }); 163 | const res = await example.instance.exports.sub(20, 10); 164 | test.strictEqual(res, 10); 165 | } 166 | 167 | test.end(); 168 | }); 169 | 170 | metatests.test('C++ WASM (web)', async (test) => { 171 | const { load } = await import(DIST); 172 | await timers.setTimeout(100); 173 | 174 | const fileName = BASE + 'cpp.wasm'; 175 | const example = await load(fileName); 176 | 177 | { 178 | const res = example.instance.exports.sum(3, 7); 179 | test.strictEqual(res, 10); 180 | } 181 | 182 | { 183 | example.instance.exports.add(1, 1, (res) => { 184 | test.strictEqual(res, 2); 185 | }); 186 | const res = await example.instance.exports.add(1, 1); 187 | test.strictEqual(res, 2); 188 | } 189 | 190 | { 191 | example.instance.exports.add(2, 2, (res) => { 192 | test.strictEqual(res, 4); 193 | }); 194 | const res = await example.instance.exports.add(2, 2); 195 | test.strictEqual(res, 4); 196 | } 197 | 198 | { 199 | example.instance.exports.sub(10, 5, (res) => { 200 | test.strictEqual(res, 5); 201 | }); 202 | const res = await example.instance.exports.sub(10, 5); 203 | test.strictEqual(res, 5); 204 | } 205 | 206 | { 207 | example.instance.exports.sub(20, 10, (res) => { 208 | test.strictEqual(res, 10); 209 | }); 210 | const res = await example.instance.exports.sub(20, 10); 211 | test.strictEqual(res, 10); 212 | } 213 | 214 | test.end(); 215 | }); 216 | 217 | metatests.test('Zig WASM (web)', async (test) => { 218 | const { load } = await import(DIST); 219 | await timers.setTimeout(100); 220 | 221 | const fileName = BASE + 'zig.wasm'; 222 | const example = await load(fileName); 223 | 224 | { 225 | const res = example.instance.exports.sum(3, 7); 226 | test.strictEqual(res, 10); 227 | } 228 | 229 | { 230 | example.instance.exports.add(1, 1, (res) => { 231 | test.strictEqual(res, 2); 232 | }); 233 | const res = await example.instance.exports.add(1, 1); 234 | test.strictEqual(res, 2); 235 | } 236 | 237 | { 238 | example.instance.exports.add(2, 2, (res) => { 239 | test.strictEqual(res, 4); 240 | }); 241 | const res = await example.instance.exports.add(2, 2); 242 | test.strictEqual(res, 4); 243 | } 244 | 245 | { 246 | example.instance.exports.sub(10, 5, (res) => { 247 | test.strictEqual(res, 5); 248 | }); 249 | const res = await example.instance.exports.sub(10, 5); 250 | test.strictEqual(res, 5); 251 | } 252 | 253 | { 254 | example.instance.exports.sub(20, 10, (res) => { 255 | test.strictEqual(res, 10); 256 | }); 257 | const res = await example.instance.exports.sub(20, 10); 258 | test.strictEqual(res, 10); 259 | } 260 | 261 | test.end(); 262 | }); 263 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Testing CI 2 | 3 | on: pull_request 4 | 5 | env: 6 | WASM_SOURCES_PATH: './test/examples' 7 | WASM_BINARIES_PATH: './test/examples' 8 | WASM_BINARIES_PATH_WIN: '.\test\examples' 9 | WASM_MODULE_NAME: 'example' 10 | 11 | jobs: 12 | build_wasm_artifacts--rust: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Install rust toolchain 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: stable 21 | profile: minimal 22 | 23 | - name: Get 'wasm-pack' tool from cache ... 24 | uses: actions/cache@v3 25 | id: cache-wasm-pack 26 | with: 27 | path: ~/.cargo/bin/wasm-pack 28 | key: ${{ runner.os }}-wasm-pack 29 | 30 | - name: ... or install and cache 'wasm-pack' tool 31 | if: ${{ steps.cache-wasm-pack.outputs.cache-hit != 'true' }} 32 | run: cargo install wasm-pack 33 | 34 | - run: | 35 | cd ${{ env.WASM_SOURCES_PATH }}/Rust 36 | wasm-pack build --target nodejs 37 | mv pkg/${{ env.WASM_MODULE_NAME }}_bg.wasm pkg/${{ env.WASM_MODULE_NAME }}.wasm 38 | 39 | - uses: actions/upload-artifact@v3 40 | with: 41 | name: artifacts--rust 42 | path: ${{ env.WASM_SOURCES_PATH }}/Rust/pkg/${{ env.WASM_MODULE_NAME }}.wasm 43 | 44 | build_wasm_artifacts--cpp: 45 | runs-on: ubuntu-latest 46 | env: 47 | EM_VERSION: 3.1.44 48 | steps: 49 | - uses: actions/checkout@v3 50 | 51 | - name: Get 'emscripten-sdk' from cache ... 52 | uses: actions/cache@v3 53 | id: cache-emscripten-sdk 54 | with: 55 | path: emsdk/ 56 | key: emscripten-sdk-${{ env.EM_VERSION }}${{ runner.os }}-wasm-pack 57 | 58 | - name: ... or install and cache 'emscripten-sdk' 59 | if: ${{ steps.cache-emscripten-sdk.outputs.cache-hit != 'true' }} 60 | run: | 61 | git clone https://github.com/emscripten-core/emsdk.git 62 | cd emsdk 63 | ./emsdk install ${{ env.EM_VERSION }} 64 | ./emsdk activate ${{ env.EM_VERSION }} 65 | 66 | - run: | 67 | source ./emsdk/emsdk_env.sh 68 | cd ${{ env.WASM_SOURCES_PATH }}/C++ 69 | em++ -std=c++11 ${{ env.WASM_MODULE_NAME }}.cpp -Os -s WASM=1 -s SIDE_MODULE=1 -o ${{ env.WASM_MODULE_NAME }}.wasm 70 | 71 | - uses: actions/upload-artifact@v3 72 | with: 73 | name: artifacts--cpp 74 | path: ${{ env.WASM_SOURCES_PATH }}/C++/${{ env.WASM_MODULE_NAME }}.wasm 75 | 76 | build_wasm_artifacts--as: 77 | runs-on: ubuntu-latest 78 | steps: 79 | - uses: actions/checkout@v3 80 | 81 | - name: Get AssemblyScript deps from cache ... 82 | id: cache-deps 83 | uses: actions/cache@v3 84 | with: 85 | path: | 86 | ${{ env.WASM_SOURCES_PATH }}/AssemblyScript/node_modules/ 87 | key: AssemblyScript-node_modules-${{ hashFiles(format('{0}/AssemblyScript/package-lock.json', env.WASM_SOURCES_PATH)) }} 88 | 89 | - name: ... or install and cache AssemblyScript deps 90 | if: ${{ steps.cache-deps.outputs.cache-hit != 'true' }} 91 | run: | 92 | cd ${{ env.WASM_SOURCES_PATH }}/AssemblyScript 93 | npm i 94 | 95 | - run: | 96 | cd ${{ env.WASM_SOURCES_PATH }}/AssemblyScript 97 | npx asc ${{ env.WASM_MODULE_NAME }}.ts --outFile ${{ env.WASM_MODULE_NAME }}.wasm --optimize 98 | 99 | - uses: actions/upload-artifact@v3 100 | with: 101 | name: artifacts--as 102 | path: ${{ env.WASM_SOURCES_PATH }}/AssemblyScript/${{ env.WASM_MODULE_NAME }}.wasm 103 | 104 | build_wasm_artifacts--wat: 105 | runs-on: ubuntu-latest 106 | steps: 107 | - uses: actions/checkout@v3 108 | 109 | - name: Get 'wabt' tools from cache ... 110 | uses: actions/cache@v3 111 | id: cache-wabt-tools 112 | with: 113 | path: wabt/ 114 | key: wabt-${{ runner.os }} 115 | 116 | - name: ... or install and cache 'wabt' tools 117 | if: ${{ steps.cache-wabt-tools.outputs.cache-hit != 'true' }} 118 | run: | 119 | git clone --recursive https://github.com/WebAssembly/wabt 120 | mkdir wabt/build && cd wabt/build 121 | cmake .. && cmake --build . 122 | 123 | - run: | 124 | cd ${{ env.WASM_SOURCES_PATH }}/WAT 125 | $GITHUB_WORKSPACE/wabt/bin/wat2wasm ${{ env.WASM_MODULE_NAME }}.wat -o ${{ env.WASM_MODULE_NAME }}.wasm 126 | 127 | - uses: actions/upload-artifact@v3 128 | with: 129 | name: artifacts--wat 130 | path: ${{ env.WASM_SOURCES_PATH }}/WAT/${{ env.WASM_MODULE_NAME }}.wasm 131 | 132 | build_wasm_artifacts--zig: 133 | runs-on: ubuntu-latest 134 | env: 135 | ZIG_VERSION: 0.11.0 136 | ZIG_FOR_OS: linux 137 | ZIG_FOR_ARCH: x86_64 138 | steps: 139 | - uses: actions/checkout@v3 140 | 141 | - name: Get Zig compiler from cache ... 142 | uses: actions/cache@v3 143 | id: cache-zig-compiler 144 | with: 145 | path: zig/ 146 | key: zig-v${{ env.ZIG_VERSION }}-${{ runner.os }} 147 | 148 | - name: ... or download and cache Zig compiler 149 | if: ${{ steps.cache-zig-compiler.outputs.cache-hit != 'true' }} 150 | run: | 151 | BUNDLE_NAME=zig-${{ env.ZIG_FOR_OS}}-${{ env.ZIG_FOR_ARCH }}-${{ env.ZIG_VERSION }} 152 | wget https://ziglang.org/download/${{ env.ZIG_VERSION }}/$BUNDLE_NAME.tar.xz 153 | tar -xf $BUNDLE_NAME.tar.xz && mv $BUNDLE_NAME zig && rm $BUNDLE_NAME.tar.xz 154 | 155 | - run: | 156 | cd ${{ env.WASM_SOURCES_PATH }}/Zig 157 | $GITHUB_WORKSPACE/zig/zig build-lib ${{ env.WASM_MODULE_NAME }}.zig -target wasm32-freestanding -dynamic -rdynamic 158 | 159 | - uses: actions/upload-artifact@v3 160 | with: 161 | name: artifacts--zig 162 | path: ${{ env.WASM_SOURCES_PATH }}/Zig/${{ env.WASM_MODULE_NAME }}.wasm 163 | 164 | lint_and_test: 165 | needs: 166 | - build_wasm_artifacts--rust 167 | - build_wasm_artifacts--cpp 168 | - build_wasm_artifacts--as 169 | - build_wasm_artifacts--wat 170 | - build_wasm_artifacts--zig 171 | 172 | runs-on: ${{ matrix.os }} 173 | 174 | strategy: 175 | matrix: 176 | node: 177 | - 18 178 | - 20 179 | os: 180 | - ubuntu-latest 181 | - windows-latest 182 | - macos-latest 183 | 184 | steps: 185 | - uses: actions/checkout@v3 186 | 187 | - name: Download WASM binaries 188 | uses: actions/download-artifact@v3 189 | 190 | - name: Swap commited binaries with CI binaries 191 | if: ${{ matrix.os != 'windows-latest' }} 192 | run: | 193 | rm ${{ env.WASM_BINARIES_PATH}}/*.wasm 194 | mv artifacts--rust/${{ env.WASM_MODULE_NAME }}.wasm ${{ env.WASM_BINARIES_PATH}}/rust.wasm 195 | mv artifacts--cpp/${{ env.WASM_MODULE_NAME }}.wasm ${{ env.WASM_BINARIES_PATH}}/cpp.wasm 196 | mv artifacts--as/${{ env.WASM_MODULE_NAME }}.wasm ${{ env.WASM_BINARIES_PATH}}/as.wasm 197 | mv artifacts--wat/${{ env.WASM_MODULE_NAME }}.wasm ${{ env.WASM_BINARIES_PATH}}/wat.wasm 198 | mv artifacts--zig/${{ env.WASM_MODULE_NAME }}.wasm ${{ env.WASM_BINARIES_PATH}}/zig.wasm 199 | rm -r artifacts--* 200 | 201 | - name: Swap commited binaries with CI binaries (Windows) 202 | if: ${{ matrix.os == 'windows-latest' }} 203 | run: | 204 | Remove-Item -Path ${{ env.WASM_BINARIES_PATH_WIN }}\*.wasm 205 | Move-Item -Path artifacts--rust\${{ env.WASM_MODULE_NAME }}.wasm -Destination ${{ env.WASM_BINARIES_PATH_WIN }}\rust.wasm 206 | Move-Item -Path artifacts--cpp\${{ env.WASM_MODULE_NAME }}.wasm -Destination ${{ env.WASM_BINARIES_PATH_WIN }}\cpp.wasm 207 | Move-Item -Path artifacts--as\${{ env.WASM_MODULE_NAME }}.wasm -Destination ${{ env.WASM_BINARIES_PATH_WIN }}\as.wasm 208 | Move-Item -Path artifacts--wat\${{ env.WASM_MODULE_NAME }}.wasm -Destination ${{ env.WASM_BINARIES_PATH_WIN }}\wat.wasm 209 | Move-Item -Path artifacts--zig\${{ env.WASM_MODULE_NAME }}.wasm -Destination ${{ env.WASM_BINARIES_PATH_WIN }}\zig.wasm 210 | Remove-Item -Path artifacts--* -Recurse 211 | 212 | - name: Use Node.js ${{ matrix.node }} 213 | uses: actions/setup-node@v3 214 | with: 215 | node-version: ${{ matrix.node }} 216 | - uses: actions/cache@v3 217 | with: 218 | path: ~/.npm 219 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 220 | restore-keys: | 221 | ${{ runner.os }}-node- 222 | - run: npm ci 223 | - run: npm test 224 | --------------------------------------------------------------------------------