Slide 1

Slide 1 text

Running JavaScript within Ruby April 17, 2025 @hmsk

Slide 2

Slide 2 text

.self @hmsk / Kengo Hamasaki Product Engineer at Persona withpersona.com RubyKaigi 2010-2019 Organizer/Sta ff /Volunteer Home-brewer Origin from Ehime

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

gem 'quickjs' github:hmsk/quickjs.rb

Slide 5

Slide 5 text

irb⏎

Slide 6

Slide 6 text

quickjs https://bellard.org/quickjs/ “A small and embeddable JavaScript engine” By Fabrice Bellard Support ES2023 (Almost) C, standalone, light

Slide 7

Slide 7 text

Just wrapped it…? Why I need to run JS in Ruby? What does building a native ext today look like today? How might we run JS in Ruby? Real world outcome

Slide 8

Slide 8 text

raise

Slide 9

Slide 9 text

withpersona.com Identity veri fi cation SaaS platform Monolithic Rails We’re hiring Rubyists :)

Slide 10

Slide 10 text

Inquiry Veri fi cation Case Graph

Slide 11

Slide 11 text

Work fl ows

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

“Custom Code” Code on No-code Temporal glue for products’ gap Arbitrary JS code by customers “Sandboxing” Run on Function as a Service

Slide 14

Slide 14 text

Problem with FaaS Add points of failure and latency Host, network Maintenance cost Node, npm for non-Frontend purpose in Ruby company Implement “XYZ” again for the env…? e.g.) handling outgoing HTTP requests

Slide 15

Slide 15 text

Run within Ruby/Rails? Prior arts mini_racer (v8), Node Duktape (ES5) quickjs looked minimal and modern C, standalone, very light, Support ES2023 (Almost) Some adoptions in the community

Slide 16

Slide 16 text

‘kay, then build a native ext Somewhat I knew about C For micro-computers at Kosen in 2005 Have built one; github:hmsk/lzfx From a workshop by @sgwr_dts at Cookpad in 2010 JUST LINK AND CALL Started from a personal project in June 2024

Slide 17

Slide 17 text

rescue

Slide 18

Slide 18 text

New to native exts in 2024 Great modern resources Articles, implementations by other languages Incremental C-ing With powerful Ruby features Found the bless by CRuby authors 💖 AI helped sometimes

Slide 19

Slide 19 text

Great modern resources “A Rubyist's Walk Along the C-side” https://blog.peterzhu.ca/ruby-c-ext/ By Peter Zhu; Ruby committer, Speaker at Day 3 ✨ Learn from quickjs bindings for other languages quickjs-go, rquickjs, quickjs-rust, and quickjs-python

Slide 20

Slide 20 text

Incremental C-ing 1. Write what I need by Ruby 2. Just call it from C VALUE class = rb_const_get(rb_cClass, rb_intern("class name”)); rb_funcall(class, rb_intern(“method name”), 0); 3. Write tests in Ruby for it 4. Convert Ruby impl to C gradually 5. REPEAT!

Slide 21

Slide 21 text

e.backtrace

Slide 22

Slide 22 text

Not only just wrapping… Sandboxing Poly fi lls Interfacing for Ruby

Slide 23

Slide 23 text

Sandboxing Disable all features touching OS resources directly Optionally allow it Provide a replacement Control timeout and stack size

Slide 24

Slide 24 text

Poly fi lls “Intl” is not fully supported by quickjs new Date().toLocaleString("en-US", { month: "short", day: "2-digit", year: "numeric", }); Expected: "Apr 17, 2025" Quickjs: "04/17/2025, 11:25:00 AM" Provide poly fi lls by @formatjs/intl-*

Slide 25

Slide 25 text

Poly fi lls Evaluate huge JS every time…?? Compile the JS to C, then compile within quickjs.rb’s build quickjs supports compiling JS to C or binary (WHAT!?)

Slide 26

Slide 26 text

“How we wanna run JavaScript from Ruby…?” Import ESM from Ruby String Persisting logs from console.log…etc Call Ruby from JS Interfacing for Ruby

Slide 27

Slide 27 text

import ESM Import from String of Ruby Import like how JavaScript does # JS: import aliasedDefault, { member } from './exports.esm.js'; vm.import({ default: 'aliasedDefault', member: 'member' }, from: File.read('exports.esm.js')) # JS: import { member, defaultMember } from './exports.esm.js'; vm.import(['member', 'defaultMember'], from: File.read('exports.esm.js')) # JS: import DefaultExport from './exports.esm.js'; vm.import('DefaultExport', from: File.read('exports.esm.js')) # JS: import * as all from './exports.esm.js'; vm.import('* as all', from: File.read('exports.esm.js'))

Slide 28

Slide 28 text

console.log Record logs from console.(log|info|warn|error) irb⏎

Slide 29

Slide 29 text

de fi ne_methodfunction Ruby evaluates JavaScript which calls Ruby e.g.) global.fetch (in JS) -> net/http (in Ruby) Translate exceptions transparently raise in Ruby-> catch is JS raise in Ruby -> (no catch in JS) -> raise in Ruby irb⏎

Slide 30

Slide 30 text

ensure

Slide 31

Slide 31 text

Use in real world Now processing over 400M/day, 2.5k/min (in peek) Save the cost of FaaS Obvious improvement in performance Found some problems per extending use-cases 😇

Slide 32

Slide 32 text

“Single source” solution A coworker adopted for a di ff erent purpose while I was still experimenting 🤣 A parser for DSL for both the backend and frontend Massive tra ffi c; 200+M/day, 50k/min

Slide 33

Slide 33 text

end github:hmsk/quickjs.rb Building native exts for CRuby is fun Thanks, RubyKaigi! withpersona.com is hiring I have some swag for the token of my referral