mini_racer-csim 0.21.1.0 → 0.21.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +15 -1
- data/README.md +59 -66
- data/ext/{mini_racer_extension → mini_racer_csim_extension}/extconf.rb +4 -9
- data/ext/{mini_racer_extension/mini_racer_extension.c → mini_racer_csim_extension/mini_racer_csim_extension.c} +237 -9
- data/ext/{mini_racer_extension → mini_racer_csim_extension}/mini_racer_v8.cc +497 -107
- data/ext/{mini_racer_extension → mini_racer_csim_extension}/mini_racer_v8.h +6 -3
- data/ext/mini_racer_csim_loader/extconf.rb +8 -0
- data/ext/{mini_racer_loader/mini_racer_loader.c → mini_racer_csim_loader/mini_racer_csim_loader.c} +3 -3
- data/lib/mini_racer-csim.rb +5 -4
- data/lib/{mini_racer → mini_racer_csim}/version.rb +2 -2
- data/lib/{mini_racer.rb → mini_racer_csim.rb} +23 -23
- metadata +12 -14
- data/ext/mini_racer_loader/extconf.rb +0 -13
- data/lib/mini_racer/shared.rb +0 -395
- data/lib/mini_racer/truffleruby.rb +0 -479
- /data/ext/{mini_racer_extension → mini_racer_csim_extension}/serde.c +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 876af5088db34f57853916faa39e0568268bfcce946d44078a263de4f9e45867
|
|
4
|
+
data.tar.gz: 887bf5d80fd136be29e5f6243bc5fd3185b1020eee67002ae5c03176e75f3a85
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 365acff79cbaca1871707ea32e50d1d91dc7f15c23002e260449c1e1a553cb50c211fdb2406bc54b5bb40aa4e5848e915064891943b68dc8b4ca3ac02589a728
|
|
7
|
+
data.tar.gz: 48dec93bf2a431bedc18c6c7d5b79890d833db748b525bc5f19c22a796adcd6a0e045acf43d5ac9226a5094dbacf4bc5a48a0214dab71983fd993f930d1ce465
|
data/CHANGELOG
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
-
|
|
1
|
+
- 0.21.1.2 - 07-06-2026
|
|
2
|
+
- **Breaking:** move off the `mini_racer` require path / `MiniRacer` namespace to a fork-specific identity so the gem never collides with — and loads deterministically alongside — upstream `mini_racer` in the same bundle
|
|
3
|
+
- require path: `require 'mini_racer_csim'` (was `require 'mini_racer'`); the `require 'mini_racer-csim'` autorequire shim now points here
|
|
4
|
+
- Ruby namespace: `MiniRacerCsim` (was `MiniRacer`) — e.g. `MiniRacerCsim::Context`, `MiniRacerCsim::Snapshot`, `MiniRacerCsim::VERSION`
|
|
5
|
+
- native extensions: `mini_racer_csim_extension` / `mini_racer_csim_loader` (distinct `Init_` symbols and `.so` names)
|
|
6
|
+
- the JS-side host-namespace brand (`globalThis.MiniRacer` by default) is unchanged — it is embedder-chosen and independent of the Ruby namespace
|
|
7
|
+
|
|
8
|
+
- 0.21.1.1 - 07-06-2026
|
|
9
|
+
- Drop the TruffleRuby backend: mini_racer-csim is now a hard fork targeting CRuby/V8 only (the GraalJS/TruffleRuby shims and their tests are removed)
|
|
10
|
+
- Add per-frame realms via `Context#create_realm`, returning a `MiniRacer::Realm` (multiple V8 realms in one isolate, browser-iframe semantics) with `Realm#eval` / `call` / `attach` / `dispose` / `disposed?`
|
|
11
|
+
- all realms share one security token so they can reach each other's globals (same-origin iframe model)
|
|
12
|
+
- `__mr_realmGlobal(id)` returns a realm's live `globalThis` cross-realm; `__mr_realmOf(fn)` reports the realm a callback was created in ([[Realm]]) for WebIDL-style error attribution
|
|
13
|
+
- unhandled promise rejections are tracked and delivered per realm via `globalThis.__mr_emitUnhandledRejection(reason, promise)`; the queue is purged when a realm is disposed or reset so a stale rejection never fires against a fresh realm
|
|
14
|
+
|
|
15
|
+
- 0.21.1.0 - 06-06-2026
|
|
2
16
|
- Add `Context#perform_microtask_checkpoint` to synchronously drain the V8 microtask queue, useful for spec-compliant `dispatchEvent` sequencing inside Ruby callbacks
|
|
3
17
|
- Expose V8 ScriptCompiler::CachedData via Context#compile / MiniRacer::Script (#411)
|
|
4
18
|
- script = ctx.compile(src, filename:, cached_data:, produce_cache:) → Script handle
|
data/README.md
CHANGED
|
@@ -14,14 +14,14 @@ MiniRacer has an adapter for [execjs](https://github.com/rails/execjs) so it can
|
|
|
14
14
|
|
|
15
15
|
This is **`mini_racer-csim`**, a private fork of [`mini_racer`](https://github.com/rubyjs/mini_racer) maintained for [capybara-simulated](https://github.com/ursm/capybara-simulated). It adds browser-fidelity extensions (ES modules, realm reset, …) that capybara-simulated needs but most users do not — **if you are not using capybara-simulated, use upstream `mini_racer`.**
|
|
16
16
|
|
|
17
|
-
It
|
|
17
|
+
It has its **own require path and namespace** so it never collides with — and loads deterministically alongside — upstream `mini_racer` in the same bundle: load it with `require 'mini_racer_csim'` and use the `MiniRacerCsim` module (e.g. `MiniRacerCsim::Context`). The native extensions are `mini_racer_csim_extension` / `mini_racer_csim_loader`. (The JS-side host-namespace brand, `globalThis.MiniRacer` by default, is embedder-chosen and unrelated to the Ruby namespace.)
|
|
18
18
|
|
|
19
19
|
### Additions over upstream
|
|
20
20
|
|
|
21
21
|
| Feature | API | Notes |
|
|
22
22
|
| --- | --- | --- |
|
|
23
23
|
| Bytecode cache | `Context#compile(src, cached_data:, produce_cache:)` → `Script`, `Script#run`, `Script#cache_rejected?` | Cross-process V8 bytecode caching to skip parsing; see [Bytecode cache for repeated script evaluation](#bytecode-cache-for-repeated-script-evaluation) below |
|
|
24
|
-
| ES Module API | `Context#compile_module` → `
|
|
24
|
+
| ES Module API | `Context#compile_module` → `MiniRacerCsim::Module` (`#instantiate` / `#evaluate` / `#namespace` / `#status` / `#cached_data` / `#dispose`); `Context#dynamic_import_resolver=` | V8's ES module pipeline, `import.meta.url`, dynamic `import()` |
|
|
25
25
|
| Batched module-graph loader | `Context#load_module_graph(resolve:, …)` | Loads an ESM graph in one batched, native (C++) pass; one `Module` per URL shared across every load path |
|
|
26
26
|
| Realm reset | `Context#reset_realm` | Discards the user realm (`globalThis`) while keeping the warm isolate (browser per-navigation model); re-binds attached host functions and the host namespace |
|
|
27
27
|
| Host namespace | `Context.new(host_namespace: "MiniRacer")` → `globalThis.MiniRacer.drainMicrotasks()` | Opt-in JS namespace exposing an inline, rendezvous-free microtask checkpoint |
|
|
@@ -31,7 +31,7 @@ The fork is periodically rebased on upstream `mini_racer` to pick up V8 / `libv8
|
|
|
31
31
|
|
|
32
32
|
## Supported Ruby Versions & Troubleshooting
|
|
33
33
|
|
|
34
|
-
MiniRacer only supports non-EOL versions of Ruby. See [Ruby Maintenance Branches](https://www.ruby-lang.org/en/downloads/branches/) for the list of non-EOL Rubies. If you require support for older versions of Ruby install an older version of the gem.
|
|
34
|
+
MiniRacer only supports non-EOL versions of Ruby. See [Ruby Maintenance Branches](https://www.ruby-lang.org/en/downloads/branches/) for the list of non-EOL Rubies. If you require support for older versions of Ruby install an older version of the gem. (The `mini_racer-csim` fork is CRuby/V8 only — the TruffleRuby backend that upstream supports has been removed.)
|
|
35
35
|
|
|
36
36
|
MiniRacer **does not support**
|
|
37
37
|
|
|
@@ -55,7 +55,7 @@ If you have a problem installing MiniRacer, please consider the following steps:
|
|
|
55
55
|
You can simply eval one or many JavaScript snippets in a shared context
|
|
56
56
|
|
|
57
57
|
```ruby
|
|
58
|
-
context =
|
|
58
|
+
context = MiniRacerCsim::Context.new
|
|
59
59
|
context.eval("var adder = (a,b)=>a+b;")
|
|
60
60
|
puts context.eval("adder(20,22)")
|
|
61
61
|
# => 42
|
|
@@ -66,14 +66,14 @@ puts context.eval("adder(20,22)")
|
|
|
66
66
|
You can attach one or many ruby proc that can be accessed via JavaScript
|
|
67
67
|
|
|
68
68
|
```ruby
|
|
69
|
-
context =
|
|
69
|
+
context = MiniRacerCsim::Context.new
|
|
70
70
|
context.attach("math.adder", proc{|a,b| a+b})
|
|
71
71
|
puts context.eval("math.adder(20,22)")
|
|
72
72
|
# => 42
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
```ruby
|
|
76
|
-
context =
|
|
76
|
+
context = MiniRacerCsim::Context.new
|
|
77
77
|
context.attach("array_and_hash", proc{{a: 1, b: [1, {a: 1}]}})
|
|
78
78
|
puts context.eval("array_and_hash()")
|
|
79
79
|
# => {"a" => 1, "b" => [1, {"a" => 1}]}
|
|
@@ -81,14 +81,14 @@ puts context.eval("array_and_hash()")
|
|
|
81
81
|
|
|
82
82
|
### Return binary data from Ruby to JavaScript
|
|
83
83
|
|
|
84
|
-
Attached Ruby functions can return binary data as `Uint8Array` using `
|
|
84
|
+
Attached Ruby functions can return binary data as `Uint8Array` using `MiniRacerCsim::Binary`:
|
|
85
85
|
|
|
86
86
|
```ruby
|
|
87
87
|
require "digest"
|
|
88
88
|
|
|
89
|
-
context =
|
|
89
|
+
context = MiniRacerCsim::Context.new
|
|
90
90
|
context.attach("sha256_raw", ->(data) {
|
|
91
|
-
|
|
91
|
+
MiniRacerCsim::Binary.new(Digest::SHA256.digest(data))
|
|
92
92
|
})
|
|
93
93
|
|
|
94
94
|
# Inside JavaScript the return value is a Uint8Array
|
|
@@ -96,14 +96,14 @@ context.eval("sha256_raw('hello') instanceof Uint8Array") # => true
|
|
|
96
96
|
context.eval("sha256_raw('hello').length") # => 32
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
This is useful when you need to pass raw bytes (e.g., cryptographic digests, compressed data, binary file contents) from Ruby to JavaScript. The `
|
|
99
|
+
This is useful when you need to pass raw bytes (e.g., cryptographic digests, compressed data, binary file contents) from Ruby to JavaScript. The `MiniRacerCsim::Binary` wrapper tells the bridge to serialize the data as a `Uint8Array` on the JavaScript side rather than a string.
|
|
100
100
|
|
|
101
101
|
### GIL free JavaScript execution
|
|
102
102
|
|
|
103
103
|
The Ruby Global interpreter lock is released when scripts are executing:
|
|
104
104
|
|
|
105
105
|
```ruby
|
|
106
|
-
context =
|
|
106
|
+
context = MiniRacerCsim::Context.new
|
|
107
107
|
Thread.new do
|
|
108
108
|
sleep 1
|
|
109
109
|
context.stop
|
|
@@ -119,7 +119,7 @@ This allows you to execute multiple scripts in parallel.
|
|
|
119
119
|
Contexts can specify a default timeout for scripts
|
|
120
120
|
|
|
121
121
|
```ruby
|
|
122
|
-
context =
|
|
122
|
+
context = MiniRacerCsim::Context.new(timeout: 1000)
|
|
123
123
|
context.eval("while(true){}")
|
|
124
124
|
# => exception is raised after 1 second (1000 ms)
|
|
125
125
|
```
|
|
@@ -130,7 +130,7 @@ Contexts can specify a memory softlimit for scripts
|
|
|
130
130
|
|
|
131
131
|
```ruby
|
|
132
132
|
# terminates script if heap usage exceeds 200mb after V8 garbage collection has run
|
|
133
|
-
context =
|
|
133
|
+
context = MiniRacerCsim::Context.new(max_memory: 200_000_000)
|
|
134
134
|
context.eval("var a = new Array(10000); while(true) {a = a.concat(new Array(10000)) }")
|
|
135
135
|
# => V8OutOfMemoryError is raised
|
|
136
136
|
```
|
|
@@ -140,24 +140,24 @@ context.eval("var a = new Array(10000); while(true) {a = a.concat(new Array(1000
|
|
|
140
140
|
You can provide `filename:` to `#eval` which will be used in stack traces produced by V8:
|
|
141
141
|
|
|
142
142
|
```ruby
|
|
143
|
-
context =
|
|
143
|
+
context = MiniRacerCsim::Context.new
|
|
144
144
|
context.eval("var foo = function() {bar();}", filename: "a/foo.js")
|
|
145
145
|
context.eval("bar()", filename: "a/bar.js")
|
|
146
146
|
|
|
147
|
-
# JavaScript at a/bar.js:1:1: ReferenceError: bar is not defined (
|
|
147
|
+
# JavaScript at a/bar.js:1:1: ReferenceError: bar is not defined (MiniRacerCsim::RuntimeError)
|
|
148
148
|
# …
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
### Bytecode cache for repeated script evaluation
|
|
152
152
|
|
|
153
|
-
`Context#compile` returns a `
|
|
153
|
+
`Context#compile` returns a `MiniRacerCsim::Script` handle you can run multiple times,
|
|
154
154
|
and exposes V8's bytecode cache so subsequent Contexts can skip the parse step.
|
|
155
155
|
|
|
156
156
|
In a single process — e.g. warming a `Context` pool from one canonical compile:
|
|
157
157
|
|
|
158
158
|
```ruby
|
|
159
159
|
# Warm the cache once — top-level compile, opt in with produce_cache: true.
|
|
160
|
-
warm =
|
|
160
|
+
warm = MiniRacerCsim::Context.new
|
|
161
161
|
warmed = warm.compile(File.read("bundle.js"),
|
|
162
162
|
filename: "bundle.js",
|
|
163
163
|
produce_cache: true)
|
|
@@ -165,7 +165,7 @@ warmed.run
|
|
|
165
165
|
blob = warmed.cached_data # ASCII-8BIT String, hold onto it in memory
|
|
166
166
|
|
|
167
167
|
# Subsequent Contexts (e.g. a per-request pool) consume the blob and skip parsing.
|
|
168
|
-
ctx =
|
|
168
|
+
ctx = MiniRacerCsim::Context.new
|
|
169
169
|
script = ctx.compile(File.read("bundle.js"),
|
|
170
170
|
filename: "bundle.js",
|
|
171
171
|
cached_data: blob)
|
|
@@ -180,12 +180,12 @@ blob. Use `Snapshot#dump` / `Snapshot.load` to share canonical bytes:
|
|
|
180
180
|
|
|
181
181
|
```ruby
|
|
182
182
|
# Build the snapshot once, persist its bytes.
|
|
183
|
-
snap_bytes =
|
|
183
|
+
snap_bytes = MiniRacerCsim::Snapshot.new(snapshot_src).dump
|
|
184
184
|
File.binwrite("snapshot.bin", snap_bytes)
|
|
185
185
|
|
|
186
186
|
# Every process loads the same bytes.
|
|
187
|
-
snap =
|
|
188
|
-
ctx =
|
|
187
|
+
snap = MiniRacerCsim::Snapshot.load(File.binread("snapshot.bin"))
|
|
188
|
+
ctx = MiniRacerCsim::Context.new(snapshot: snap)
|
|
189
189
|
script = ctx.compile(File.read("bundle.js"),
|
|
190
190
|
filename: "bundle.js",
|
|
191
191
|
cached_data: File.binread("bundle.js.cache"))
|
|
@@ -198,14 +198,14 @@ callers can skip a redundant copy. When V8 produces a fresh blob (initial compil
|
|
|
198
198
|
with `produce_cache: true`, or a rejection while `produce_cache: true` was also
|
|
199
199
|
set), it returns the new bytes.
|
|
200
200
|
|
|
201
|
-
`
|
|
201
|
+
`MiniRacerCsim::V8_CACHED_DATA_VERSION_TAG` exposes V8's
|
|
202
202
|
`ScriptCompiler::CachedDataVersionTag()` — mix it into your cache key alongside
|
|
203
203
|
the source hash so a libv8-node version bump invalidates stale blobs automatically.
|
|
204
204
|
The constant is populated on first `Context.new` (after `Platform.set_flags!`),
|
|
205
205
|
so read it after constructing at least one Context.
|
|
206
206
|
|
|
207
207
|
```ruby
|
|
208
|
-
key = "#{Digest::SHA256.hexdigest(source)}-#{
|
|
208
|
+
key = "#{Digest::SHA256.hexdigest(source)}-#{MiniRacerCsim::V8_CACHED_DATA_VERSION_TAG}"
|
|
209
209
|
```
|
|
210
210
|
|
|
211
211
|
Notes:
|
|
@@ -218,32 +218,28 @@ Notes:
|
|
|
218
218
|
`Context#dispose` clears them.
|
|
219
219
|
- `produce_cache: true` is only safe at the top level. From inside a host-fn
|
|
220
220
|
callback (i.e., re-entrant compile while a JS → Ruby → JS frame is on the
|
|
221
|
-
stack) it raises `
|
|
221
|
+
stack) it raises `MiniRacerCsim::RuntimeError`, because V8's `CreateCodeCache`
|
|
222
222
|
walks live isolate state and corrupts the parser when re-entered. Warm the
|
|
223
223
|
cache from the top level once and pass it back via `cached_data:` from your
|
|
224
224
|
callbacks.
|
|
225
|
-
- Cross-process reuse is **incompatible with `
|
|
225
|
+
- Cross-process reuse is **incompatible with `MiniRacerCsim::Platform.set_flags!(:single_threaded)`**.
|
|
226
226
|
V8's single-threaded mode embeds process-local state in the cache blob, so
|
|
227
227
|
every cached_data is rejected when consumed in a fresh process. Same-process
|
|
228
228
|
reuse still works under `:single_threaded`. If you need both cross-process
|
|
229
229
|
reuse and `:single_threaded` (e.g. for fork-safety reasons), disable
|
|
230
230
|
`:single_threaded` for the path that produces / consumes the cache.
|
|
231
|
-
- On TruffleRuby, `Script` is implemented as source replay (GraalJS has no
|
|
232
|
-
equivalent per-script bytecode cache reachable from `Polyglot::InnerContext`),
|
|
233
|
-
so `cached_data` and `produce_cache` are silently ignored and `cached_data`
|
|
234
|
-
always returns `nil`, and `MiniRacer::V8_CACHED_DATA_VERSION_TAG` is `0`.
|
|
235
231
|
|
|
236
232
|
### Fork Safety
|
|
237
233
|
|
|
238
234
|
Some Ruby web servers employ forking (for example unicorn or puma in clustered mode). V8 is not fork safe by default and sadly Ruby does not have support for fork notifications per [#5446](https://bugs.ruby-lang.org/issues/5446).
|
|
239
235
|
|
|
240
|
-
Since 0.6.1 mini_racer does support V8 single threaded platform mode which should remove most forking related issues. To enable run this before using `
|
|
236
|
+
Since 0.6.1 mini_racer does support V8 single threaded platform mode which should remove most forking related issues. To enable run this before using `MiniRacerCsim::Context`, for example in a Rails initializer:
|
|
241
237
|
|
|
242
238
|
```ruby
|
|
243
|
-
|
|
239
|
+
MiniRacerCsim::Platform.set_flags!(:single_threaded)
|
|
244
240
|
```
|
|
245
241
|
|
|
246
|
-
When using pre-fork `
|
|
242
|
+
When using pre-fork `MiniRacerCsim::Context` objects in `:single_threaded` mode,
|
|
247
243
|
ensure the process only forks while MiniRacer is quiescent: no thread may be
|
|
248
244
|
evaluating JavaScript, calling into a context, disposing/freeing a context,
|
|
249
245
|
running a Ruby callback from JavaScript, or otherwise using MiniRacer at the
|
|
@@ -254,14 +250,14 @@ in an unusable state in the child process.
|
|
|
254
250
|
|
|
255
251
|
If you want to ensure your application does not leak memory after fork either:
|
|
256
252
|
|
|
257
|
-
1. Ensure no `
|
|
258
|
-
2. Dispose manually of all `
|
|
253
|
+
1. Ensure no `MiniRacerCsim::Context` objects are created in the master process; or
|
|
254
|
+
2. Dispose manually of all `MiniRacerCsim::Context` objects prior to forking
|
|
259
255
|
|
|
260
256
|
```ruby
|
|
261
257
|
# before fork
|
|
262
258
|
|
|
263
259
|
require "objspace"
|
|
264
|
-
ObjectSpace.each_object(
|
|
260
|
+
ObjectSpace.each_object(MiniRacerCsim::Context){|c| c.dispose}
|
|
265
261
|
|
|
266
262
|
# fork here
|
|
267
263
|
```
|
|
@@ -271,7 +267,7 @@ ObjectSpace.each_object(MiniRacer::Context){|c| c.dispose}
|
|
|
271
267
|
Context usage is threadsafe
|
|
272
268
|
|
|
273
269
|
```ruby
|
|
274
|
-
context =
|
|
270
|
+
context = MiniRacerCsim::Context.new
|
|
275
271
|
context.eval("counter=0; plus=()=>counter++;")
|
|
276
272
|
|
|
277
273
|
(1..10).map do
|
|
@@ -289,9 +285,9 @@ puts context.eval("counter")
|
|
|
289
285
|
Contexts can be created with pre-loaded snapshots:
|
|
290
286
|
|
|
291
287
|
```ruby
|
|
292
|
-
snapshot =
|
|
288
|
+
snapshot = MiniRacerCsim::Snapshot.new("function hello() { return 'world!'; }")
|
|
293
289
|
|
|
294
|
-
context =
|
|
290
|
+
context = MiniRacerCsim::Context.new(snapshot: snapshot)
|
|
295
291
|
|
|
296
292
|
context.eval("hello()")
|
|
297
293
|
# => "world!"
|
|
@@ -313,11 +309,11 @@ Snapshots can come in handy for example if you want your contexts to be pre-load
|
|
|
313
309
|
Also note that snapshots can be warmed up, using the `warmup!` method, which allows you to call functions which are otherwise lazily compiled to get them to compile right away; any side effect of your warm up code being then dismissed. [More details on warming up here](https://github.com/electron/electron/issues/169#issuecomment-76783481), and a small example:
|
|
314
310
|
|
|
315
311
|
```ruby
|
|
316
|
-
snapshot =
|
|
312
|
+
snapshot = MiniRacerCsim::Snapshot.new("var counter = 0; function hello() { counter++; return 'world! '; }")
|
|
317
313
|
|
|
318
314
|
snapshot.warmup!("hello()")
|
|
319
315
|
|
|
320
|
-
context =
|
|
316
|
+
context = MiniRacerCsim::Context.new(snapshot: snapshot)
|
|
321
317
|
|
|
322
318
|
context.eval("hello()")
|
|
323
319
|
# => "world! 1"
|
|
@@ -329,13 +325,13 @@ Snapshots can also be persisted to disk for faster startup:
|
|
|
329
325
|
|
|
330
326
|
```ruby
|
|
331
327
|
# Save a snapshot to disk
|
|
332
|
-
snapshot =
|
|
328
|
+
snapshot = MiniRacerCsim::Snapshot.new('var foo = "bar";')
|
|
333
329
|
File.binwrite("snapshot.bin", snapshot.dump)
|
|
334
330
|
|
|
335
331
|
# Load it back in a later process
|
|
336
332
|
blob = File.binread("snapshot.bin")
|
|
337
|
-
snapshot =
|
|
338
|
-
context =
|
|
333
|
+
snapshot = MiniRacerCsim::Snapshot.load(blob)
|
|
334
|
+
context = MiniRacerCsim::Context.new(snapshot: snapshot)
|
|
339
335
|
context.eval("foo")
|
|
340
336
|
# => "bar"
|
|
341
337
|
```
|
|
@@ -346,26 +342,26 @@ Note that snapshots are architecture and V8-version specific. A snapshot created
|
|
|
346
342
|
|
|
347
343
|
### Garbage collection
|
|
348
344
|
|
|
349
|
-
You can make the garbage collector more aggressive by defining the context with `
|
|
345
|
+
You can make the garbage collector more aggressive by defining the context with `MiniRacerCsim::Context.new(ensure_gc_after_idle: 1000)`. Using this will ensure V8 will run a full GC using `context.low_memory_notification` 1 second after the last eval on the context. Low memory notifications ensure long living contexts use minimal amounts of memory.
|
|
350
346
|
|
|
351
347
|
### V8 Runtime flags
|
|
352
348
|
|
|
353
349
|
It is possible to set V8 Runtime flags:
|
|
354
350
|
|
|
355
351
|
```ruby
|
|
356
|
-
|
|
352
|
+
MiniRacerCsim::Platform.set_flags! :noconcurrent_recompilation, max_inlining_levels: 10
|
|
357
353
|
```
|
|
358
354
|
|
|
359
355
|
This can come in handy if you want to use MiniRacer with Unicorn, which doesn't seem to always appreciate V8's liberal use of threading:
|
|
360
356
|
|
|
361
357
|
```ruby
|
|
362
|
-
|
|
358
|
+
MiniRacerCsim::Platform.set_flags! :noconcurrent_recompilation, :noconcurrent_sweeping
|
|
363
359
|
```
|
|
364
360
|
|
|
365
361
|
Or else to unlock experimental features in V8, for example tail recursion optimization:
|
|
366
362
|
|
|
367
363
|
```ruby
|
|
368
|
-
|
|
364
|
+
MiniRacerCsim::Platform.set_flags! :harmony
|
|
369
365
|
|
|
370
366
|
js = <<-JS
|
|
371
367
|
'use strict';
|
|
@@ -379,13 +375,13 @@ var f = function f(n){
|
|
|
379
375
|
f(1e6);
|
|
380
376
|
JS
|
|
381
377
|
|
|
382
|
-
context =
|
|
378
|
+
context = MiniRacerCsim::Context.new
|
|
383
379
|
|
|
384
380
|
context.eval js
|
|
385
381
|
# => "foo"
|
|
386
382
|
```
|
|
387
383
|
|
|
388
|
-
The same code without the harmony runtime flag results in a `
|
|
384
|
+
The same code without the harmony runtime flag results in a `MiniRacerCsim::RuntimeError: RangeError: Maximum call stack size exceeded` exception.
|
|
389
385
|
Please refer to http://node.green/ as a reference on other harmony features.
|
|
390
386
|
|
|
391
387
|
A list of all V8 runtime flags can be found using `node --v8-options`, or else by perusing [the V8 source code for flags (make sure to use the right version of V8)](https://github.com/v8/v8/blob/master/src/flags/flag-definitions.h).
|
|
@@ -404,7 +400,7 @@ Flags:
|
|
|
404
400
|
When hosting v8 you may want to keep track of memory usage, use `#heap_stats` to get memory usage:
|
|
405
401
|
|
|
406
402
|
```ruby
|
|
407
|
-
context =
|
|
403
|
+
context = MiniRacerCsim::Context.new
|
|
408
404
|
# use context
|
|
409
405
|
p context.heap_stats
|
|
410
406
|
# {:total_physical_size=>1280640,
|
|
@@ -417,11 +413,11 @@ p context.heap_stats
|
|
|
417
413
|
If you wish to dispose of a context before waiting on the GC use `#dispose`:
|
|
418
414
|
|
|
419
415
|
```ruby
|
|
420
|
-
context =
|
|
416
|
+
context = MiniRacerCsim::Context.new
|
|
421
417
|
context.eval("let a='testing';")
|
|
422
418
|
context.dispose
|
|
423
419
|
context.eval("a = 2")
|
|
424
|
-
#
|
|
420
|
+
# MiniRacerCsim::ContextDisposedError
|
|
425
421
|
|
|
426
422
|
# nothing works on the context from now on, it's a shell waiting to be disposed
|
|
427
423
|
```
|
|
@@ -429,7 +425,7 @@ context.eval("a = 2")
|
|
|
429
425
|
A MiniRacer context can also be dumped in a heapsnapshot file using `#write_heap_snapshot(file_or_io)`
|
|
430
426
|
|
|
431
427
|
```ruby
|
|
432
|
-
context =
|
|
428
|
+
context = MiniRacerCsim::Context.new
|
|
433
429
|
# use context
|
|
434
430
|
context.write_heap_snapshot("test.heapsnapshot")
|
|
435
431
|
```
|
|
@@ -441,7 +437,7 @@ This file can then be loaded in the "memory" tab of the [Chrome DevTools](https:
|
|
|
441
437
|
This calls the function passed as first argument:
|
|
442
438
|
|
|
443
439
|
```ruby
|
|
444
|
-
context =
|
|
440
|
+
context = MiniRacerCsim::Context.new
|
|
445
441
|
context.eval("function hello(name) { return `Hello, ${name}!` }")
|
|
446
442
|
context.call("hello", "George")
|
|
447
443
|
# "Hello, George!"
|
|
@@ -457,7 +453,7 @@ Performance is slightly better than running `context.eval("hello('George')")` si
|
|
|
457
453
|
V8 drains its microtask queue (e.g. callbacks queued via `Promise.resolve().then(...)`) automatically when script execution returns to the embedder, so most code "just works":
|
|
458
454
|
|
|
459
455
|
```ruby
|
|
460
|
-
context =
|
|
456
|
+
context = MiniRacerCsim::Context.new
|
|
461
457
|
context.eval(<<~JS)
|
|
462
458
|
let x = 0;
|
|
463
459
|
Promise.resolve().then(() => x = 99);
|
|
@@ -469,7 +465,7 @@ context.eval("x")
|
|
|
469
465
|
When JavaScript invokes a Ruby callback synchronously and you need queued microtasks to drain mid-execution — e.g. for spec-compliant ordering across a chain of synchronous `dispatchEvent` listeners — call `context.perform_microtask_checkpoint` from the callback:
|
|
470
466
|
|
|
471
467
|
```ruby
|
|
472
|
-
context =
|
|
468
|
+
context = MiniRacerCsim::Context.new
|
|
473
469
|
context.attach("drain", -> { context.perform_microtask_checkpoint })
|
|
474
470
|
context.eval(<<~JS)
|
|
475
471
|
globalThis.log = [];
|
|
@@ -489,7 +485,7 @@ When the drain has to happen from within JavaScript itself — for example betwe
|
|
|
489
485
|
It is exposed through an opt-in **host namespace** — a single object (in the spirit of Deno's `Deno` or Bun's `Bun`) that mini_racer hangs its non-standard helpers off. Pass `host_namespace:` to enable it; by default nothing is injected and the global stays clean:
|
|
490
486
|
|
|
491
487
|
```ruby
|
|
492
|
-
context =
|
|
488
|
+
context = MiniRacerCsim::Context.new(host_namespace: "MiniRacer")
|
|
493
489
|
context.eval(<<~JS)
|
|
494
490
|
globalThis.log = [];
|
|
495
491
|
Promise.resolve().then(() => log.push("microtask"));
|
|
@@ -501,7 +497,7 @@ context.eval("log")
|
|
|
501
497
|
# => ["before", "microtask", "after"]
|
|
502
498
|
```
|
|
503
499
|
|
|
504
|
-
`host_namespace:` accepts a String (the global name to use — it must be a valid JavaScript identifier), `true` (the default name `"MiniRacer"`), or `nil`/`false` (the default — inject nothing). The namespace object is defined non-enumerable so it does not appear in `Object.keys(globalThis)`, while its methods are ordinary properties discoverable via `Object.keys(MiniRacer)`. Like `perform_microtask_checkpoint`, `drainMicrotasks()` is a no-op while a microtask checkpoint is already in progress, and it lets watchdog/out-of-memory termination propagate to the enclosing `eval`/`call`.
|
|
500
|
+
`host_namespace:` accepts a String (the global name to use — it must be a valid JavaScript identifier), `true` (the default name `"MiniRacer"`), or `nil`/`false` (the default — inject nothing). The namespace object is defined non-enumerable so it does not appear in `Object.keys(globalThis)`, while its methods are ordinary properties discoverable via `Object.keys(MiniRacer)`. Like `perform_microtask_checkpoint`, `drainMicrotasks()` is a no-op while a microtask checkpoint is already in progress, and it lets watchdog/out-of-memory termination propagate to the enclosing `eval`/`call`.
|
|
505
501
|
|
|
506
502
|
### ES modules
|
|
507
503
|
|
|
@@ -511,7 +507,7 @@ syntax), modules can have static imports that resolve to other modules and
|
|
|
511
507
|
expose named exports through a real Module Namespace Object.
|
|
512
508
|
|
|
513
509
|
```ruby
|
|
514
|
-
context =
|
|
510
|
+
context = MiniRacerCsim::Context.new
|
|
515
511
|
|
|
516
512
|
dep = context.compile_module("export const base = 10", filename: "dep.js")
|
|
517
513
|
main = context.compile_module(<<~JS, filename: "main.js")
|
|
@@ -527,18 +523,18 @@ main.namespace # => {"doubled" => 20}
|
|
|
527
523
|
```
|
|
528
524
|
|
|
529
525
|
* `Context#compile_module(source, filename:)` — parses the source as a
|
|
530
|
-
module; the returned `
|
|
526
|
+
module; the returned `MiniRacerCsim::Module` is bound to its Context. The
|
|
531
527
|
`filename` is also exposed to the module as `import.meta.url`.
|
|
532
528
|
* `Module#instantiate { |specifier, referrer_url| ... }` — walks the static
|
|
533
529
|
import graph. The resolver block is called once per import declaration
|
|
534
530
|
with the raw specifier string and the importing module's filename, so
|
|
535
531
|
relative specifiers (`./foo`, `../bar`) can be resolved against the
|
|
536
|
-
referrer. It must return another `
|
|
532
|
+
referrer. It must return another `MiniRacerCsim::Module` (typically from a
|
|
537
533
|
per-Context cache). Imports can also be resolved lazily from inside the
|
|
538
534
|
block via further `Context#compile_module` calls.
|
|
539
535
|
* `Module#evaluate` — runs the module body. Returns the evaluation result
|
|
540
536
|
(`nil` for the typical `export const …` shape). Modules with top-level
|
|
541
|
-
`await` raise `
|
|
537
|
+
`await` raise `MiniRacerCsim::RuntimeError` for now.
|
|
542
538
|
* `Module#namespace` — returns the Module Namespace Object as a Hash
|
|
543
539
|
(`{ "default" => …, "namedExport" => … }`). Available after
|
|
544
540
|
`instantiate` succeeds; `evaluate` populates the values.
|
|
@@ -548,7 +544,7 @@ main.namespace # => {"doubled" => 20}
|
|
|
548
544
|
the convention used elsewhere.
|
|
549
545
|
* `Context#dynamic_import_resolver = proc { |specifier, referrer_url| ... }`
|
|
550
546
|
— handler for JS `import(...)` expressions. The proc must return a
|
|
551
|
-
`
|
|
547
|
+
`MiniRacerCsim::Module` (already instantiated; `evaluate` is driven for you
|
|
552
548
|
if pending). Set to `nil` to reject all dynamic imports. Drain the
|
|
553
549
|
microtask queue with `Context#perform_microtask_checkpoint` to see the
|
|
554
550
|
result in a `.then` callback or after `await`.
|
|
@@ -570,9 +566,6 @@ Notes:
|
|
|
570
566
|
accumulate handles until `Context#dispose` clears them.
|
|
571
567
|
- Top-level await is not yet supported; `evaluate` raises if the
|
|
572
568
|
module's evaluation promise stays pending after the microtask drain.
|
|
573
|
-
- On TruffleRuby, `Context#compile_module` raises `NotImplementedError`
|
|
574
|
-
— GraalJS has its own module-loading mechanism that doesn't map onto
|
|
575
|
-
this handle-based API. PRs to bridge are welcome.
|
|
576
569
|
|
|
577
570
|
## Performance
|
|
578
571
|
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
require 'mkmf'
|
|
2
2
|
|
|
3
|
-
$srcs = ["
|
|
3
|
+
$srcs = ["mini_racer_csim_extension.c", "mini_racer_v8.cc"]
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
require_relative '../../lib/mini_racer/version'
|
|
11
|
-
gem 'libv8-node', MiniRacer::LIBV8_NODE_VERSION
|
|
5
|
+
require_relative '../../lib/mini_racer_csim/version'
|
|
6
|
+
gem 'libv8-node', MiniRacerCsim::LIBV8_NODE_VERSION
|
|
12
7
|
require 'libv8-node'
|
|
13
8
|
|
|
14
9
|
IS_DARWIN = RUBY_PLATFORM =~ /darwin/
|
|
@@ -72,4 +67,4 @@ if RUBY_ENGINE == 'ruby'
|
|
|
72
67
|
$CPPFLAGS += ' -DENGINE_IS_CRUBY '
|
|
73
68
|
end
|
|
74
69
|
|
|
75
|
-
create_makefile '
|
|
70
|
+
create_makefile 'mini_racer_csim_extension'
|