---
title: ruby.wasm can also enable JavaScript to call Ruby libraries.
tags: 
author: [中島 滋](https://www.docswell.com/user/ledsun)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/GJ8DDK1KJD.jpg?width=480
description: RubyKagi 2026 の発表資料です。 これまでruby.wasmでやって来たブラウザ上でRubyからJavaScriptを呼ぶための実装の紹介と、これからJavaScriptからRubyを呼ぶためにしていく実装の作戦を発表しました。
published: April 27, 26
canonical: https://www.docswell.com/s/ledsun/Z27MPN-ruby_wasm_can_also_enable_JavaScript_to_call_Ruby_libraries
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/GJ8DDK1KJD.jpg)

ruby.wasm can also
enable JavaScript to call
Ruby libraries.
2026/04/23
RubyKaigi 2026


# Page. 2

![Page Image](https://bcdn.docswell.com/page/LJLMMZQPER.jpg)

Self Introduction
Luxiar co., Ltd.
Shigeru Nakajima, Chief Architect
ledsun on GitHub and Twitter
ko.rb organizer
5/11 meetup in Tokyo
株式会社ラグザイアはRubyKaigi 2026のシルバースポンサーです。
1/48


# Page. 3

![Page Image](https://bcdn.docswell.com/page/47MYYV127W.jpg)

Past RubyKaigi Presentations
Year
Title
2023 Load Gems from the Browser
2024 Using Ruby in the Browser Is Wonderful
2025 dRuby on Browser Again!
I was blown away by kateinoigakukun-san’s 2022 Keynote titled “Ruby meets
WebAssembly,” which reminded me of how much I’d wanted to write browser
2/48
applications in Ruby.


# Page. 4

![Page Image](https://bcdn.docswell.com/page/P7R992L5E9.jpg)

RubyKaigi 2023: Load Gems from the
Browser
The concept of require_relative
Implementing require_relative on top of
the browser’s fetch API made it possible to
load Ruby applications composed of multiple
files in the browser.
3/48


# Page. 5

![Page Image](https://bcdn.docswell.com/page/PJXQQDWX7X.jpg)

RubyKaigi 2024: Using Ruby in the Browser
Is Wonderful
Implemented require_relative
Example implementation
Ruby applications running in the browser
Frontend frameworks built with Ruby
I named that framework “Orbital ring”.
4/48


# Page. 6

![Page Image](https://bcdn.docswell.com/page/3JK99XV9JD.jpg)

RubyKaigi 2025: dRuby on Browser Again!
Implemented Ruby’s TCP-compatible sockets
using the browser’s WebSocket API
Called Ruby objects on a dRuby server from a
browser-based dRuby client
Presented different dRuby implementations
with youchan-san
5/48


# Page. 7

![Page Image](https://bcdn.docswell.com/page/LE3WW5M9E5.jpg)

Summary of Previous Presentations
Ruby applications running in the browser
Focused on “Ruby in the Browser”
Improved Ruby-to-JavaScript integration
6/48


# Page. 8

![Page Image](https://bcdn.docswell.com/page/8EDKKD687G.jpg)

Table of Contents
Prologue: Improving Ruby-to-JavaScript Calls
1: Why ruby.wasm Isn’t Used in Production
2: What if JavaScript-to-Ruby was possible?
3: Project Ruby’s Blanket
4: Practice
Conclusion
7/48


# Page. 9

![Page Image](https://bcdn.docswell.com/page/V7PKKXYWJ8.jpg)

Prologue: Improving Ruby-to-JavaScript
Calls
8/48


# Page. 10

![Page Image](https://bcdn.docswell.com/page/2JVVVLG9JQ.jpg)

Added to_js method
I added:
Methods for Ruby classes in ruby.wasm
Conversion from Ruby objects to JS::Object
instances
[1, 2, 3].to_js
{ a: 1, b: &quot;two&quot; }.to_js
nil.to_js
9/48


# Page. 11

![Page Image](https://bcdn.docswell.com/page/5EGLLX84JL.jpg)

What is JS::Object?
Ruby objects works in wasm memory, while
JavaScript objects works in JavaScript memory.
Ruby objects cannot directly reference
JavaScript objects, and vice versa.
In ruby.wasm, JS::Object is a wrapper class
that allows Ruby to reference JavaScript
objects.
10/48


# Page. 12

![Page Image](https://bcdn.docswell.com/page/4JQYY89R7P.jpg)

Implemented JS::RequireRemote
An internal class for require_relative
require &#039;js/require_remote&#039;
module Kernel
def require_relative(path)
JS::RequireRemote.instance.load(path)
end
end
11/48


# Page. 13

![Page Image](https://bcdn.docswell.com/page/K74WW2XQE1.jpg)

Changed the parent of JS::Object from
Object to BasicObject
Method calls on JS::Object use
method_missing
No longer conflicts with Object#send
ws = JS.global[:WebSocket].new(&quot;ws://localhost:9292&quot;)
ws[:onopen] = -&gt; (event) { ws.send(&quot;Hello&quot;) }
12/48


# Page. 14

![Page Image](https://bcdn.docswell.com/page/LJ1YYMNLEG.jpg)

Accessing properties as method calls results
in a TypeError
In ruby.wasm, to access JavaScript properties,
you need to use the [] syntax instead of
method calls.
JS.global.location =&gt; TypeError
JS.global[:location] =&gt; Correct Usage!
13/48


# Page. 15

![Page Image](https://bcdn.docswell.com/page/GJWGG34Q72.jpg)

Conversion from new method to constructor
JavaScript has a new operator to create
instances of classes, while Ruby uses the new
method.
// JavaScript
new URLSearchParams(location.search)
# Ruby
JS.global[:URLSearchParams].new(JS.global[:location][:search])
14/48


# Page. 16

![Page Image](https://bcdn.docswell.com/page/4EZLLV2373.jpg)

Added JS::True/JS::False
JavaScript’s false is not the same as Ruby’s
false, so JavaScript boolean methods would
always appear truthy in Ruby.
searchParams = JS.global[:URLSearchParams].new(JS.global[:location][:search])
# before
if searchParams.has(&#039;phrase&#039;) # =&gt; always true
# after
if searchParams.has(&#039;phrase&#039;) == JS::True # =&gt; true/false
15/48


# Page. 17

![Page Image](https://bcdn.docswell.com/page/Y76WW5VZ7V.jpg)

Method Calls with a ?
This feature was implemented by
Kateinoigakukun-san.
Expanded method calls
If the method name ends with ?, it returns a
Ruby boolean value
searchParams = JS.global[:URLSearchParams].new(JS.global[:location][:search])
if searchParams.has?(&#039;q&#039;) # =&gt; true/false
16/48


# Page. 18

![Page Image](https://bcdn.docswell.com/page/G75MMYD974.jpg)

Methods Can Accept Blocks
The constructor of JavaScript’s Promise
requires a function
Initialize a Promise using Ruby block syntax
// JavaScript
new Promise((resolve) =&gt; resolve(42));
# Ruby
JS.global[:Promise].new do |resolve, reject|
resolve.call(42)
end
17/48


# Page. 19

![Page Image](https://bcdn.docswell.com/page/9J299G85ER.jpg)

Promise Scheduler
It can await a Promise
This is a unique feature of ruby.wasm.
Neither Ruby nor JavaScript has a built-in
way to wait for Promise completion like this.
result = JS.global[:Promise].new do |resolve, reject|
resolve.call(42)
end.await # =&gt; 42
18/48


# Page. 20

![Page Image](https://bcdn.docswell.com/page/DEY44V66JM.jpg)

1: Why ruby.wasm Isn’t Used in Production
19/48


# Page. 21

![Page Image](https://bcdn.docswell.com/page/VJNYYG1178.jpg)

Calling JavaScript from Ruby Is Well
Supported
But ruby.wasm is not used in production yet.
20/48


# Page. 22

![Page Image](https://bcdn.docswell.com/page/YE9PPW5YJ3.jpg)

Why Isn’t It Used in Production?
The Wasm binary is large
JavaScript-to-Ruby support is weak
21/48


# Page. 23

![Page Image](https://bcdn.docswell.com/page/GE8DDNYKED.jpg)

The Wasm Binary Is Large
https://cdn.jsdelivr.net/npm/@ruby/head-wasmwasi@2.9.3-2.9.4/dist/ruby+stdlib.wasm:
Uncompressed file size: 33,343,825 bytes
(31.80 MiB)
Brotli compressed download size: 9,266,486
bytes (8.84 MiB)
22/48


# Page. 24

![Page Image](https://bcdn.docswell.com/page/LELMMP4P7R.jpg)

Is the Wasm Binary Size a Problem?
For BtoC browser applications, download size
is important
For BtoB, download size may not be a
significant issue
Stable network environment
Effective browser caching
Is this a critical reason?
PicoRuby.Wasm may be a solution for reducing the binary size.
23/48


# Page. 25

![Page Image](https://bcdn.docswell.com/page/4JMYY4K2JW.jpg)

JavaScript-to-Ruby Support Is Weak
We may need to take the reverse approach.
Instead of calling JavaScript from Ruby, we
should enable JavaScript to call Ruby.
24/48


# Page. 26

![Page Image](https://bcdn.docswell.com/page/PJR99WX579.jpg)

Ruby-to-JavaScript vs JavaScript-to-Ruby
Feature
eval
Method Call
Property Access
Object Conversion
Load Ruby Scripts
R→J
Yes
Yes
Yes
Yes
browser
J→R
Yes
call
N/A
string
No
25/48


# Page. 27

![Page Image](https://bcdn.docswell.com/page/PEXQQP6XJX.jpg)

Currently, JavaScript calls Ruby using eval
const script = `
class Dog
def name()= &#039;john&#039;
end
Dog.new
`
const dog = vm.eval(script);
dog.call(&quot;name&quot;).toString(); // =&gt; &quot;John&quot;
26/48


# Page. 28

![Page Image](https://bcdn.docswell.com/page/3EK99D19ED.jpg)

Currently, JavaScript calls Ruby using eval
Currently, Ruby can be executed from
JavaScript using eval.
However, there is no proper API for working
with the return values on the JavaScript side.
Could this be one of the reasons it has not been
adopted in production?
27/48


# Page. 29

![Page Image](https://bcdn.docswell.com/page/L73WWRQ975.jpg)

2: What if JavaScript-to-Ruby was possible?
28/48


# Page. 30

![Page Image](https://bcdn.docswell.com/page/87DKKY98JG.jpg)

What if JavaScript-to-Ruby was possible?
For example:
You could use a format converter written in
Ruby in the browser
You could run Rails’ money calculation logic
in the browser
29/48


# Page. 31

![Page Image](https://bcdn.docswell.com/page/VJPKK6ZWE8.jpg)

Using a format converter written in Ruby in
the browser
Convert text annotations between JSON and
plain-text formats
Text format is more LLM-friendly than JSON
Wrote the converter in Ruby
30/48


# Page. 32

![Page Image](https://bcdn.docswell.com/page/2EVVV3W9EQ.jpg)

Using a format converter written in Ruby in
the browser
Called it via the Rails API
This introduced a frontend server
dependency
Rewrote it in JavaScript
31/48


# Page. 33

![Page Image](https://bcdn.docswell.com/page/57GLL3D4EL.jpg)

Maintaining two separate libraries is tough
https://rubygems.org/gems/
simple_inline_text_annotation
https://www.npmjs.com/package/@pubann/
simple-inline-text-annotation
32/48


# Page. 34

![Page Image](https://bcdn.docswell.com/page/4EQYY51RJP.jpg)

If you could call Ruby libraries from
JavaScript using ruby.wasm
In reality, the Wasm binary would increase
the size of the distributed assets, so it is
unclear whether this would be worth doing
You could create a PoC before porting the
library without creating a Rails API
33/48


# Page. 35

![Page Image](https://bcdn.docswell.com/page/KJ4WW9RQ71.jpg)

Running Rails’ money calculation logic in
the browser
You could run Rails’ money calculation logic
in the browser for previews
Port it to JavaScript?
34/48


# Page. 36

![Page Image](https://bcdn.docswell.com/page/LE1YYKQL7G.jpg)

Supproting Edge Cases is Difficult
JavaScript does not have a built-in decimal
type
Use a Decimal library
Even if you verify compatibility with
automated tests, can you cover edge case
behavior?
For BtoB applications, this might still be
practical
35/48


# Page. 37

![Page Image](https://bcdn.docswell.com/page/GEWGGDMQJ2.jpg)

3: Project Ruby’s Blanket
36/48


# Page. 38

![Page Image](https://bcdn.docswell.com/page/47ZLLGQ3J3.jpg)

Implementing an interface to call Ruby from
JavaScript
The features for calling JavaScript from Ruby
are implemented mainly in the JS::Object
class in ruby.wasm.
Method call
Constructor
Property access
Object conversion
37/48


# Page. 39

![Page Image](https://bcdn.docswell.com/page/YJ6WWY6ZJV.jpg)

Implove RbValue
To implement an interface for calling Ruby from
JavaScript, we need to expand RbValue that
wraps Ruby objects.
I call this project as Ruby’s Blanket.
38/48


# Page. 40

![Page Image](https://bcdn.docswell.com/page/GJ5MMG69J4.jpg)

Ruby’s Blanket
Ruby’s Blanket is named after a song by GLAY,
a famous band from Hakodate, Japan.
39/48


# Page. 41

![Page Image](https://bcdn.docswell.com/page/LE3WWRQ2E5.jpg)

Roadmap
1. Load Ruby scripts in Node.js
2. Method call
3. Convert JavaScript values to RbValue
(arguments)
4. Convert RbValue to JavaScript values (return
values)
40/48


# Page. 42

![Page Image](https://bcdn.docswell.com/page/8EDKKY967G.jpg)

Imagine
vm.evalFile(&quot;dog.rb&quot;);
const dog = vm.eval(&quot;Dog.new&quot;);
dog.vow();
dog.vow();
const count = dog.count_of_vows.toInt();
How reference constants? Maybe window.ruby.Dog.new() ?
41/48


# Page. 43

![Page Image](https://bcdn.docswell.com/page/V7PKK69ZJ8.jpg)

4: Practice
42/48


# Page. 44

![Page Image](https://bcdn.docswell.com/page/2JVVV3MMJQ.jpg)

Before that, there were things to do
Support for Ruby 4.0 (explicitly install rdoc)
Upgrade magnus
Follow Prism fixes in ruby head
Unskip npm run test
43/48


# Page. 45

![Page Image](https://bcdn.docswell.com/page/5EGLL3GXJL.jpg)

Demo
vm.eval(`
require &quot;js/require_local/relative_shim&quot;
JS::RequireLocal.instance.base_dir = &quot;require_local&quot;
require_relative &quot;./dog&quot;
`);
const dog = vm.eval(&quot;Dog.new&quot;);
console.log(dog.call(&quot;vow&quot;).toString()); // woof
console.log(dog.call(&quot;vow&quot;).toString()); // woof
console.log(dog.call(&quot;count_of_vows&quot;).toString()); // 2
44/48


# Page. 46

![Page Image](https://bcdn.docswell.com/page/4JQYY5X57P.jpg)

Unrecognized Change of Coding Agent
main().catch((error) =&gt; {
if (String(error) === &quot;Symbol(kExitCode)&quot;) {
return;
}
throw error;
});
There is one more option that needs to be
specified in Node.js
45/48


# Page. 47

![Page Image](https://bcdn.docswell.com/page/K74WW96VE1.jpg)

Thanks to the Coding Agent
Thanks to the coding agent, we were able to
dig deeper into debugging than before.
As we dug deeper, it became harder to decide
what to fix first.
But by working through those difficult parts
instead of skipping them, future development
will become easier.
46/48


# Page. 48

![Page Image](https://bcdn.docswell.com/page/LJ1YYKV4EG.jpg)

Conclusion
I’ve only just started working on this, but I’ll be
setting things up so you can call Ruby from
JavaScript, so please stay tuned!
47/48


# Page. 49

![Page Image](https://bcdn.docswell.com/page/GJWGGDLZ72.jpg)

bye!
Let&#039;s hang out at ko.rb
48/48


