Summarized using AI

Running JavaScript within Ruby

Kengo Hamasaki • April 17, 2025 • Matsuyama, Ehime, Japan • Talk

In the talk "Running JavaScript within Ruby" by Kengo Hamasaki at RubyKaigi 2025, the speaker presents his project called QuickJS RB, which is a Ruby gem that allows running JavaScript code within Ruby applications. The main highlights of the presentation include:

  • Introduction and Context: Kengo expresses his excitement about attending RubyKaigi for the first time as a speaker, sharing a bit about his background, including his role as a product engineer at Persona, an identity verification startup. He mentioned his experience in organizing RubyKaigi and his passion for brewing beer.

  • Project Overview: QuickJS RB is designed to enable developers to execute JavaScript within Ruby applications. Kengo demonstrates its functionality, showing how JavaScript code can be evaluated and run within a Ruby environment.

  • Technical Implementation: The talk dives into technical details, explaining how QuickJS is a lightweight JavaScript engine created by a well-known programmer. The speaker emphasizes the focus on integrating QuickJS with Ruby through native extensions, which he found enjoyable and fulfilling. He explains the challenges and processes of building the binding to allow Ruby to interface effectively with QuickJS.

  • Use Cases and Benefits: Kengo discusses the practical applications of QuickJS RB within Persona’s platform, highlighting features such as a no-code tool that allows users to run custom JavaScript code as part of their workflows for identity verification. This capability allows for flexibility and enhancement of features beyond what standard products can offer.

  • Security Considerations: Kengo addresses the importance of sandboxing JavaScript execution for security purposes and how he integrated this feature while maintaining access to essential JavaScript functionalities.

  • Real-World Application and Performance: Since the integration of QuickJS RB, Kengo reports a significant increase in efficiency, processing over 4 million invocations per day and reducing reliance on external function as a service products. The team has also seen unexpected use cases, demonstrating versatility in both backend and frontend applications.

  • Conclusion and Call to Action: Kengo concludes by sharing the GitHub repository for QuickJS RB and expressing his enjoyment in building native extensions with Ruby. He encourages others to explore similar paths in software development and mentions job opportunities at Persona for Ruby developers.

Overall, this presentation provided an insightful look into running JavaScript within Ruby, showcasing practical implementations and considerations for developers.

Running JavaScript within Ruby
Kengo Hamasaki • Matsuyama, Ehime, Japan • Talk

Date: April 17, 2025
Published: May 27, 2025
Announced: unknown

RubyKaigi 2025

00:00:05.720 morning all
00:00:08.200 right let's get
00:00:10.360 start
00:00:12.120 so I so yeah thanks for joining my
00:00:16.440 session i hope everyone had a yeah great
00:00:19.840 day one yesterday and also the official
00:00:23.640 party for me the yeah it's first time to
00:00:27.039 attend Ruby Kai after six years so I'm
00:00:30.480 so excited for being here thanks for
00:00:33.680 having me Lubiki but yeah last night
00:00:37.440 yeah I enjoy too so this picture was
00:00:40.719 taken at about 3:00 actually
00:00:44.680 today fortunately the Yeah I'm not so
00:00:47.440 hung over thanks to my nervousness
00:00:50.840 now this is so funny so now I'm gonna
00:00:54.800 talk about the running JavaScript within
00:00:57.440 Ruby which is implemented by C rank in
00:01:00.879 pearl room so yeah for my favorite
00:01:05.600 langages are gathered in the yeah same
00:01:08.760 place so hi again uh I'm Ko or at HMSK
00:01:15.119 i'm working as a product engineer for
00:01:18.439 persona which is an identity
00:01:21.040 verification startup so I used to be in
00:01:24.400 Ruby Kai organizers for about 10 years
00:01:27.360 but this is my first time to attend Ruby
00:01:30.159 Kai as a speaker and uh I'm a home
00:01:33.600 brewer so yeah but not about the package
00:01:36.640 manager so yeah this is real
00:01:40.040 brewing so yeah these years I brewed
00:01:42.960 over 100 gallon so about 380 liters so
00:01:47.840 yeah I guess I'm kind of yeah beer noob
00:01:50.960 and uh yeah please give me
00:01:52.720 recommendations for breweries around
00:01:55.040 Matsyama i hope I can visit there to
00:01:57.920 celebrate with nice beer if my this
00:02:00.880 presentation goes
00:02:03.399 well although my hometown is actually
00:02:06.560 Foka but my dad came from Eime
00:02:09.599 Prefecture where Masyama is so I'm
00:02:13.360 feeling something special for this
00:02:15.120 year's Ruby Kai that is one of my yeah
00:02:18.160 some reasons I submitted my proposal for
00:02:21.840 yeah this this
00:02:23.800 kai but my dad does hometown is Kobuchi
00:02:28.400 that where is a bit far away that yeah
00:02:31.120 even from Matsyama it takes 10 hours
00:02:33.840 from Tokyo so yeah that is longer than
00:02:36.879 my flight for San Francisco where I live
00:02:39.360 in now so I never had a chance to visit
00:02:42.640 yeah over 10 years so thanks to Ruby Kai
00:02:46.080 I'm going to visit yeah this
00:02:49.800 weekend anyways so let's talk about
00:02:53.200 technical stuff so this session is just
00:02:56.239 about my project quickJS RB so it's
00:02:59.840 published as a Ruby gem already and the
00:03:02.159 source code is on
00:03:04.200 GitHub so let me show a quick demo about
00:03:07.280 the yeah this gem at first
00:03:13.159 so you can require the this gen and you
00:03:18.800 can have the quick js cross
00:03:23.239 oh oh
00:03:31.799 so yeah I I just require require for the
00:03:35.840 first and
00:03:38.360 uh this
00:03:42.519 uh this method can evaluate the JS code
00:03:47.920 but it looks like not JS
00:03:51.480 actually so let's try the yeah some
00:03:54.760 JSish code so you can define the
00:03:58.920 const a and uh if you evaluate a you can
00:04:04.000 get yeah one or the yeah let's see some
00:04:07.959 other yeah javascript specific for
00:04:11.840 example mass prototype
00:04:14.480 uh maybe p
00:04:17.160 to this should be 256 maybe yes it's
00:04:22.320 running and also the
00:04:25.000 quickjs is having the yeah VM class
00:04:29.280 which is kind of the runtime instance so
00:04:32.080 you can instantiate
00:04:34.919 it and this keeps the yeah maintains the
00:04:39.040 yeah consistent runtime so if you do e
00:04:42.880 code for
00:04:44.440 example so let's define some
00:04:49.320 function a b a + v
00:04:57.800 And if you do another
00:05:03.639 code so let's call
00:05:07.080 funk one and
00:05:10.120 two
00:05:12.199 oh so yeah it's returning three such
00:05:15.280 like this
00:05:20.919 and so this gem could run JavaScript
00:05:24.240 code on Ruby but uh yeah this is just
00:05:26.639 wrapping an existing open-source project
00:05:29.919 uh which is named quickjs that is made
00:05:32.960 by a legendary programmer the fvis bar
00:05:37.039 who made very famous projects like
00:05:39.360 ffmpeg or qmu yeah there are so more and
00:05:43.520 quickjs supports the modern javascripts
00:05:45.919 specification ECM script 2023 mostly and
00:05:50.880 it's written by C rank and the work
00:05:53.440 standard one and the binary size is
00:05:55.840 relative to very
00:05:58.280 small so what I made actually you may
00:06:01.840 think that I just wrapped such a great
00:06:04.080 project so yeah that's totally right so
00:06:07.039 my focus were mostly about the
00:06:09.280 productizing and interfacing by binding
00:06:12.479 a Ruby uh building a Ruby binding of
00:06:16.280 quickjs so this session is more about my
00:06:19.440 practice like why and how I built it for
00:06:23.120 the real world use case this my
00:06:26.319 presentation may be bored for some
00:06:28.560 people who are familiar with Ruby's
00:06:30.720 native extension so I just recommend
00:06:33.120 them leaving for other sessions and my
00:06:36.160 personal recommendation is I'll show you
00:06:38.400 some the session about about performance
00:06:41.360 as subh hall and uh yeah honestly I want
00:06:44.479 to watch that instead of presenting here
00:06:47.039 right
00:06:48.520 now all right then yeah let me dump why
00:06:52.560 I built quick
00:06:54.919 JSR so my employer uh persona is running
00:06:59.680 a s platform for identity
00:07:02.840 verification if I explain our business I
00:07:07.120 usually say the stripe.com for
00:07:09.880 identity it's learning by a monoic waves
00:07:13.280 project and also yeah we are hiring nice
00:07:15.919 rubies like people in Ruby Kai so talk
00:07:19.280 to me if you're interested
00:07:21.160 in and persona is learning multiple
00:07:24.400 products within a single platform for
00:07:27.280 example the inquiry product is to build
00:07:30.560 a customer's journey to get verified and
00:07:33.919 the verification product is for checking
00:07:36.720 specific document documentation or
00:07:39.440 biometrics like driving license or
00:07:42.639 social security number or my number for
00:07:45.520 Japan and selfie or yeah whatever and
00:07:49.680 case product is for manual check uh yeah
00:07:53.599 providing a way to correct and summarize
00:07:56.000 for making a decision about an identity
00:07:59.520 and the graph product is to build and uh
00:08:03.440 build and visualize uh networks of the
00:08:07.360 end users to find the fraud stores or
00:08:10.240 scams or something undiscovered
00:08:14.160 and my team's primary product is
00:08:16.400 workflows uh which is a no code tool to
00:08:19.440 automate all operations of platform
00:08:22.479 internally and
00:08:25.080 externally and this is a yeah sample of
00:08:27.680 workflows configuration so this workflow
00:08:30.319 is listening to the event the inquiry is
00:08:33.279 created then making a decision per end
00:08:36.640 user's input on the conditional step and
00:08:40.479 uh learning additional process per that
00:08:42.919 decision among these steps uh we are
00:08:46.399 providing an interesting feature which
00:08:48.880 is called custom
00:08:51.240 code so custom code is the tool to run
00:08:55.600 JavaScript code on no port tool so yes
00:08:59.279 code maybe and this feature generates uh
00:09:02.800 much revenue since it fills the gaps uh
00:09:06.080 which we or our competitors can't cover
00:09:08.800 customers needs by yeah native product
00:09:12.480 uh product natively so customers can
00:09:15.839 write code to make something happens
00:09:18.399 within personaliz platform so arbitrary
00:09:21.600 JavaScript code is executed and for
00:09:24.480 security purposes the we needed to care
00:09:27.360 about sandboxing it and typical
00:09:30.560 implementation of the product like
00:09:32.880 custom code for companies that who don't
00:09:35.920 use JavaScript in backend servers like
00:09:38.560 yeah we are using rails and should be
00:09:41.760 using the function as a service usually
00:09:44.240 so like Amazon lambda or Google cloud
00:09:47.080 functions as a yeah natural choice that
00:09:50.240 we also adopted function as a service as
00:09:54.040 well but then we continuously annoyed by
00:09:58.560 some issues with using function as a
00:10:00.720 service over three years in general uh
00:10:04.640 this adds another yeah points of failure
00:10:08.160 and need to manage environment of
00:10:10.560 function as a service and as network
00:10:13.120 latency and requires some additional
00:10:15.920 maintenance cost for the yeah keeping in
00:10:20.440 infrastructure so while maintaining this
00:10:23.120 system for about three years I was
00:10:25.360 wondering if we could run JavaScript
00:10:28.000 within various servers itself so there
00:10:32.079 are some prior arts like the mini racer
00:10:35.839 which calls the yeah which runs V8 and
00:10:38.760 NodeJS which is also running by V8 and
00:10:42.240 the duct tape is kind of the similar
00:10:45.360 product as quick JS but not supporting
00:10:47.440 the modern JS so actually I ended up
00:10:51.680 with using quickjs since it seems to fit
00:10:55.120 to our use
00:10:58.360 cases but I couldn't find a binding of
00:11:01.920 the quickjs for ruby in public so I
00:11:06.320 thought I should build by myself it will
00:11:10.399 be writing crank to handle quickjs that
00:11:14.560 most of co-workers didn't get this idea
00:11:17.360 because of Cang however that I knew
00:11:20.800 building native extension is not so uh
00:11:23.680 exceptional choice thanks to Ruby Kagi
00:11:26.480 and because yeah and also I had a
00:11:29.519 somewhat experience in Cang when I was
00:11:32.320 in tech courage cosen and uh also in my
00:11:35.920 new grad job I had published a simple
00:11:38.640 native binding of the compression
00:11:41.040 library which is called LZ FX so yeah I
00:11:45.279 published it as a result of team's
00:11:47.839 workshop in
00:11:49.240 cookbat so I experimentally started to
00:11:52.480 build quickjs binding as a personal
00:11:57.000 project so how could I build the quick
00:12:00.480 quick js
00:12:03.000 rv i'd like to say that building a yeah
00:12:06.720 new native function yeah even for now
00:12:09.120 that is very fun so this is a yeah a
00:12:11.760 quick summary from my journey so there
00:12:15.279 are the great modern resources and C
00:12:17.920 ruby is so powerful to use Ruby in C but
00:12:21.519 because of C Ruby so I could build C
00:12:24.800 parts incrementally and starting from
00:12:27.279 writing just Ruby through the
00:12:29.800 implementation I found so much efforts
00:12:32.880 by C Ruby authors for yeah interfacing
00:12:36.160 to handle Ruby by C also that yeah
00:12:40.000 sometimes the AI is like chat GPT helped
00:12:45.839 specifically about the resources so this
00:12:48.800 one the uh lubist walk around the
00:12:51.440 seaside is an amazing series of articles
00:12:54.720 about using Ruby in C i' I'd like to
00:12:58.720 shout out here to the author the Peter
00:13:01.360 Jour who is a Ruby comeer and the
00:13:04.000 speaker at day three
00:13:05.800 tomorrow and I found existing native
00:13:09.200 bindings for other langages as well so
00:13:12.560 they were the great references to learn
00:13:15.279 how I handle quickjs
00:13:19.399 externally for incremental seeing so
00:13:23.200 yeah I could write what I needed by just
00:13:26.079 Ruby so and yes for the first I lo the C
00:13:30.839 to call Ruby's implementation like this
00:13:34.399 code so this code is getting a class
00:13:37.040 object and calling uh yeah cross method
00:13:40.399 on it then I wrote tests by Ruby to
00:13:44.399 ensure it works and once I completed
00:13:47.720 testing started to migrate Ruby's
00:13:50.480 implementations to C and by repeating
00:13:54.240 this that most of implementations are
00:13:56.800 built by C
00:13:59.800 eventually so yeah I enjoyed building a
00:14:02.720 native extensions uh thanks to the
00:14:05.519 Ruby's friendliness
00:14:07.600 now let me show that yeah what I built
00:14:11.360 uh until today for quickjs
00:14:14.519 RV while the core quickjs does
00:14:17.760 JavaScript processing so I could focus
00:14:20.639 on the productizing and the
00:14:22.839 interfacing these three pillars were my
00:14:25.680 main focus sandboxing and the
00:14:28.000 peripherals and the interfacing for
00:14:33.079 Ruby for soundboxing so quickjs
00:14:37.519 has yeah powerful feature which may
00:14:40.560 touch OS resources directly so that kind
00:14:44.399 of Marcus code can do something
00:14:47.720 unpreent so I restricted all those all
00:14:51.440 those features as default and gave
00:14:54.240 options to enable it
00:14:56.199 instead and some essential features for
00:14:59.839 JavaScript we needed so then I provided
00:15:03.760 a replacement which is implemented
00:15:06.240 implemented by C
00:15:10.600 Ruby so for prefields the yeah the
00:15:14.399 biggest missing piece of quick JS is
00:15:17.240 internationalization API this was really
00:15:20.240 important for our custom codes use case
00:15:23.839 so for example that if you call the two
00:15:26.320 ro string of uh date prototype so quickj
00:15:31.279 just ignore us about those
00:15:33.480 formatting so I work worked on providing
00:15:36.720 the peripherals from existing open
00:15:38.800 source
00:15:40.839 projects but uh yeah even if it's
00:15:43.680 bundled and the minified the peripheral
00:15:46.240 implementation is not so small so
00:15:49.839 evaluating such the huge bundle
00:15:52.320 peripheral js is a bit cumbersome to
00:15:55.440 even for the quickjs so I needed some
00:15:59.519 creativity on it and I figured it out so
00:16:03.440 quickjs is amazingly supporting
00:16:05.759 compilation of JS code into binary or C
00:16:10.000 C so the bite code is
00:16:12.440 generated so I built a workflow to
00:16:15.600 bundle JS then compile it into C and uh
00:16:20.000 improving it into the quick JS main
00:16:23.240 build that was
00:16:26.440 insane and the main enjoyment was uh was
00:16:30.240 yeah considering about how we want to
00:16:33.040 learn JavaScript from Ruby i' I'd like
00:16:36.560 to introduce these three features which
00:16:39.199 are my favorites
00:16:42.560 so the first one is yeah importing
00:16:45.279 feature the you can import ES module by
00:16:48.160 calling the import method on the runtime
00:16:51.480 instance i try to design to show how I
00:16:55.440 write importings in JavaScript syntax so
00:16:59.120 for the first line uh you can pass a
00:17:01.839 hash to map imported members to local
00:17:06.160 areas yeah it looks like JS and the
00:17:10.160 second one is taking a n for the first
00:17:13.360 argument to list yeah imported members
00:17:17.199 with keeping the the original names and
00:17:20.400 the third one is uh taking a string
00:17:24.480 which is deemed as a default
00:17:26.760 import and the last one you can give
00:17:30.080 arbitrary string which expects the
00:17:32.480 JavaScript syntax directory
00:17:35.600 these are totally powered by Ruby's
00:17:37.840 powerful pattern matching which is
00:17:39.919 relatively new feature and this fe this
00:17:43.520 features implementation is not fully
00:17:45.840 migrated to C lang yet because of the
00:17:49.360 its intuitiveness i decided to keep as
00:17:54.200 Ruby so you can call uh another another
00:17:58.160 feature so you can call the yeah console
00:18:00.640 rogue within JavaScript yeah as same as
00:18:03.679 uh you do in NodeJS or
00:18:06.280 browsers and the recorded logs will be
00:18:09.679 persisted on the consistent runtime as
00:18:12.799 instance variable let me show
00:18:18.200 the now you're
00:18:20.600 seeing
00:18:23.240 okay so against
00:18:26.440 to instance that you can call evil code
00:18:30.960 and let's try console log here
00:18:38.280 hello and uh if you hit the logs method
00:18:44.720 uh yeah it's something yeah decoded so
00:18:48.559 and the logs
00:18:51.320 last is log and then you can take the
00:18:55.360 yeah low data instead of the uh instead
00:18:58.559 of the actual uh representation and also
00:19:02.400 you can see the ser oh it's
00:19:07.160 not so it's info and uh so the you can
00:19:12.799 see you can use another severity as well
00:19:16.480 like the one console one if you do this
00:19:20.400 yeah you can see the
00:19:26.760 warning and uh yeah this is this is my
00:19:30.400 most favorite feature the define
00:19:32.640 function feature the for runtime
00:19:34.880 instance we can define a JavaScript uh
00:19:38.720 JavaScript function which is implemented
00:19:41.039 by Ruby this is so powerful or for
00:19:44.919 example yeah we built our specific HTTP
00:19:48.720 client with our backend implementation
00:19:51.919 so which the yeah requires the some
00:19:54.880 common configurations like proxy or
00:19:57.720 routing yeah or user agent or yeah
00:20:00.400 something and so we use use the this
00:20:04.240 define function feature to provide a
00:20:06.400 custom fetch function for the custom
00:20:08.799 code uh
00:20:10.200 environment also the exceptions can be
00:20:13.600 handled
00:20:15.400 transparently so let me demo
00:20:20.039 again so let's try define
00:20:23.720 something define function so lubiki
00:20:27.960 maybe and this can't take the the
00:20:30.880 argument
00:20:32.679 like maybe here
00:20:35.159 is and okay this part is total Ruby
00:20:41.000 and be
00:20:44.120 quy and uh yeah would be integer so try
00:20:49.440 to do this and
00:20:52.360 and then in JS and give you a
00:20:56.840 code so you can call Ruby
00:21:00.039 Kai and
00:21:07.240 2025 yeah it's running the yeah Lies
00:21:11.360 method from JS and also the Yeah let's
00:21:15.120 see the exception so if you have some
00:21:20.640 custom error based on the standard error
00:21:24.440 and so let's define something
00:21:28.679 to this is interesting that yeah we can
00:21:32.000 provide a lace function in JS uh so
00:21:37.440 maybe
00:21:39.400 message and uh send my
00:21:44.600 error
00:21:46.120 new
00:21:47.799 the
00:21:51.080 message and let's call
00:21:54.840 it
00:21:56.840 so late uh
00:22:00.600 s c s c s c s c s c s c s c s c s c s c
00:22:01.600 sess
00:22:08.280 Oh
00:22:10.679 oh
00:22:18.120 oh
00:22:20.919 sness sorry
00:22:24.760 uh
00:22:27.240 why then yeah you can see the exception
00:22:30.480 is
00:22:32.520 happen and uh also yeah it's interesting
00:22:36.159 but you can catch this within
00:22:43.480 the catch uh maybe you can take copy and
00:22:48.240 the console
00:22:51.640 error and let's take
00:22:55.000 the so exception doesn't happen in lay
00:22:58.880 Rudy's end but if you see the VM logs
00:23:03.440 last yeah it's corrected so this means
00:23:07.280 that yeah this exception was caught by
00:23:11.280 uh JS
00:23:16.760 end so yeah after some incremental
00:23:20.159 implementations
00:23:22.240 uh the end of last year and now replaced
00:23:25.919 existing our custom code system which
00:23:28.240 was powered by function as a
00:23:31.799 service for now the quickjs RB is
00:23:35.360 processing over 4 million per days of
00:23:38.840 invocations so you have cut the cost of
00:23:42.320 function as a service and seeing obvious
00:23:45.280 improvements by cutting HTTP request and
00:23:48.799 the real world usage is now the giving
00:23:51.280 more problems that I have to fix day by
00:23:53.840 day but but so yeah it's nice that real
00:23:56.960 world usage helps me a lot to implement
00:24:00.840 more and this was an expected outcome uh
00:24:05.200 while I was building and testing quickJS
00:24:08.240 RB my coworker adopted my gem for a
00:24:11.440 different purpose before I release in
00:24:14.960 personaliz platform there's DSL to
00:24:18.240 represent a query for data like data dog
00:24:22.320 to compile the DSL that she built uh
00:24:25.679 puzzle by JavaScript and use use quickJS
00:24:29.679 RB to run in back end but it's JS so it
00:24:34.080 can be used in front end as well so that
00:24:37.120 means uh yeah her purpose for the both
00:24:39.760 back end and the front end was achieved
00:24:41.840 by writing just a single uh source code
00:24:45.200 by
00:24:46.039 JS and this was totally unexpected for
00:24:49.120 me originally but I think I found more
00:24:52.559 possibilities of this
00:24:56.520 gem all right so let me lap up this
00:25:01.320 presentation and so QuickJS RB repo is
00:25:05.120 on GitHub and uh yeah for me the
00:25:08.080 building native extensions was so fun so
00:25:11.360 I to I highly recommend and uh I
00:25:15.279 appreciate for this opportunity speak up
00:25:17.440 here in Ruby Kai and lastly the yeah
00:25:20.559 persona is hiring nice rubists like you
00:25:24.000 and let me know if you are interested in
00:25:26.240 I have some swag like stickers and the
00:25:28.640 key caps for the yeah token of my
00:25:31.720 referral then that's it thank you
Explore all talks recorded at RubyKaigi 2025
+66