Skip to content

hmsk/quickjs.rb

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

217 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

quickjs.rb

A Ruby wrapper for QuickJS to run JavaScript codes via Ruby with a smaller footprint.

Gem Version GitHub Actions Workflow Status

Installation

gem install quickjs
gem 'quickjs'

Usage

Quickjs.eval_code: Evaluate JavaScript code instantly

require 'quickjs'

Quickjs.eval_code('const fn = (n, pow) => n ** pow; fn(2,8);') # => 256
Quickjs.eval_code('const fn = (name) => `Hi, ${name}!`; fn("Itadori");') # => "Hi, Itadori!"
Quickjs.eval_code("[1,2,3]") #=> [1, 2, 3]
Quickjs.eval_code("({ a: '1', b: 1 })") #=> { 'a' => '1', 'b' => 1 }
Options

Resources

Quickjs.eval_code(code,
  memory_limit: 1024 ** 3,   # 1GB memory limit
  max_stack_size: 1024 ** 2, # 1MB max stack size
)

Timeout

# eval_code will be interrupted after 1 sec (default: 100 msec)
Quickjs.eval_code(code, timeout_msec: 1_000)

Features

Quickjs.eval_code(code, features: [::Quickjs::MODULE_STD, ::Quickjs::POLYFILL_FILE])
Constant Description
MODULE_STD QuickJS std module
MODULE_OS QuickJS os module
FEATURE_TIMEOUT setTimeout / setInterval managed by CRuby
POLYFILL_INTL Intl API (DateTimeFormat, NumberFormat, PluralRules, Locale)
POLYFILL_FILE W3C File API (Blob and File)
POLYFILL_ENCODING Encoding API (TextEncoder and TextDecoder)

Quickjs::VM: Maintain a consistent VM/runtime

Accepts the same options as Quickjs.eval_code.

vm = Quickjs::VM.new
vm.eval_code('const a = { b: "c" };')
vm.eval_code('a.b;') #=> "c"
vm.eval_code('a.b = "d";')
vm.eval_code('a.b;') #=> "d"

Quickjs::VM#import: πŸ”Œ Import ESM from a source code

vm = Quickjs::VM.new

# Equivalent to `import { default: aliasedDefault, member: member } from './exports.esm.js';`
vm.import({ default: 'aliasedDefault', member: 'member' }, from: File.read('exports.esm.js'))

vm.eval_code("aliasedDefault()") #=> Exported `default` of the ESM is called
vm.eval_code("member()") #=> Exported `member` of the ESM is called

# import { member, defaultMember } from './exports.esm.js';
vm.import(['member', 'defaultMember'], from: File.read('exports.esm.js'))

# import DefaultExport from './exports.esm.js';
vm.import('DefaultExport', from: File.read('exports.esm.js'))

# import * as all from './exports.esm.js';
vm.import('* as all', from: File.read('exports.esm.js'))

Quickjs::VM#define_function: πŸ’Ž Define a global function for JS by Ruby

vm = Quickjs::VM.new
vm.define_function("greetingTo") do |arg1|
  ['Hello!', arg1].join(' ')
end

vm.eval_code("greetingTo('Rick')") #=> 'Hello! Rick'

A Ruby exception raised inside the block is catchable in JS as an Error, and propagates back to Ruby as the original exception type if uncaught in JS.

vm.define_function("fail") { raise IOError, "something went wrong" }

vm.eval_code('try { fail() } catch (e) { e.message }') #=> "something went wrong"
vm.eval_code("fail()") #=> raise IOError transparently

With POLYFILL_FILE enabled, a Ruby ::File returned from the block becomes a JS File-compatible proxy. Passing it back to Ruby from JS returns the original ::File object.

vm = Quickjs::VM.new(features: [::Quickjs::POLYFILL_FILE])
vm.define_function(:get_file) { File.open('report.pdf') }

vm.eval_code("get_file().name")          #=> "report.pdf"
vm.eval_code("get_file().size")          #=> Integer (byte size)
vm.eval_code("await get_file().text()") #=> file content as String

Quickjs::VM#on_log: πŸ“‘ Handle console logs in real time

Register a block to be called for each console.(log|info|debug|warn|error) call.

vm = Quickjs::VM.new
vm.on_log { |log| puts "#{log.severity}: #{log.to_s}" }

vm.eval_code('console.log("hello", 42)')
# => prints: info: hello 42

# log.severity #=> :info / :verbose / :warning / :error
# log.to_s     #=> space-joined string of all arguments
# log.raw      #=> Array of raw Ruby values

Value Conversion

JavaScript Ruby Note
number (integer / float) ↔ Integer / Float
string ↔ String
true / false ↔ true / false
null ↔ nil
Array ↔ Array via JSON
Object ↔ Hash via JSON
undefined β†’ Quickjs::Value::UNDEFINED
NaN β†’ Quickjs::Value::NAN
Blob β†’ Quickjs::Blob β€” .size, .type, .content requires POLYFILL_FILE
File β†’ Quickjs::File β€” .name, .last_modified + Blob attrs requires POLYFILL_FILE
File proxy ← ::File requires POLYFILL_FILE; applies to define_function return values

License

Otherwise, the MIT License, Copyright 2024 by Kengo Hamasaki.