dommy-js-quickjs 0.10.0 → 0.11.0
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.md +19 -0
- data/README.md +29 -7
- data/lib/dommy/js/quickjs/backend.rb +12 -37
- data/lib/dommy/js/quickjs/runtime.rb +20 -22
- data/lib/dommy/js/quickjs/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df093cff1000de6ad73f27367c17845c2b6846b87785913d70695b0d38938daf
|
|
4
|
+
data.tar.gz: aab8821f4e6974306d7e47b35730f32d7340cec51437d0926e94986e692bbb4d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: efe4b423fc15fc33f2e3a77108f145cadf0d4d072b69c9447f958cbdcb872b367cd7857ce07a975bacb2d7a64a36c3bee760a4070ebfcc893bff8e6df8ea8441
|
|
7
|
+
data.tar.gz: c5fa56f132d9df6e4c067f96f0fa5c1e915c63d9ff641832c72aac75307e6fad57d7e671e4585e0cdffe73419cba0e965a08d750e2c50e5dfbad223b5987c6f1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.0 — 2026-10-04
|
|
4
|
+
|
|
5
|
+
Requires `dommy >= 0.15.0, < 0.16` and `quickjs ~> 0.22.0`.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- ES modules can be preloaded as bytecode. `Runtime.register_module(name, source:)` registers a module once per process (wrapping `Quickjs.register_module`), and `Runtime.new(preload_modules: [name, …])` reads registered modules into the new VM, so a later page loads them without parsing their source again. A preloaded module is found before the module loader is asked for its name, and a loader that returns `{as: name}` for another specifier lands on it too. dommy's `Dommy::Js::ModulePreload` drives both: with it enabled, a page that imports Turbo as an ES module boots in about 11 ms instead of 24 ms from the third page on.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Requires `dommy >= 0.15.0, < 0.16` (was `>= 0.14.0, < 0.15`), the release whose `ModulePreload` uses the API above and whose behavior the suite checks.
|
|
14
|
+
- Requires `quickjs ~> 0.22.0` (was `~> 0.21.0`). 0.22.0 releases [hmsk/quickjs.rb#141](https://github.com/hmsk/quickjs.rb/pull/141): an unhandled rejection is reported at the end of the microtask checkpoint, as HTML requires, so `Promise.reject(x).catch(...)` and `try { await rejecting() } catch {}` are no longer misreported. On a RubyGems install, `on_unhandled_rejection` reports again, a strict `Dommy::Browser` fails on a rejection the page leaves unhandled, and the page's `unhandledrejection` event fires. `event.reason` and `event.promise` are still not the page's own values there, and `rejectionhandled` does not fire: those need the JS rejection hook, which no quickjs release has yet.
|
|
15
|
+
- The development Gemfile pins the quickjs fork that carries the hook by tag ([`v0.22.0-rejection-hook.1`](https://github.com/takahashim/quickjs.rb/tree/v0.22.0-rejection-hook.1)) rather than by commit, so the fork's branch can be rebased onto each upstream release without stranding the ref an older commit pins.
|
|
16
|
+
- The suite no longer takes `DOMMY_BACKEND`: dommy 0.15 has Makiri as its only backend.
|
|
17
|
+
|
|
18
|
+
### Removed
|
|
19
|
+
|
|
20
|
+
- The 0.21.0 workaround is gone: `Backend.premature_rejection_reports?`, its once-per-process probe, and the warning `on_unhandled_rejection` gave in place of reporting. No quickjs the gemspec allows behaves that way.
|
|
21
|
+
|
|
3
22
|
## 0.10.0 — 2026-10-01
|
|
4
23
|
|
|
5
24
|
Requires `dommy >= 0.14.0, < 0.15` and `quickjs ~> 0.21.0`.
|
data/README.md
CHANGED
|
@@ -110,15 +110,37 @@ The pieces it relies on are public Runtime API:
|
|
|
110
110
|
- `Runtime#on_unhandled_rejection { |err| }` — surface promise rejections that
|
|
111
111
|
reach the microtask queue with no handler. Frameworks swallow these; `err.backtrace`
|
|
112
112
|
carries the JS stack, which is the difference between blind and one-shot debugging.
|
|
113
|
-
The released quickjs (0.21.0) reports a rejection before a handler attached in
|
|
114
|
-
the same microtask checkpoint can catch it, so `.catch` and `try { await … }
|
|
115
|
-
catch` would look unhandled; on such an engine nothing is reported here (a
|
|
116
|
-
warning says so once), so a strict `Dommy::Browser` does not fail on
|
|
117
|
-
rejections and the page's `unhandledrejection` event (and
|
|
118
|
-
`window.onunhandledrejection`) never fires. A quickjs with
|
|
119
|
-
[hmsk/quickjs.rb#141](https://github.com/hmsk/quickjs.rb/pull/141) reports them.
|
|
120
113
|
- `Runtime#on_log { |log| }` — observe `console.*` (`log.severity` / `log.to_s`).
|
|
121
114
|
|
|
115
|
+
### Preloading ES modules
|
|
116
|
+
|
|
117
|
+
A module the loader hands over as source is parsed again by every Runtime that
|
|
118
|
+
imports it. Register it once per process and later Runtimes read it in as
|
|
119
|
+
bytecode instead:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
Dommy::Js::Quickjs::Runtime.register_module("https://example.test/vendor/turbo.js", source: turbo_js)
|
|
123
|
+
|
|
124
|
+
rt = Dommy::Js::Quickjs::Runtime.new(preload_modules: ["https://example.test/vendor/turbo.js"])
|
|
125
|
+
rt.module_loader = ->(specifier, _importer) {
|
|
126
|
+
{as: "https://example.test/vendor/turbo.js"} if specifier == "@hotwired/turbo"
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- The name is the module's canonical name: the VM keys its module map by it,
|
|
131
|
+
and the module's relative imports resolve against it. Register a page's
|
|
132
|
+
module under its resolved URL.
|
|
133
|
+
- `source:` may be a Proc returning the source; it is read when a Runtime first
|
|
134
|
+
preloads the module, and the bytecode is shared from then on. Each Runtime
|
|
135
|
+
still gets its own instance, with its own module-level state.
|
|
136
|
+
- A preloaded module is found before the loader is asked for its name. A loader
|
|
137
|
+
that returns `{as: name}` for another specifier (an importmap entry, say)
|
|
138
|
+
lands on it too.
|
|
139
|
+
- A preloaded module's own imports are not preloaded; register each one you
|
|
140
|
+
want cached. Naming a module that is not registered raises `ArgumentError`.
|
|
141
|
+
- Preloading bypasses the loader for that name, so do not preload a module the
|
|
142
|
+
loader would refuse to some importers.
|
|
143
|
+
|
|
122
144
|
### Capybara
|
|
123
145
|
|
|
124
146
|
Requiring the adapter enables `execute_script` / `evaluate_script` on
|
|
@@ -45,42 +45,9 @@ module Dommy
|
|
|
45
45
|
# the top-level tag); either way it maps to Dommy::Bridge::UNDEFINED. No
|
|
46
46
|
# normalization is needed here.
|
|
47
47
|
class Backend
|
|
48
|
-
# Whether this quickjs reports a promise rejection the moment it
|
|
49
|
-
# happens, before a handler attached later in the same microtask
|
|
50
|
-
# checkpoint gets the chance — the released quickjs (0.21.0) does, so
|
|
51
|
-
# `Promise.reject(x).catch(f)` and `try { await p } catch {}` both
|
|
52
|
-
# come out as unhandled. HTML reports at the end of the checkpoint
|
|
53
|
-
# (hmsk/quickjs.rb#141, merged upstream but unreleased).
|
|
54
|
-
#
|
|
55
|
-
# Decided by behavior, not by version — a git checkout of the fixed
|
|
56
|
-
# engine still says 0.21.0 — on a throwaway VM, once per process.
|
|
57
|
-
def self.premature_rejection_reports?
|
|
58
|
-
return @premature_rejection_reports if defined?(@premature_rejection_reports)
|
|
59
|
-
|
|
60
|
-
@premature_rejection_reports = probe_premature_rejection_reports
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def self.probe_premature_rejection_reports
|
|
64
|
-
vm = ::Quickjs::VM.new
|
|
65
|
-
reported = false
|
|
66
|
-
vm.on_unhandled_rejection { |_error| reported = true }
|
|
67
|
-
vm.eval_code(<<~JS, async: false)
|
|
68
|
-
Promise.reject(1).catch(function () {});
|
|
69
|
-
(async function () { try { await Promise.reject(2); } catch (e) {} })();
|
|
70
|
-
0
|
|
71
|
-
JS
|
|
72
|
-
vm.drain_jobs!
|
|
73
|
-
reported
|
|
74
|
-
rescue ::StandardError
|
|
75
|
-
# A probe that cannot run says nothing either way; keep reporting.
|
|
76
|
-
false
|
|
77
|
-
ensure
|
|
78
|
-
vm&.dispose!
|
|
79
|
-
end
|
|
80
|
-
private_class_method :probe_premature_rejection_reports
|
|
81
|
-
|
|
82
48
|
# Timeout and memory ceiling both differ from the gem's defaults, for
|
|
83
|
-
# reasons Config documents.
|
|
49
|
+
# reasons Config documents. Every other option goes to the VM as is,
|
|
50
|
+
# `preload_modules:` among them (see Runtime#initialize).
|
|
84
51
|
def initialize(**vm_opts)
|
|
85
52
|
vm_opts = {timeout_msec: Config.timeout_msec, memory_limit: Config.memory_limit}.merge(vm_opts)
|
|
86
53
|
@vm = ::Quickjs::VM.new(**vm_opts)
|
|
@@ -114,6 +81,14 @@ module Dommy
|
|
|
114
81
|
::Quickjs.compile(source, filename: filename)
|
|
115
82
|
end
|
|
116
83
|
|
|
84
|
+
# Register an ES module process-wide under its canonical name: compiled
|
|
85
|
+
# to bytecode once, on the first VM that preloads it, and read into each
|
|
86
|
+
# VM that names it in `preload_modules:`. `source:` is a String or a Proc
|
|
87
|
+
# returning one.
|
|
88
|
+
def self.register_module(name, source:)
|
|
89
|
+
::Quickjs.register_module(name, source: source)
|
|
90
|
+
end
|
|
91
|
+
|
|
117
92
|
# Process-global cache for the engine-internal runtime bundles run via
|
|
118
93
|
# #run_bundle (host_runtime.js, observable_runtime.js). Kept separate
|
|
119
94
|
# from ScriptCache (user-facing external scripts) so the two concerns
|
|
@@ -143,8 +118,8 @@ module Dommy
|
|
|
143
118
|
def eval_awaited(js) = guarded { @vm.eval_code(js, async: true) }
|
|
144
119
|
|
|
145
120
|
# Install the ESM module resolver: a callable `(specifier, importer) ->
|
|
146
|
-
# source String | { code:, as: } | nil` the engine consults for
|
|
147
|
-
# static/dynamic `import`. nil clears it (engine default loader).
|
|
121
|
+
# source String | { code:, as: } | { as: } | nil` the engine consults for
|
|
122
|
+
# every static/dynamic `import`. nil clears it (engine default loader).
|
|
148
123
|
def module_loader=(callable)
|
|
149
124
|
@vm.module_loader = callable
|
|
150
125
|
end
|
|
@@ -21,8 +21,23 @@ module Dommy
|
|
|
21
21
|
# What stays here is the port itself: script evaluation, and the wiring
|
|
22
22
|
# that decides which collaborator answers a host's call.
|
|
23
23
|
class Runtime
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
# Register an ES module process-wide, so a later Runtime can preload it
|
|
25
|
+
# as bytecode instead of parsing its source again. `name` is the
|
|
26
|
+
# module's canonical name — the one the VM's module map keys it by and
|
|
27
|
+
# its relative imports resolve against — so a page module is best
|
|
28
|
+
# registered under its resolved URL. `source:` is a String or a Proc
|
|
29
|
+
# returning one (read only when a Runtime first preloads it).
|
|
30
|
+
def self.register_module(name, source:)
|
|
31
|
+
Backend.register_module(name, source: source)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# `preload_modules:` names registered modules (see .register_module) to
|
|
35
|
+
# read into this Runtime's VM as it is made. A preloaded module is found
|
|
36
|
+
# before the module loader is asked for its name, and a loader that
|
|
37
|
+
# returns `{as: name}` for another specifier lands on it too. A name not
|
|
38
|
+
# registered raises ArgumentError. Other options go to the VM.
|
|
39
|
+
def initialize(preload_modules: [], **vm_opts)
|
|
40
|
+
@backend = Backend.new(preload_modules: preload_modules, **vm_opts)
|
|
26
41
|
@bridge = Dommy::Js::HostBridge.new(@backend)
|
|
27
42
|
@environment = BrowserEnvironment.new(@backend)
|
|
28
43
|
@errors = ErrorTranslator.new(@backend, @bridge)
|
|
@@ -113,7 +128,9 @@ module Dommy
|
|
|
113
128
|
end
|
|
114
129
|
|
|
115
130
|
# Install the ESM module resolver (see Backend#module_loader=). A
|
|
116
|
-
# callable `(specifier, importer) -> source | {code:, as:} | nil
|
|
131
|
+
# callable `(specifier, importer) -> source | {code:, as:} | {as:} | nil`;
|
|
132
|
+
# `{as: name}` with no code redirects to a module the VM already has,
|
|
133
|
+
# such as one in `preload_modules:`.
|
|
117
134
|
def module_loader=(callable)
|
|
118
135
|
@backend.module_loader = callable
|
|
119
136
|
end
|
|
@@ -195,19 +212,11 @@ module Dommy
|
|
|
195
212
|
# Surface otherwise-swallowed JS promise rejections (see Backend).
|
|
196
213
|
# Goes quiet once the JS hook is installed: both would fire for the same
|
|
197
214
|
# rejection, and the hook carries strictly more.
|
|
198
|
-
#
|
|
199
|
-
# Goes quiet, too, on an engine that reports a rejection before a
|
|
200
|
-
# handler could attach (Backend.premature_rejection_reports?): there
|
|
201
|
-
# every `.catch` and `try { await … } catch` would surface as an
|
|
202
|
-
# unhandled rejection and fail a strict host on correct code. Reporting
|
|
203
|
-
# none is the lesser loss — a rejection the page truly leaves unhandled
|
|
204
|
-
# goes unreported until the engine is fixed.
|
|
205
215
|
def on_unhandled_rejection(&block)
|
|
206
216
|
# Decided when a rejection actually arrives, not now: a host registers
|
|
207
217
|
# this before #install_window, which is where the hook goes in.
|
|
208
218
|
relay = lambda do |error|
|
|
209
219
|
next if js_rejection_hook?
|
|
210
|
-
next self.class.warn_premature_rejection_reports if Backend.premature_rejection_reports?
|
|
211
220
|
|
|
212
221
|
block.call(@track_rejections ? @errors.enrich_rejection(error) : error)
|
|
213
222
|
end
|
|
@@ -215,17 +224,6 @@ module Dommy
|
|
|
215
224
|
self
|
|
216
225
|
end
|
|
217
226
|
|
|
218
|
-
# Said once per process, on the first rejection left unreported.
|
|
219
|
-
def self.warn_premature_rejection_reports
|
|
220
|
-
return if @warned_premature_rejection_reports
|
|
221
|
-
|
|
222
|
-
@warned_premature_rejection_reports = true
|
|
223
|
-
warn "dommy-js-quickjs: this quickjs (#{::Quickjs::VERSION}) reports a promise rejection before " \
|
|
224
|
-
"a handler can attach, so unhandled rejections are not reported. A quickjs with " \
|
|
225
|
-
"hmsk/quickjs.rb#141 (unhandled-rejection timing) reports them."
|
|
226
|
-
nil
|
|
227
|
-
end
|
|
228
|
-
|
|
229
227
|
# Whether rejections arrive through the JS hook. A host that also relays
|
|
230
228
|
# #on_unhandled_rejection would otherwise report each rejection twice.
|
|
231
229
|
def js_rejection_hook? = !!@js_rejection_hook
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dommy-js-quickjs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- takahashim
|
|
@@ -15,34 +15,34 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.22.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.22.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: dommy
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.15.0
|
|
33
33
|
- - "<"
|
|
34
34
|
- !ruby/object:Gem::Version
|
|
35
|
-
version: '0.
|
|
35
|
+
version: '0.16'
|
|
36
36
|
type: :runtime
|
|
37
37
|
prerelease: false
|
|
38
38
|
version_requirements: !ruby/object:Gem::Requirement
|
|
39
39
|
requirements:
|
|
40
40
|
- - ">="
|
|
41
41
|
- !ruby/object:Gem::Version
|
|
42
|
-
version: 0.
|
|
42
|
+
version: 0.15.0
|
|
43
43
|
- - "<"
|
|
44
44
|
- !ruby/object:Gem::Version
|
|
45
|
-
version: '0.
|
|
45
|
+
version: '0.16'
|
|
46
46
|
description: |
|
|
47
47
|
dommy-js-quickjs lets JavaScript drive a Dommy DOM by embedding QuickJS (via
|
|
48
48
|
the quickjs gem) and bridging DOM nodes to JS through an ES Proxy that routes
|