npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@takahashim/mruby-wasm-js

v0.2.0

Published

Minimal mruby ↔ JavaScript bridge for WebAssembly. Run mruby in browsers and Node with a JS::Object DOM/JS interop API.

Downloads

312

Readme

mruby-wasm-js

Minimal mruby ↔ JavaScript bridge for WASM hosts. Lets Ruby code running in an mruby VM compiled to WebAssembly call into the JS host (browser, Node, etc.) and vice versa, with a ruby.wasm-compatible feel (JS.global[:document][:title] = "hello").

Layout

  • src/ — C primitives compiled into the wasm
  • mrblib/js.rb — Ruby surface (JS module + JS::Object class)
  • js/ — JS-side adapter (createVM factory + bundled WASI preview1)
  • wasm_spec/ — Node-driven test suite. Named wasm_spec/ (not test/) to opt out of mruby-test's test/*.rb auto-discovery, since the tests assume a JS host and can't run inside the standard mrbtest binary.

Using the gem

1. Add it to your build_config

MRuby::CrossBuild.new("wasi") do |conf|
  conf.toolchain :clang
  # ... your wasi-sdk setup ...
  conf.gembox "default-no-stdio"
  conf.gem File.expand_path("path/to/mruby-wasm-js")
  # mruby-method / mruby-fiber / mruby-compiler are pulled in
  # transitively via the gem's add_dependency declarations.
  conf.gem core: "mruby-io"       # optional (File.read/open via WASI fs)
  conf.gem core: "mruby-time"     # optional (Time.now via WASI clock_time_get)
  conf.gem core: "mruby-random"   # optional (rand/Random via WASI random_get)
  # Optional sibling gems for WASI primitives mruby core doesn't ship.
  conf.gem File.expand_path("path/to/mruby-wasi-dir")  # Dir.entries / mkdir / rmdir / exist?
  conf.gem File.expand_path("path/to/mruby-wasi-env")  # ENV[] / ENV[]= / each / keys / ...
end

The gem's C side declares WASM imports under the module name js (e.g. js.js_eval, js.js_call). Your linker must allow these undefined symbols:

-Wl,--allow-undefined -Wl,--export=js_invoke_proc -Wl,--export=js_eval_handle

2. Spawn a VM from the JS host

// Via npm / bare specifier (preferred — package.json#main resolves
// to ./index.js):
import { createVM } from "@takahashim/mruby-wasm-js";

// Or via explicit path (vendored / unpublished consumers):
import { createVM } from "./vendor/mruby-wasm-js/index.js";

const vm = await createVM({ wasm: "/path/to/mruby-js.wasm" });
vm.eval("puts JS.global[:navigator][:userAgent].to_s");

TypeScript users get type definitions out of the box (index.d.ts ships with the package; createVM is overloaded so the returned shape narrows depending on whether you pass wasi or not).

createVM(options) fetches the wasm, instantiates it with all required imports (js.* for the JS imports, wasi_snapshot_preview1.* for puts, Time.now, File.read, etc.), runs the reactor's _initialize entry, and returns a VM handle with all per-instance state. Each createVM() call gets an independent handle table + WASI state — multiple VMs can coexist in one process (useful for tests, sandboxing, hot reload).

vm.eval(source) parses + runs Ruby source on the live VM. Each call is auto-wrapped in a Fiber so JS::Object#await works at top level.

Common browser helpers are exposed directly on JS as thin wrappers:

JS.encode_uri_component("a b/c?d")   # => "a%20b%2Fc%3Fd"
JS.decode_uri_component("a%20b")     # => "a b"

The VM handle exposes:

| Property | Purpose | |---|---| | vm.eval(src) | parse + run Ruby; returns 0 / 1 | | vm.fs | Map-like facade over the tree VFS | | vm.env | mutable env hash | | vm.args | mutable argv array | | vm.stdin | feed stdin (bytes, pushText(s)) | | vm.instance | underlying WebAssembly.Instance | | vm.alloc / vm.get / vm.release | low-level handle table | | vm.handleCount() | live JS handle count (for leak detection) |

Module-level exports:

| Export | Purpose | |---|---| | createVM(options) | the factory above | | Directory / File | tree-VFS node classes | | createFsFacade(root) | wrap a Directory tree as a Map-style fs facade | | debug | global debug toggle ({ trace: false }) |

createVM options

| Option | Default | Notes | |---|---|---| | wasm (string, required) | — |URL to mruby-js.wasm | | env (object) | {} | initial environ, available to mruby via wasi-libc's getenv | | args (string[]) | ["mruby-wasm-js"] | initial argv (args[1..] lands in Ruby's ARGV) | | stdin (string | Uint8Array) | "" | initial stdin payload for STDIN.read / gets | | fs (Directory) | empty Directory | declarative initial tree (or use vm.fs.set(...) after creation) | | wasi (object) | bundled in-memory impl | replacement wasi_snapshot_preview1 import object | | onStart (function) | calls _initialize() (or _start() as fallback) | post-instantiate callback; override for shims that need to bind the instance themselves |

Populating the virtual filesystem

import { createVM, Directory, File } from "@takahashim/mruby-wasm-js";

// 1. Declarative — hand the whole tree to createVM.
const vm = await createVM({
  wasm: "/path/to/mruby-js.wasm",
  fs: new Directory({
    data: new Directory({
      "poem.vtt": new File(new TextEncoder().encode("WEBVTT\n...")),
    }),
    empty_dir: new Directory(),
  }),
});

// 2. Map-style after creation — auto-creates intermediate Directory nodes.
vm.fs.set("/config/app.json", new TextEncoder().encode("{}"));

vm.fs supports set / get / has / delete / entries / keys / values / Symbol.iterator / clear / size (Map-compatible), plus populate(dir) and root for tree access. Iteration yields only File leaves, in tree-traversal order.

3. Dispatch from Ruby

doc = JS.global[:document]                  # property access
doc.title = "hello"                         # method_missing → setter
doc.getElementById("audio")                 # method_missing → JS call

button.on(:click) { |ev| puts ev[:type].to_s }   # block as callback
JS.global[:Promise].resolve(42).then { |v| ... } # JS Promise
JS.global[:Promise].resolve(42).await            # blocking await (Fiber-based)
JS.global[:Date].new(2026, 4, 8)            # JS constructor

See mrblib/js.rb for the full surface (==, typeof, instanceof?, to_a, each, to_proc, inspect, ...).

Running the spec

The spec suite requires a JS host. From within this gem directory:

node wasm_spec/runner.mjs

By default it looks for mruby-js.wasm at ../../../build/mruby-js.wasm (matches the repo's layout where make js writes the build output to build/). To point at any other location, set the env var:

MRUBY_WASM_PATH=/abs/path/to/your/mruby-js.wasm \
  node wasm_spec/runner.mjs

Exit code 0 on success, 1 on any failure.

Dependencies

Required gems (mruby-method, mruby-fiber, mruby-compiler) are declared in mrbgem.rake and pulled in transitively. Add the following yourself if you want the corresponding Ruby surfaces backed by WASI: mruby-io, mruby-time, mruby-random.

Tested against mruby 4.0.0. Runs in any modern browser or Node.js with WebAssembly Exception Handling support — no flags or experimental options required.

Related projects

This gem ships the JS-host build of upstream mruby on WebAssembly. A sibling command / wasmtime build (same upstream mruby, no JS bridge) is built into dist/mruby-wasm-cmd/ from the same repo (mruby-wasm-runtime). Useful for sandboxing and running mruby scripts from wasmtime / node:wasi / other preview1 hosts. Build it via make cmd from the repo root.

If you're doing mruby on WASM more broadly, see also:

  • ruby/ruby.wasm — official CRuby on WebAssembly. Heavier (full CRuby) but more compatible with CRuby gems. The structural inspiration for this gem; createVM is shaped after the same factory-returning-VM-handle pattern.

License

MIT (see mrbgem.rake).