quickjs 0.18.0 → 0.19.0.pre1
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/README.md +20 -1
- data/ext/quickjsrb/extconf.rb +0 -5
- data/ext/quickjsrb/quickjsrb.c +33 -9
- data/ext/quickjsrb/quickjsrb.h +0 -2
- data/lib/quickjs/polyfills/intl.rb +11 -0
- data/lib/quickjs/polyfills.rb +67 -0
- data/lib/quickjs/version.rb +1 -1
- data/lib/quickjs.rb +4 -0
- data/polyfills/package-lock.json +2 -2
- data/polyfills/package.json +1 -1
- data/polyfills/rolldown.config.mjs +4 -1
- data/sig/quickjs.rbs +2 -0
- metadata +4 -2
- /data/{ext/quickjsrb/vendor/polyfill-intl-en.min.js → lib/quickjs/polyfills/intl-en.min.js} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 40b6432886ae2f54e4b38466b0916b7cdf94ff2e7c3ac94f6f3b66ebd762f09a
|
|
4
|
+
data.tar.gz: 4720748aaec1473b7c5a9a9ea2348196f45bbe9ac27ed6009a467d7e37ae0761
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 97aa1ca89e4bc79c12fe11f228202426d3b95ffa2558a6803f1045e58a983f7d0abb3b597185182bf1e507c5dc48ccecf237596c70feb1d21a2f862a64635472
|
|
7
|
+
data.tar.gz: 5c0932632f6f619fd7cb720b3ae6f0444ca20fd08413af42fa235bf6b5741e872e8e5139fe9a38df6c9810e8a50e5653e7da56ae1ff303134a1833e6521a034d
|
data/README.md
CHANGED
|
@@ -392,6 +392,25 @@ Useful when porting JS that assumed V8's implicit-drain semantics — V8 (and th
|
|
|
392
392
|
| `File` | → | `Quickjs::File` — `.name`, `.last_modified` + Blob attrs | requires `POLYFILL_FILE` |
|
|
393
393
|
| `File` proxy | ← | `::File` | requires `POLYFILL_FILE`; applies to `define_function` return values |
|
|
394
394
|
|
|
395
|
+
## Extending: registering polyfills
|
|
396
|
+
|
|
397
|
+
`Quickjs.register_polyfill(name, source:, init: nil)` adds a polyfill to a process-wide registry. Any VM constructed with `name` in its `features:` list runs the registered bundle on top of the JS runtime. Companion gems use this hook to ship additional polyfills (e.g. `Intl.Collator`, `DisplayNames`) without bundling them into the main gem.
|
|
398
|
+
|
|
399
|
+
```rb
|
|
400
|
+
Quickjs.register_polyfill(
|
|
401
|
+
:polyfill_my_thing,
|
|
402
|
+
source: File.read('vendor/my-polyfill.min.js'),
|
|
403
|
+
init: 'globalThis.MyThing ||= {};' # optional, runs before the bundle
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
vm = Quickjs::VM.new(features: [:polyfill_my_thing])
|
|
407
|
+
vm.eval_code('MyThing.greet("hi")')
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
The first VM with a given polyfill pays the parse cost (the source is compiled to QuickJS bytecode on a disposable VM with a generous timeout); subsequent VMs reuse the cached bytecode. The polyfill body runs without consuming the user VM's `timeout_msec` budget — that's reserved for user code.
|
|
411
|
+
|
|
412
|
+
The bundled `POLYFILL_INTL` (FormatJS Intl, `en` locale) is itself registered through this API at gem load time.
|
|
413
|
+
|
|
395
414
|
## Acknowledgements
|
|
396
415
|
|
|
397
416
|
- [@ursm](https://github.com/ursm) — for continuous contributions improving performance and developer experience
|
|
@@ -401,7 +420,7 @@ Useful when porting JS that assumed V8's implicit-drain semantics — V8 (and th
|
|
|
401
420
|
|
|
402
421
|
- `ext/quickjsrb/quickjs`
|
|
403
422
|
- [MIT License Copyright (c) 2017-2021 by Fabrice Bellard and Charlie Gordon](https://github.com/bellard/quickjs/blob/6e2e68fd0896957f92eb6c242a2e048c1ef3cae0/LICENSE).
|
|
404
|
-
- `
|
|
423
|
+
- `lib/quickjs/polyfills/intl-en.min.js` ([bundled and minified from `polyfills/`](https://github.com/hmsk/quickjs.rb/tree/main/polyfills))
|
|
405
424
|
- MIT License Copyright (c) 2022 FormatJS
|
|
406
425
|
- [@formatjs/intl-supportedvaluesof](https://github.com/formatjs/formatjs/blob/main/packages/intl-supportedvaluesof/LICENSE.md)
|
|
407
426
|
- MIT License Copyright (c) 2023 FormatJS
|
data/ext/quickjsrb/extconf.rb
CHANGED
|
@@ -12,7 +12,6 @@ $srcs = [
|
|
|
12
12
|
'cutils.c',
|
|
13
13
|
'quickjs.c',
|
|
14
14
|
'quickjs-libc.c',
|
|
15
|
-
'polyfill-intl-en.min.c',
|
|
16
15
|
'polyfill-file.min.c',
|
|
17
16
|
'polyfill-encoding.min.c',
|
|
18
17
|
'polyfill-url.min.c',
|
|
@@ -62,10 +61,6 @@ POLYFILL_OPTS=-fno-string-normalize -fno-typedarray -fno-typedarray -fno-eval -f
|
|
|
62
61
|
|
|
63
62
|
qjsc: ./qjsc.o $(QJS_LIB_OBJS)
|
|
64
63
|
$(CC) -g -o $@ $^ -lm -ldl -lpthread
|
|
65
|
-
polyfill-intl-en.min.js:
|
|
66
|
-
$(COPY) $(srcdir)/vendor/$@ $@
|
|
67
|
-
polyfill-intl-en.min.c: ./qjsc polyfill-intl-en.min.js
|
|
68
|
-
./qjsc $(POLYFILL_OPTS) -c -M polyfill/intl-en.so,intlen -m -o $@ polyfill-intl-en.min.js
|
|
69
64
|
polyfill-file.min.js:
|
|
70
65
|
$(COPY) $(srcdir)/vendor/$@ $@
|
|
71
66
|
polyfill-file.min.c: ./qjsc polyfill-file.min.js
|
data/ext/quickjsrb/quickjsrb.c
CHANGED
|
@@ -1077,15 +1077,8 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
|
|
|
1077
1077
|
JS_NewCFunction(data->context, js_quickjsrb_set_timeout, "setTimeout", 2));
|
|
1078
1078
|
}
|
|
1079
1079
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
const char *defineIntl = "Object.defineProperty(globalThis, 'Intl', { value:{} });\n";
|
|
1083
|
-
JSValue j_defineIntl = JS_Eval(data->context, defineIntl, strlen(defineIntl), vmInternalFilename, JS_EVAL_TYPE_GLOBAL);
|
|
1084
|
-
JS_FreeValue(data->context, j_defineIntl);
|
|
1085
|
-
|
|
1086
|
-
JSValue j_polyfillIntlResult = load_polyfill_bytecode(data->context, &qjsc_polyfill_intl_en_min, qjsc_polyfill_intl_en_min_size);
|
|
1087
|
-
JS_FreeValue(data->context, j_polyfillIntlResult);
|
|
1088
|
-
}
|
|
1080
|
+
// POLYFILL_INTL is registered Ruby-side via Quickjs.register_polyfill and
|
|
1081
|
+
// applied by the wrapper around VM#initialize in lib/quickjs/polyfills.rb.
|
|
1089
1082
|
|
|
1090
1083
|
if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featurePolyfillFileId))))
|
|
1091
1084
|
{
|
|
@@ -1317,6 +1310,36 @@ static VALUE vm_m_evalBytecode(VALUE r_self, VALUE r_bytecode)
|
|
|
1317
1310
|
return to_rb_return_value(data->context, j_returnedValue);
|
|
1318
1311
|
}
|
|
1319
1312
|
|
|
1313
|
+
// Loads pre-compiled polyfill bytecode without arming the eval timer.
|
|
1314
|
+
// The user's `timeout_msec` is a budget for *their* code; running our
|
|
1315
|
+
// own polyfill (~140 ms for FormatJS Intl) under that budget would
|
|
1316
|
+
// interrupt the load on tight defaults. Unlike load_polyfill_bytecode
|
|
1317
|
+
// above we hold the GVL through JS_ReadObject + JS_EvalFunction: the
|
|
1318
|
+
// bytecode buffer is a Ruby String, so releasing would let GC compact
|
|
1319
|
+
// the backing storage out from under us. The static-symbol path can
|
|
1320
|
+
// release safely; this path cannot.
|
|
1321
|
+
static VALUE vm_m_loadPolyfillBytecode(VALUE r_self, VALUE r_bytecode)
|
|
1322
|
+
{
|
|
1323
|
+
VMData *data;
|
|
1324
|
+
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
1325
|
+
|
|
1326
|
+
check_disposed(data);
|
|
1327
|
+
StringValue(r_bytecode);
|
|
1328
|
+
|
|
1329
|
+
JSValue j_func = JS_ReadObject(data->context,
|
|
1330
|
+
(const uint8_t *)RSTRING_PTR(r_bytecode),
|
|
1331
|
+
(size_t)RSTRING_LEN(r_bytecode),
|
|
1332
|
+
JS_READ_OBJ_BYTECODE);
|
|
1333
|
+
if (JS_IsException(j_func))
|
|
1334
|
+
return to_rb_value(data->context, j_func); // raises
|
|
1335
|
+
|
|
1336
|
+
JSValue j_result = JS_EvalFunction(data->context, j_func); // frees j_func
|
|
1337
|
+
if (JS_IsException(j_result))
|
|
1338
|
+
return to_rb_value(data->context, j_result); // raises
|
|
1339
|
+
JS_FreeValue(data->context, j_result);
|
|
1340
|
+
return Qnil;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1320
1343
|
static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
1321
1344
|
{
|
|
1322
1345
|
rb_need_block();
|
|
@@ -1728,6 +1751,7 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
|
|
|
1728
1751
|
rb_define_method(r_class_vm, "eval_code", vm_m_evalCode, -1);
|
|
1729
1752
|
rb_define_private_method(r_class_vm, "_compile_to_bytecode", vm_m_compile, -1);
|
|
1730
1753
|
rb_define_private_method(r_class_vm, "_run_bytecode", vm_m_evalBytecode, 1);
|
|
1754
|
+
rb_define_private_method(r_class_vm, "_load_polyfill_bytecode", vm_m_loadPolyfillBytecode, 1);
|
|
1731
1755
|
rb_define_method(r_class_vm, "call", vm_m_callGlobalFunction, -1);
|
|
1732
1756
|
rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
|
|
1733
1757
|
rb_define_method(r_class_vm, "import", vm_m_import, -1);
|
data/ext/quickjsrb/quickjsrb.h
CHANGED
|
@@ -14,8 +14,6 @@
|
|
|
14
14
|
#include <string.h>
|
|
15
15
|
#include <time.h>
|
|
16
16
|
|
|
17
|
-
extern const uint32_t qjsc_polyfill_intl_en_min_size;
|
|
18
|
-
extern const uint8_t qjsc_polyfill_intl_en_min;
|
|
19
17
|
extern const uint32_t qjsc_polyfill_file_min_size;
|
|
20
18
|
extern const uint8_t qjsc_polyfill_file_min;
|
|
21
19
|
extern const uint32_t qjsc_polyfill_encoding_min_size;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# FormatJS Intl polyfill (`en` locale only — getCanonicalLocales, Locale,
|
|
4
|
+
# PluralRules, NumberFormat, DateTimeFormat). Compiled and cached on first
|
|
5
|
+
# VM that enables the feature; the file is not read until then.
|
|
6
|
+
|
|
7
|
+
Quickjs.register_polyfill(
|
|
8
|
+
Quickjs::POLYFILL_INTL,
|
|
9
|
+
source: -> { File.read(File.expand_path('intl-en.min.js', __dir__)) },
|
|
10
|
+
init: "Object.defineProperty(globalThis, 'Intl', { value:{} });"
|
|
11
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Quickjs
|
|
4
|
+
# Process-wide registry; entries are lazily compiled to bytecode on
|
|
5
|
+
# first use and the bytecode is reused across VMs.
|
|
6
|
+
@_polyfills = {}
|
|
7
|
+
|
|
8
|
+
# `source:` accepts either a `String` (eager) or a `Proc` returning one
|
|
9
|
+
# (lazy). The lazy form lets a companion gem call `register_polyfill`
|
|
10
|
+
# at require time without paying the file-read cost unless a VM
|
|
11
|
+
# actually opts into the feature.
|
|
12
|
+
def self.register_polyfill(name, source:, init: nil)
|
|
13
|
+
raise ::TypeError, "name must be a Symbol, got #{name.class}" unless name.is_a?(Symbol)
|
|
14
|
+
raise ::TypeError, "source: must be a String or Proc, got #{source.class}" unless source.is_a?(String) || source.is_a?(Proc)
|
|
15
|
+
raise ::TypeError, "init: must be a String or nil, got #{init.class}" unless init.nil? || init.is_a?(String)
|
|
16
|
+
|
|
17
|
+
@_polyfills[name] = {source: source, init: init&.freeze, bytecode: nil}
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self._polyfill_for(name)
|
|
22
|
+
@_polyfills[name]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self._unregister_polyfill(name)
|
|
26
|
+
@_polyfills.delete(name)
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self._apply_registered_polyfills(vm, features)
|
|
31
|
+
features.each do |feature|
|
|
32
|
+
next unless (entry = @_polyfills[feature])
|
|
33
|
+
# `||=` isn't atomic — `_precompile_polyfill` releases the GVL inside
|
|
34
|
+
# `Quickjs.compile`, so two threads racing to construct VMs with the
|
|
35
|
+
# same polyfill can both see nil and both compile. The bytecode write
|
|
36
|
+
# is harmless because both threads produce identical bytes; the only
|
|
37
|
+
# observable cost is wasted compile work, and (for `source: Proc`) a
|
|
38
|
+
# second Proc invocation — so register Procs that are safe to call
|
|
39
|
+
# more than once.
|
|
40
|
+
entry[:bytecode] ||= _precompile_polyfill(entry, feature)
|
|
41
|
+
vm.send(:_load_polyfill_bytecode, entry[:bytecode])
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Compiled once per process per polyfill on a disposable VM whose
|
|
46
|
+
# generous timeout covers parsing multi-MB bundles (FormatJS Intl is
|
|
47
|
+
# ~2 MB). The user's per-VM `timeout_msec` is for their own JS — it
|
|
48
|
+
# would otherwise interrupt our infrastructure on tight defaults.
|
|
49
|
+
# `features: []` skips applying any registered polyfills to the temp
|
|
50
|
+
# VM (no recursion / no wasted polyfill loads).
|
|
51
|
+
def self._precompile_polyfill(entry, feature)
|
|
52
|
+
source = entry[:source]
|
|
53
|
+
source = source.call if source.is_a?(Proc)
|
|
54
|
+
combined = entry[:init] ? "#{entry[:init]}\n#{source}" : source
|
|
55
|
+
Quickjs.compile(combined, filename: feature.to_s, timeout_msec: 60_000, features: []).to_s
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
module PolyfillLoader
|
|
59
|
+
def initialize(features: [], **opts)
|
|
60
|
+
super
|
|
61
|
+
Quickjs._apply_registered_polyfills(self, features)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Guard against double-prepend if this file is reloaded (e.g. Rails dev).
|
|
66
|
+
VM.prepend(PolyfillLoader) unless VM.ancestors.include?(PolyfillLoader)
|
|
67
|
+
end
|
data/lib/quickjs/version.rb
CHANGED
data/lib/quickjs.rb
CHANGED
|
@@ -8,6 +8,10 @@ require_relative "quickjs/crypto_key"
|
|
|
8
8
|
require_relative "quickjs/function"
|
|
9
9
|
require_relative "quickjs/quickjsrb"
|
|
10
10
|
require_relative "quickjs/runnable"
|
|
11
|
+
# Polyfills.rb defines Quickjs.register_polyfill; polyfills/intl.rb calls
|
|
12
|
+
# it at load time, so the order matters.
|
|
13
|
+
require_relative "quickjs/polyfills"
|
|
14
|
+
require_relative "quickjs/polyfills/intl"
|
|
11
15
|
|
|
12
16
|
module Quickjs
|
|
13
17
|
class Blob
|
data/polyfills/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quickjs-rb-polyfills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0.pre1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "quickjs-rb-polyfills",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.19.0.pre1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@formatjs/intl-datetimeformat": "^7.3.1",
|
|
12
12
|
"@formatjs/intl-getcanonicallocales": "^3.2.2",
|
data/polyfills/package.json
CHANGED
|
@@ -4,7 +4,10 @@ export default [
|
|
|
4
4
|
defineConfig({
|
|
5
5
|
input: "src/intl-en.js",
|
|
6
6
|
output: {
|
|
7
|
-
|
|
7
|
+
// Loaded at runtime by lib/quickjs/polyfills/intl.rb via
|
|
8
|
+
// Quickjs.register_polyfill; other entries are still embedded as
|
|
9
|
+
// pre-compiled bytecode in the .so until they go the same way.
|
|
10
|
+
file: "../lib/quickjs/polyfills/intl-en.min.js",
|
|
8
11
|
format: "iife",
|
|
9
12
|
minify: true,
|
|
10
13
|
},
|
data/sig/quickjs.rbs
CHANGED
|
@@ -14,6 +14,8 @@ module Quickjs
|
|
|
14
14
|
def self.eval_code: (String code, ?Hash[Symbol, untyped] overwrite_opts) -> untyped
|
|
15
15
|
def self.compile: (String source, ?filename: String, **untyped) -> Quickjs::Runnable
|
|
16
16
|
|
|
17
|
+
def self.register_polyfill: (Symbol name, source: String | ^() -> String, ?init: String?) -> nil
|
|
18
|
+
|
|
17
19
|
class Value
|
|
18
20
|
UNDEFINED: Symbol
|
|
19
21
|
NAN: Symbol
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: quickjs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.19.0.pre1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- hmsk
|
|
@@ -86,11 +86,13 @@ files:
|
|
|
86
86
|
- ext/quickjsrb/quickjsrb_file.h
|
|
87
87
|
- ext/quickjsrb/vendor/polyfill-encoding.min.js
|
|
88
88
|
- ext/quickjsrb/vendor/polyfill-file.min.js
|
|
89
|
-
- ext/quickjsrb/vendor/polyfill-intl-en.min.js
|
|
90
89
|
- ext/quickjsrb/vendor/polyfill-url.min.js
|
|
91
90
|
- lib/quickjs.rb
|
|
92
91
|
- lib/quickjs/crypto_key.rb
|
|
93
92
|
- lib/quickjs/function.rb
|
|
93
|
+
- lib/quickjs/polyfills.rb
|
|
94
|
+
- lib/quickjs/polyfills/intl-en.min.js
|
|
95
|
+
- lib/quickjs/polyfills/intl.rb
|
|
94
96
|
- lib/quickjs/runnable.rb
|
|
95
97
|
- lib/quickjs/subtle_crypto.rb
|
|
96
98
|
- lib/quickjs/version.rb
|
|
File without changes
|