torch2424 / as-bind

Isomorphic library to handle passing high-level data structures between AssemblyScript and JavaScript πŸ€πŸš€
https://torch2424.github.io/as-bind/
MIT License
245 stars 23 forks source link

as-bind

Build Status npm bundle size (minified) npm npm version GitHub

Isomorphic library to handle passing high-level data structures between AssemblyScript and JavaScript. πŸ€πŸš€

Markdown Parser Demo

Asbind Markdown Parser Demo Gif

Table of Contents

Features

Installation

You can install as-bind in your project by running the following:

npm install --save as-bind

Quick Start

1. Compiling your Assemblyscript

To enable as-bind for your AssemblyScript Wasm modules, add the as-bind transform when compiling your module:

asc your-entryfile.ts --exportRuntime --transform as-bind [...other cli options...]

The things to notice are:

For optional testing purposes , let's export an example function we can try in your-entryfile.ts:

export function myExportedFunctionThatTakesAString(value: string): string {
  return "AsBind: " + value;
}

2. In your Javascript

For browser JavaScript. We can do the following:

// If you are using a Javascript bundler, use the ESM bundle with import syntax
import * as AsBind from "as-bind";

// If you are not using a bundler add a <script> tag to your HTML
// Where the `src` points to the iife bundle (as-bind.iife.js), for example: https://unpkg.com/as-bind
// Then, INSTEAD of using the import syntax, do: `const { AsBind } = AsBindIIFE;`

const wasm = fetch("./path-to-my-wasm.wasm");

const asyncTask = async () => {
  const asBindInstance = await AsBind.instantiate(wasm);

  // You can now use your wasm / as-bind instance!
  const response = asBindInstance.exports.myExportedFunctionThatTakesAString(
    "Hello World!"
  );
  console.log(response); // AsBind: Hello World!
};
asyncTask();

For Node JavaScript, we would use the CommonJS bundle by doing the following:

// We need to import the direct as-bind.cjs.js for Node applications.
// This is because the default "main" key in the `package.json`,
// is the as-bind transform script
const AsBind = require("as-bind/dist/as-bind.cjs.js");

const fs = require("fs");

const wasm = fs.readFileSync("./path-to-my-wasm.wasm");

const asyncTask = async () => {
  const asBindInstance = await AsBind.instantiate(wasm);

  // You can now use your wasm / as-bind instance!
  const response = asBindInstance.exports.myExportedFunctionThatTakesAString(
    "Hello World!"
  );
  console.log(response); // AsBind: Hello World!
};
asyncTask();

Additional Examples

Passing a high-level type to a an exported function, and returning a high-level type

See the Quick Start

Passing a high-level type to an imported function

In this example, we will implement a console.log that we can call from AssemblyScript!

AssemblyScript

Inside of myWasmFileName.ts:

declare function consoleLog(message: string): void;

export function myExportedFunctionThatWillCallConsoleLog(): void {
  consoleLog("Hello from AS!");
}

JavaScript

import { AsBind } from "as-bind";

const wasm = fetch("./path-to-my-wasm.wasm");

const asyncTask = async () => {
  // Instantiate the wasm file, and pass in our importObject
  const asBindInstance = await AsBind.instantiate(wasm, {
    myWasmFileName: {
      consoleLog: message => {
        console.log(message);
      }
    }
  });

  // Should call consoleLog, and log: "Hello from AS!"
  asBindInstance.exports.myExportedFunctionThatWillCallConsoleLog();
};
asyncTask();

Supported Data Types

All primitive types, ie. Numbers (u8, f32, ...) , Strings, Typed Arrays (Uint8Array, Float32Array, ...) are supported. All of those types can also be used with Array<T>.

Custom classes are currently not support, but planned.

Browser SDK

as-bind works with the Browser SDK. For a fully working example, see the browser-sdk example.

Reference API

AsBind

The default exported ESM class of as-bind, also available as import { AsBind } from "as-bind" / const { AsBind } = require('as-bind').

Class Properties

The AsBind class is meant to vaguely act as the WebAssembly Object exposed to JavaScript environments.

version

AsBind.version: string

Value that is the current version of your imported AsBind.

instantiate
AsBind.instantiate: (
  moduleOrBuffer: (
    WebAssembly.Module |
    BufferSource |
    Response |
    PromiseLike<WebAssembly.Module>
  ),
  imports?: WasmImports
) => Promise<AsBindInstance>`

This function is the equivalent to the AssemblyScript Loader instantiate function, which is similar to the WebAssembly.instantiateStreaming function. It essentially takes as its parameters:

instantiateSync
AsBind.instantiateSync: (
  moduleOrBuffer: (
    WebAssembly.Module |
    BufferSource
  ),
  imports?: WasmImports
) => AsBindInstance`

This is a synchronous version of AsBind.instantiate. This does not accept a promise-like as its module, and returns an AsBindInstance instead of a Promise that resolves an AsBindInstance. This is only reccomended for use in testing or development. Please see the Documentation sections for AsBind.instantiate for more information.

Instance Properties

An AsBindInstance is vaguely similar to a WebAssembly instance.

loadedModule

The raw, untouched instance of the WebAssembly Module.

exports

Similar to to WebAssembly.Instance.prototype.exports, this is an object containing all of the exported fields from the WebAssembly module. However, exported functions are bound / wrapped in which they will handle passing the supported high-level data types to the exported AssemblyScript function.

importObject

Similar to to WebAssembly.instantiateStreaming() importObject, this is the augmented importObject. It’s based on the original that was passed to one of the instantiation functions, but functions have been wrapped to handle high-level types.

Motivation

This library was inspired by several chats I had with some awesome buddies of mine in the WebAssembly Community:

Thus, this library was made to help AssemblyScript/JavaScript users build awesome things! I also want to give a huge thanks to the AssemblyScript team and community for the help they provided me. I'm super appreciative of you all! πŸ˜„πŸŽ‰

(Also! Huge Shoutout to @surma for doing the awesome refactor to use AssemblyScript compiler transforms! πŸ™)

Performance

TL;DR This library should be pretty darn fast. πŸ€”

The transform embeds all the required type information of imported and exported functions into a custom section of the WebAssembly module. All the runtime does is utilize the AssemblyScript Loader to convert these types from JS to ASC and vice-versa. Apart from Array<T>, which needs to be handled recursively, the overhead is fairly static and minimal.

In the future, these types of high-level data passing tools might not be needed at all, as the WebAssembly Inteface Types proposal aims to give WebAssembly an understanding of higher-level types.

Projects using as-bind

If you're project is using as-bind, and you would like to be featured here. Please open a README with links to your project, and if appropriate, explaining how as-bind is being used. 😊

Contributing

Contributions are definitely welcome! Feel free to open a PR for small fixes such as typos and things. Larger fixes, or new features should start out as an issue for discussion, in which then a PR should be made. πŸ₯³

This project will also adhere to the AssemblyScript Code of Conduct.

License

MIT. πŸ“