ruby-head-wasm-wasi
TypeScript icon, indicating that this package has built-in type declarations

2.3.0 • Public • Published

ruby-head-wasm-wasi

WebAssembly port of CRuby with WASI.

This package distributes the latest master branch of CRuby.

Installation

For installing ruby-head-wasm-wasi family, just run this command in your shell:

$ npm install --save ruby-head-wasm-wasi@latest
# or if you want the nightly snapshot
$ npm install --save ruby-head-wasm-wasi@next
# or you can specify the exact snapshot version
$ npm install --save ruby-head-wasm-wasi@2.3.0-2023-11-26-a

Quick Start (for Node.js)

See the example project for more details.

import fs from "fs/promises";
import { DefaultRubyVM } from "@ruby/wasm-wasi/dist/node.cjs.js";

const main = async () => {
  const binary = await fs.readFile(
    //  Tips: Replace the binary with debug info if you want symbolicated stack trace.
    //  (only nightly release for now)
    //  "./node_modules/ruby-head-wasm-wasi/dist/ruby.debug+stdlib.wasm"
    "./node_modules/ruby-head-wasm-wasi/dist/ruby.wasm"
  );
  const module = await WebAssembly.compile(binary);
  const { vm } = await DefaultRubyVM(module);

  vm.eval(`
    luckiness = ["Lucky", "Unlucky"].sample
    puts "You are #{luckiness}"
  `);
};

main();

Then you can run the example project in your terminal:

$ node --experimental-wasi-unstable-preview1 index.node.js

Quick Start (for Browser)

In browser, you need a WASI polyfill. See the example project for more details.

<html>
  <script src="https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@latest/dist/browser.umd.js"></script>
  <script>
    const { DefaultRubyVM } = window["ruby-wasm-wasi"];
    const main = async () => {
      // Fetch and instantiate WebAssembly binary
      const response = await fetch(
        //      Tips: Replace the binary with debug info if you want symbolicated stack trace.
        //      (only nightly release for now)
        //      "https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@next/dist/ruby.debug+stdlib.wasm"
        "https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/ruby.wasm"
      );
      const buffer = await response.arrayBuffer();
      const module = await WebAssembly.compile(buffer);
      const { vm } = await DefaultRubyVM(module);

      vm.printVersion();
      vm.eval(`
        require "js"
        luckiness = ["Lucky", "Unlucky"].sample
        JS::eval("document.body.innerText = '#{luckiness}'")
      `);
    };

    main();
  </script>
  <body></body>
</html>

GC limitation with JavaScript interoperability

Since JavaScript's GC system and Ruby's GC system are separated and not cooperative, they cannot collect cyclic references between JavaScript and Ruby objects.

The following code will cause a memory leak:

class JNode {
  setRNode(rnode) {
    this.rnode = rnode;
  }
}
jnode = new JNode();

rnode = vm.eval(`
class RNode
  def set_jnode(jnode)
    @jnode = jnode
  end
end
RNode.new
`);

rnode.call("set_jnode", vm.wrap(jnode));
jnode.setRNode(rnode);

APIs

Table of Contents

RubyVM

A Ruby VM instance

Examples

const wasi = new WASI();
const vm = new RubyVM();
const imports = {
  wasi_snapshot_preview1: wasi.wasiImport,
};

vm.addToImports(imports);

const instance = await WebAssembly.instantiate(rubyModule, imports);
await vm.setInstance(instance);
wasi.initialize(instance);
vm.initialize();

initialize

Initialize the Ruby VM with the given command line arguments

Parameters
  • args The command line arguments to pass to Ruby. Must be an array of strings starting with the Ruby program name. (optional, default ["ruby.wasm","--disable-gems","-e_=0"])

setInstance

Set a given instance to interact JavaScript and Ruby's WebAssembly instance. This method must be called before calling Ruby API.

Parameters
  • instance The WebAssembly instance to interact with. Must be instantiated from a Ruby built with JS extension, and built with Reactor ABI instead of command line.

addToImports

Add intrinsic import entries, which is necessary to interact JavaScript and Ruby's WebAssembly instance.

Parameters
  • imports The import object to add to the WebAssembly instance

printVersion

Print the Ruby version to stdout

eval

Runs a string of Ruby code from JavaScript

Parameters
  • code The Ruby code to run
Examples
vm.eval("puts 'hello world'");
const result = vm.eval("1 + 2");
console.log(result.toString()); // 3

Returns any the result of the last expression

evalAsync

Runs a string of Ruby code with top-level JS::Object#await Returns a promise that resolves when execution completes.

Parameters
  • code The Ruby code to run
Examples
const text = await vm.evalAsync(`
  require 'js'
  response = JS.global.fetch('https://example.com').await
  response.text.await
`);
console.log(text.toString()); // <html>...</html>

Returns any a promise that resolves to the result of the last expression

wrap

Wrap a JavaScript value into a Ruby JS::Object

Parameters
  • value The value to convert to RbValue
Examples
const hash = vm.eval(`Hash.new`);
hash.call("store", vm.eval(`"key1"`), vm.wrap(new Object()));

Returns any the RbValue object representing the given JS value

RbValue

A RbValue is an object that represents a value in Ruby

call

Call a given method with given arguments

Parameters
  • callee name of the Ruby method to call
  • args ...any arguments to pass to the method. Must be an array of RbValue
Examples
const ary = vm.eval("[1, 2, 3]");
ary.call("push", 4);
console.log(ary.call("sample").toString());

toPrimitive

Parameters
  • hint Preferred type of the result primitive value. "number", "string", or "default".

toString

Returns a string representation of the value by calling to_s

toJS

Returns a JavaScript object representation of the value by calling to_js.

Returns null if the value is not convertible to a JavaScript object.

RbError

Extends Error

Error class thrown by Ruby execution

Building the package from source

The instructions for building a Ruby targeting WebAssembly are available here.

Then, you can run the following command in your shell:

# Check the directory structure of your Ruby build
$ tree -L 3 path/to/wasm32-unknown-wasi-full-js/
path/to/wasm32-unknown-wasi-full-js/
├── usr
│   └── local
│       ├── bin
│       ├── include
│       ├── lib
│       └── share
└── var
    └── lib
        └── gems
$ ./build-package.sh path/to/wasm32-unknown-wasi-full-js/
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-head-wasm-wasi/src/bindgen/intrinsics.js"
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-head-wasm-wasi/src/bindgen/rb-abi-guest.d.ts"
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-head-wasm-wasi/src/bindgen/rb-abi-guest.js"
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-head-wasm-wasi/src/bindgen/rb-js-abi-host.d.ts"
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-head-wasm-wasi/src/bindgen/rb-js-abi-host.js"

src/index.ts → dist/index.umd.js, dist/index.esm.js, dist/index.cjs.js...
created dist/index.umd.js, dist/index.esm.js, dist/index.cjs.js in 682ms

Package Sidebar

Install

npm i ruby-head-wasm-wasi

Weekly Downloads

576

Version

2.3.0

License

MIT

Unpacked Size

107 MB

Total Files

25

Last publish

Collaborators

  • kateinoigakukun