ruby-wasm-wasi
WebAssembly port of CRuby with WASI.
The CRuby source code is available at a working branch.
Installation
For instaling ruby-wasm-wasi, just run this command in your shell:
Ruby head
$ npm install --save ruby-head-wasm-wasi
Quick Start (for Node.js)
See the example project for more details.
import fs from "fs/promises";
import { WASI } from "wasi";
import { RubyVM } from "ruby-head-wasm-wasi";
const main = async () => {
const wasi = new WASI();
const binary = await fs.readFile(
"./node_modules/ruby-head-wasm-wasi/ruby.wasm"
);
const vm = new RubyVM();
const imports = {
wasi_snapshot_preview1: wasi.wasiImport,
};
// Add imports for WebAssembly instantiation
vm.addToImports(imports);
// Instantiate the WebAssembly module
const { instance } = await WebAssembly.instantiate(binary.buffer, imports);
// Set instance to vm
await vm.setInstance(instance);
// Initialize WASI application
wasi.initialize(instance);
// Initialize Ruby VM
vm.initialize();
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.
import { WASI } from "@wasmer/wasi";
import { WasmFs } from "@wasmer/wasmfs";
import { RubyVM } from "ruby-head-wasm-wasi";
const main = async () => {
// Setup WASI and FileSystem emulation
const wasmFs = new WasmFs();
const wasi = new WASI({
bindings: {
...WASI.defaultBindings,
fs: wasmFs.fs,
},
});
// (Optional) Forward stdout/stderr to console
const originalWriteSync = wasmFs.fs.writeSync.bind(wasmFs.fs);
wasmFs.fs.writeSync = (fd, buffer, offset, length, position) => {
const text = new TextDecoder("utf-8").decode(buffer);
const handlers = {
1: (line) => console.log(line),
2: (line) => console.warn(line),
};
if (handlers[fd]) handlers[fd](text);
return originalWriteSync(fd, buffer, offset, length, position);
};
// Fetch and instantiate WebAssembly binary
const response = await fetch(
"./node_modules/ruby-head-wasm-wasi/dist/ruby.wasm"
);
const buffer = await response.arrayBuffer();
const vm = new RubyVM();
const imports = {
wasi_snapshot_preview1: wasi.wasiImport,
};
// Add imports for WebAssembly instantiation
vm.addToImports(imports);
// Instantiate the WebAssembly module
const { instance } = await WebAssembly.instantiate(buffer, imports);
// Set instance to vm
await vm.setInstance(instance);
// Initialize WASI application
wasi.setMemory(instance.exports.memory);
// Initialize Ruby VM
vm.initialize();
vm.eval(`
require "js"
luckiness = ["Lucky", "Unlucky"].sample
JS::eval("document.body.innerText = '#{luckiness}'")
`);
};
main();
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);
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
RbValue
A RbValue is an object that represents a value in Ruby
Parameters
inner
vm
exporter
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
- See: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive
Parameters
hint
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
Parameters
message
Building the package from source
For building the package from source, you need to prepare a Ruby build produced by WASI SDK, and you need wit-bindgen
and wasm-opt
in your PATH.
The instructions for building a Ruby targeting WebAssembly are available at: TODO.
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/kateinoigakukun/ruby.wasm/packages/ruby-wasm-wasi/src/bindgen/intrinsics.js"
Generating "/Users/katei/.ghq/github.com/kateinoigakukun/ruby.wasm/packages/ruby-wasm-wasi/src/bindgen/rb-abi-guest.d.ts"
Generating "/Users/katei/.ghq/github.com/kateinoigakukun/ruby.wasm/packages/ruby-wasm-wasi/src/bindgen/rb-abi-guest.js"
Generating "/Users/katei/.ghq/github.com/kateinoigakukun/ruby.wasm/packages/ruby-wasm-wasi/src/bindgen/rb-js-abi-host.d.ts"
Generating "/Users/katei/.ghq/github.com/kateinoigakukun/ruby.wasm/packages/ruby-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