Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Running JavaScript within Ruby

Running JavaScript within Ruby

This talk explores the journey of building quickjs.rb, a native Ruby extension for running JavaScript using QuickJS. The project began with the need to execute JavaScript within Ruby in a SaaS and involved diving into native extension development as a newcomer.

Presented at RubyKaigi 2025
https://rubykaigi.org/2025/presentations/hmsk.html

Kengo Hamasaki

April 22, 2025
Tweet

More Decks by Kengo Hamasaki

Other Decks in Technology

Transcript

  1. .self @hmsk / Kengo Hamasaki Product Engineer at Persona withpersona.com

    RubyKaigi 2010-2019 Organizer/Sta ff /Volunteer Home-brewer Origin from Ehime
  2. 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
  3. “Custom Code” Code on No-code Temporal glue for products’ gap

    Arbitrary JS code by customers “Sandboxing” Run on Function as a Service
  4. 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
  5. 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
  6. ‘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
  7. 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
  8. 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
  9. 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!
  10. Sandboxing Disable all features touching OS resources directly Optionally allow

    it Provide a replacement Control timeout and stack size
  11. 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-*
  12. 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!?)
  13. “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
  14. 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'))
  15. 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⏎
  16. 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 😇
  17. “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
  18. 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