Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Ruby cannot execute JavaScript with Kernel#eval: that method parses Ruby code. To load JavaScript held in a Ruby string, pass the string to a JavaScript runtime. The two documented Ruby approaches are ExecJS, which provides a common interface over installed runtimes, and MiniRacer, which embeds V8 in a Ruby process.
Use ExecJS.eval for a one-off expression, ExecJS.compile for reusable functions, or a persistent MiniRacer::Context when you specifically want embedded V8.
What “load JavaScript from a string” means in Ruby
A Ruby string is only text until a JavaScript engine parses it. Ruby itself has no JavaScript parser, so this does not work:
js = "1 + 2"
eval(js) # evaluates Ruby syntax, not JavaScript
Ruby’s Kernel#eval documentation describes evaluation of Ruby expressions, optionally in a Ruby Binding. It is not a JavaScript loader.
#1 Best Overall
Choose a runtime first, then give that runtime your source string. The runtime may be a separately installed executable (through ExecJS) or an engine embedded in the Ruby process (MiniRacer).
Option 1: evaluate a one-off string with ExecJS
Install and require the gem
Add ExecJS to your bundle:
gem "execjs"
Then run bundle install and require it:
require "execjs"
Evaluate an expression
ExecJS.eval is the shortest path for a string containing one expression:
require "execjs"
result = ExecJS.eval("'red yellow blue'.split(' ')")
p result
# => ["red", "yellow", "blue"]
The returned value is converted to a Ruby value when ExecJS can represent it. Keep the JavaScript source in a variable when it comes from a file, database, or another application component:
javascript = <<~JS
({ name: "Ada", roles: ["admin", "author"] })
JS
value = ExecJS.eval(javascript)
p value
# => {"name"=>"Ada", "roles"=>["admin", "author"]}
For an expression that is an object literal, wrapping it in parentheses avoids parsing it as a statement block.
Reuse functions with an ExecJS context
When the string defines functions or contains a library that you will call repeatedly, compile it once:
Rank #2
require "execjs"
source = <<~JS
function add(a, b) {
return a + b;
}
JS
context = ExecJS.compile(source)
result = context.call("add", 20, 22)
puts result
# => 42
compile creates a context containing the supplied source; call invokes a named JavaScript function with Ruby arguments. This avoids reparsing the same definitions for every call.
Load a JavaScript file into the string
require "execjs"
source = File.read("lib/calculator.js", encoding: "UTF-8")
context = ExecJS.compile(source)
puts context.call("add", 7, 5)
Your file must define the function in the context’s top-level scope, for example function add(a, b) { return a + b; }. Module systems such as Node’s require and browser globals are not supplied automatically by the common ExecJS interface.
Which JavaScript runtime does ExecJS use?
ExecJS presents a common API and automatically selects an available runtime. Its README lists Node.js, Bun, JavaScriptCore, Windows Script Host/JScript, Duktape, Rhino, V8/MiniRacer, and GraalVM JavaScript as supported possibilities; the actual choice depends on your environment. See the ExecJS README for the current list and setup details.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchInspect or select a runtime
To see the selected runtime:
require "execjs"
puts ExecJS.runtime.name
You can assign a runtime in Ruby:
ExecJS.runtime = ExecJS::ExternalRuntime.new(
name: "Node.js",
command: "node",
runner_path: ExecJS.root.join("support", "node_runner.js")
)
In practice, prefer a runtime adapter already provided by your ExecJS version and deployment. You can also request a runtime with the EXECJS_RUNTIME environment variable. Verify the exact runtime name and executable on the target machine rather than assuming that a runtime available on a laptop exists in production.
ExecJS limits you must design around
Lowest-common-denominator JavaScript
ExecJS intentionally abstracts several engines. Runtime behavior and supported features therefore vary. Its documentation recommends relying on ES3-level features unless you have checked the selected runtime and its APIs. Modern syntax or host APIs may work with one runtime and fail with another.
Rank #3
No general event loop
ExecJS does not guarantee a full JavaScript event loop. Timers such as setTimeout and setInterval are not defined through the interface. Code that expects browser APIs, DOM objects, timers, network access, or Node-specific modules needs a different integration approach.
Not a security sandbox
Do not treat automatic runtime selection as a safe sandbox for arbitrary input. ExecJS warns that runtimes have different sandbox properties and advises against evaluating input that you would not be comfortable passing to Ruby’s eval. Only execute trusted, application-controlled JavaScript, or isolate untrusted code with a separately designed security boundary.
Option 2: load the string in MiniRacer’s embedded V8
Create a context and evaluate source
MiniRacer embeds V8 and exposes a persistent context:
require "mini_racer"
context = MiniRacer::Context.new
context.eval("var adder = (a, b) => a + b;")
result = context.eval("adder(20, 22)")
puts result
# => 42
The first evaluation defines JavaScript state; later evaluations reuse that same context. This is useful when you need V8-specific behavior or several calls against initialized code.
Evaluate a Ruby string and call a function
require "mini_racer"
source = <<~JS
function slugify(value) {
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-");
}
JS
context = MiniRacer::Context.new
a = context.eval(source)
slug = context.call("slugify", "Ruby & JavaScript")
puts slug
# => ruby-javascript
MiniRacer’s documentation also describes context timeouts, a memory soft limit, filename information for stack traces, and attaching Ruby functions to JavaScript. Consult its current documentation for the supported Ruby and platform matrix before selecting a version.
Rank #4
ExecJS or MiniRacer?
| Requirement | ExecJS | MiniRacer |
|---|---|---|
| Runtime choice | Common interface; selects or can be configured to use an available runtime | Embedded V8 context |
| One expression | ExecJS.eval |
context.eval |
| Reusable code | ExecJS.compile and context.call |
Keep one MiniRacer::Context and evaluate or call repeatedly |
| Portability | Depends on the runtime installed and its feature set | Depends on MiniRacer’s supported Ruby/platform and V8 packaging |
| Resource controls | No universal event-loop or sandbox guarantee | Documented timeout and memory soft-limit controls |
Choose ExecJS when portability across engines and a uniform API matter. Choose MiniRacer when your application specifically wants V8 embedded in the Ruby process and its documented controls. In either case, test the exact Ruby version, operating system, JavaScript syntax, and deployment image you will ship.
Recommended Free Tools
Common errors and fixes
ExecJS::RuntimeUnavailable
Cause: ExecJS cannot find a usable JavaScript runtime. Fix: install a supported runtime such as Node.js in the deployment image, confirm its executable is on PATH, then print ExecJS.runtime.name. If several runtimes are present, configure the intended one explicitly.
Syntax errors after switching environments
Cause: the selected engine does not support syntax or APIs used by your source. Fix: check the selected runtime, reduce the code to the supported feature set, or use MiniRacer when embedded V8 is a better fit.
“setTimeout is not defined”
Cause: ExecJS does not promise a browser-style event loop. Fix: rewrite the code as synchronous functions, or run it in an environment that actually provides the required event loop.
Function is not found
Cause: the function was defined inside a local wrapper, module export, or different context. Fix: define it at the context’s accessible top level and call the exact name; for MiniRacer, keep and reuse the same context.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteBest Value
Unexpected Ruby evaluation
Cause: calling Ruby eval instead of a JavaScript runtime. Fix: replace it with ExecJS.eval, ExecJS.compile, or MiniRacer::Context#eval.
Untrusted code execution
Cause: JavaScript engines and adapters do not automatically create a security boundary. Fix: do not pass user-supplied source to these APIs. If untrusted execution is unavoidable, design a separately isolated service with a security model appropriate to your threat.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Operational guidance
- Compile or initialize reusable source once per worker or request scope that safely owns the context; avoid reparsing large libraries unnecessarily.
- Pin gem and runtime versions in deployment, and run tests against the same image used in production.
- Return plain data (strings, numbers, arrays, and objects) across the Ruby/JavaScript boundary to minimize conversion surprises.
- Log the selected runtime and JavaScript exceptions, but avoid logging secrets embedded in source or arguments.
- Set MiniRacer’s documented timeout and memory controls where long-running or resource-heavy scripts could affect a worker.
Or skip the browser setup
If your goal is to obtain a rendered website image rather than execute arbitrary JavaScript inside Ruby, ScreenshotNeo makes one HTTP request and returns a PNG, JPEG, WebP, or PDF. It accepts cookie/consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status.
For a direct call, see the ScreenshotNeo API documentation:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same endpoint can be called from Ruby:
require "net/http"
require "uri"
uri = URI("https://api.screenshotneo.com/v1/shot")
uri.query = URI.encode_www_form(access_key: "YOUR_API_KEY", url: "https://stripe.com")
File.binwrite("shot.webp", Net::HTTP.get(uri))
It also has an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. One thousand screenshots per month are free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Frequently Asked Questions
Can I execute JavaScript without installing Node.js?
Yes, if you use MiniRacer’s embedded V8 and your platform is supported. ExecJS, however, needs an available runtime adapter, which may be Node.js, Bun, JavaScriptCore, or another documented engine.
How do I pass Ruby values to JavaScript?
Pass values as arguments to ExecJS context.call or MiniRacer context.call; use JSON-compatible data types to keep conversion predictable.
Does this load browser DOM code?
No. These APIs evaluate JavaScript engines, not a browser page. DOM, browser events, timers, and network APIs are unavailable unless the chosen host explicitly provides them.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

