quickjs 0.15.2 → 0.16.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8cfe5ee5e8eef08251b2d90c11763cbbfa4c0a5a64e83d201f0e15c576f1c597
4
- data.tar.gz: 9a59bb09ba65423d6f09a8afb0e0aa2e5c91b89155fbf6c5b1f15065cefbdbd3
3
+ metadata.gz: e8f0513a5e0a54c24bdc8c20000f8cf9d1611e394683a710a2260ebedb57913a
4
+ data.tar.gz: 335ab02e13a0b8b137342f5895bc821641ac33d6f8370e05b9c24f0ca3ce7416
5
5
  SHA512:
6
- metadata.gz: 0ab5cb1e220ffcb01350c1ee43e7167710a5cda902945fee155e292352d7c10bd309959c8816a26bcb54fe0ac8291f910156c8d3ad6f64397b71c2e706a2cac0
7
- data.tar.gz: acfbac2d942837185453f203debc9280f5a77529307e6ca07f222a26b9377b93fad1531a4db3ccd3e85a18cd264536f73542d92696726ff4d4ed2f3db5c12478
6
+ metadata.gz: f6e975069d7b77adf0d2e4ca7d6459c6499e1465155cae563ff3fb87d7f1f8aefe9f3b26191d9736a2c398ee13df48d5ded389b17f54aec56dc2bf4e4ac580a8
7
+ data.tar.gz: b1341d5c2e0ba51e6891752bc6c203fe7c2bfc8e1d76beafaf93129f3fb0bb600c64e786beec42ec6a9cc95336562de739d32f02711422b1f9930dd4b57cbb93
data/CLAUDE.md CHANGED
@@ -55,6 +55,14 @@ Tests use minitest with `describe`/`it` blocks. Key test files:
55
55
  - `test/quickjs_test.rb` — Main test suite (value conversion, errors, VM features, ESM imports, function definitions)
56
56
  - `test/quickjs_polyfill_test.rb` — Intl polyfill tests
57
57
 
58
+ ## Release Process
59
+
60
+ 1. On `main`, run `bundle exec rake polyfills:build` — rebuilds polyfill bundles and syncs `polyfills/package.json` version to match the gem version
61
+ 2. Bump `lib/quickjs/version.rb` and `Gemfile.lock` to the new version
62
+ 3. Commit `lib/quickjs/version.rb`, `Gemfile.lock`, `polyfills/package.json`, `polyfills/package-lock.json` as `"prepare vX.Y.Z"` — do NOT push; `rake release` handles that
63
+ 4. Run `bundle exec rake release` — tags, pushes to GitHub, and publishes to RubyGems (human step; do not run this as Claude)
64
+ 5. Create a GitHub release via `gh release create` with notes following the pattern of previous releases
65
+
58
66
  ## Build Notes
59
67
 
60
68
  - `extconf.rb` compiles with `-DNDEBUG` to avoid conflicts with Ruby 4.0 GC assertions
data/README.md CHANGED
@@ -129,6 +129,26 @@ vm.import('DefaultExport', from: File.read('exports.esm.js'))
129
129
  vm.import('* as all', from: File.read('exports.esm.js'))
130
130
  ```
131
131
 
132
+ #### `Quickjs::VM#module_loader=`: 🧩 Resolve `import` specifiers from Ruby
133
+
134
+ By default, `import` specifiers that aren't already loaded fall through to QuickJS's filesystem loader. Set a `module_loader` Proc to resolve specifiers in-memory instead — useful when the source code lives in a database, an importmap, or a virtual filesystem.
135
+
136
+ ```rb
137
+ vm = Quickjs::VM.new
138
+ modules = {
139
+ 'a' => "import { b } from 'b'; export const a = () => `a-${b()}`;",
140
+ 'b' => "export const b = () => 'b-result';"
141
+ }
142
+ vm.module_loader = ->(name) { modules[name] }
143
+
144
+ vm.import(['a'], filename: 'a')
145
+ vm.eval_code('a()') #=> 'a-b-result'
146
+ ```
147
+
148
+ The Proc receives the (already normalized) module specifier and returns the module source as a `String`, or `nil` to signal "not found" (which raises `Quickjs::ReferenceError` on the JS side). Pass `nil` to clear a previously set loader.
149
+
150
+ When `module_loader=` is set, pass `filename:` to `import` instead of `from:` to resolve a named specifier directly through the loader — no inline bridge source needed.
151
+
132
152
  #### `Quickjs::VM#define_function`: 💎 Define a global function for JS by Ruby
133
153
 
134
154
  ```rb
@@ -270,11 +270,7 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
270
270
  return JS_ToBool(ctx, j_val) > 0 ? Qtrue : Qfalse;
271
271
  }
272
272
  case JS_TAG_STRING:
273
- case JS_TAG_STRING_ROPE:
274
273
  {
275
- // QuickJS keeps long `s += chunk` chains as a rope (JS_TAG_STRING_ROPE)
276
- // until something materialises them. JS_ToCStringLen flattens ropes
277
- // transparently, so both tags share the same conversion path.
278
274
  size_t len;
279
275
  const char *str = JS_ToCStringLen(ctx, &len, j_val);
280
276
  if (str == NULL)
@@ -474,6 +470,59 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
474
470
  }
475
471
  }
476
472
 
473
+ struct module_loader_call_args
474
+ {
475
+ VALUE proc;
476
+ VALUE r_module_name;
477
+ };
478
+
479
+ static VALUE r_module_loader_call(VALUE r_args_val)
480
+ {
481
+ struct module_loader_call_args *args = (struct module_loader_call_args *)r_args_val;
482
+ return rb_funcall(args->proc, rb_intern("call"), 1, args->r_module_name);
483
+ }
484
+
485
+ static JSModuleDef *quickjsrb_module_loader(JSContext *ctx, const char *module_name, void *opaque, JSValueConst attributes)
486
+ {
487
+ VMData *data = JS_GetContextOpaque(ctx);
488
+ if (NIL_P(data->module_loader))
489
+ return js_module_loader(ctx, module_name, opaque, attributes);
490
+
491
+ struct module_loader_call_args args = {data->module_loader, rb_str_new_cstr(module_name)};
492
+ int state;
493
+ VALUE r_source = rb_protect(r_module_loader_call, (VALUE)&args, &state);
494
+ if (state)
495
+ {
496
+ VALUE r_error = rb_errinfo();
497
+ rb_set_errinfo(Qnil);
498
+ JSValue j_error = j_error_from_ruby_error(ctx, r_error);
499
+ JS_Throw(ctx, j_error);
500
+ return NULL;
501
+ }
502
+
503
+ if (NIL_P(r_source) || r_source == Qfalse)
504
+ {
505
+ JS_ThrowReferenceError(ctx, "module loader returned no source for '%s'", module_name);
506
+ return NULL;
507
+ }
508
+
509
+ if (!RB_TYPE_P(r_source, T_STRING))
510
+ {
511
+ JS_ThrowTypeError(ctx, "module loader must return a String or nil, got %s", rb_obj_classname(r_source));
512
+ return NULL;
513
+ }
514
+
515
+ JSValue j_func = JS_Eval(ctx, RSTRING_PTR(r_source), RSTRING_LEN(r_source), module_name,
516
+ JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
517
+ if (JS_IsException(j_func))
518
+ return NULL;
519
+
520
+ js_module_set_import_meta(ctx, j_func, FALSE, FALSE);
521
+ JSModuleDef *m = JS_VALUE_GET_PTR(j_func);
522
+ JS_FreeValue(ctx, j_func);
523
+ return m;
524
+ }
525
+
477
526
  static VALUE r_try_call_proc(VALUE r_try_args)
478
527
  {
479
528
  return rb_funcall(
@@ -736,7 +785,7 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
736
785
  JS_SetMemoryLimit(runtime, NUM2UINT(r_memory_limit));
737
786
  JS_SetMaxStackSize(runtime, NUM2UINT(r_max_stack_size));
738
787
 
739
- JS_SetModuleLoaderFunc2(runtime, NULL, js_module_loader, js_module_check_attributes, NULL);
788
+ JS_SetModuleLoaderFunc2(runtime, NULL, quickjsrb_module_loader, js_module_check_attributes, NULL);
740
789
  js_std_init_handlers(runtime);
741
790
 
742
791
  JSValue j_global = JS_GetGlobalObject(data->context);
@@ -1147,6 +1196,25 @@ static VALUE vm_m_callGlobalFunction(int argc, VALUE *argv, VALUE r_self)
1147
1196
  return to_rb_return_value(data->context, js_std_await(data->context, j_result));
1148
1197
  }
1149
1198
 
1199
+ static VALUE vm_m_set_module_loader(VALUE r_self, VALUE r_loader)
1200
+ {
1201
+ VMData *data;
1202
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
1203
+
1204
+ if (!NIL_P(r_loader) && !rb_obj_is_kind_of(r_loader, rb_cProc))
1205
+ rb_raise(rb_eTypeError, "module_loader must be a Proc or nil");
1206
+
1207
+ data->module_loader = r_loader;
1208
+ return r_loader;
1209
+ }
1210
+
1211
+ static VALUE vm_m_get_module_loader(VALUE r_self)
1212
+ {
1213
+ VMData *data;
1214
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
1215
+ return data->module_loader;
1216
+ }
1217
+
1150
1218
  static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
1151
1219
  {
1152
1220
  VALUE r_import_string, r_opts;
@@ -1154,7 +1222,8 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
1154
1222
  if (NIL_P(r_opts))
1155
1223
  r_opts = rb_hash_new();
1156
1224
  VALUE r_from = rb_hash_aref(r_opts, ID2SYM(rb_intern("from")));
1157
- if (NIL_P(r_from))
1225
+ VALUE r_filename = rb_hash_aref(r_opts, ID2SYM(rb_intern("filename")));
1226
+ if (NIL_P(r_from) && NIL_P(r_filename))
1158
1227
  {
1159
1228
  VALUE r_error_message = rb_str_new2("missing import source");
1160
1229
  rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_ROOT_RUNTIME_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
@@ -1165,16 +1234,24 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
1165
1234
  VMData *data;
1166
1235
  TypedData_Get_Struct(r_self, VMData, &vm_type, data);
1167
1236
 
1168
- char *filename = random_string();
1169
- char *source = StringValueCStr(r_from);
1170
- JSValue module = JS_Eval(data->context, source, strlen(source), filename, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
1171
- if (JS_IsException(module))
1237
+ char *filename;
1238
+ if (!NIL_P(r_filename))
1172
1239
  {
1240
+ filename = StringValueCStr(r_filename);
1241
+ }
1242
+ else
1243
+ {
1244
+ filename = random_string();
1245
+ char *source = StringValueCStr(r_from);
1246
+ JSValue module = JS_Eval(data->context, source, strlen(source), filename, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
1247
+ if (JS_IsException(module))
1248
+ {
1249
+ JS_FreeValue(data->context, module);
1250
+ return to_rb_value(data->context, module);
1251
+ }
1252
+ js_module_set_import_meta(data->context, module, TRUE, FALSE);
1173
1253
  JS_FreeValue(data->context, module);
1174
- return to_rb_value(data->context, module);
1175
1254
  }
1176
- js_module_set_import_meta(data->context, module, TRUE, FALSE);
1177
- JS_FreeValue(data->context, module);
1178
1255
 
1179
1256
  VALUE r_import_settings = rb_funcall(
1180
1257
  rb_const_get(rb_cClass, rb_intern("Quickjs")),
@@ -1223,6 +1300,8 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
1223
1300
  rb_define_method(r_class_vm, "call", vm_m_callGlobalFunction, -1);
1224
1301
  rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
1225
1302
  rb_define_method(r_class_vm, "import", vm_m_import, -1);
1303
+ rb_define_method(r_class_vm, "module_loader", vm_m_get_module_loader, 0);
1304
+ rb_define_method(r_class_vm, "module_loader=", vm_m_set_module_loader, 1);
1226
1305
  rb_define_method(r_class_vm, "on_log", vm_m_on_log, 0);
1227
1306
  r_define_log_class(r_class_vm);
1228
1307
  }
@@ -54,6 +54,7 @@ typedef struct VMData
54
54
  struct EvalTime *eval_time;
55
55
  VALUE log_listener;
56
56
  VALUE alive_objects;
57
+ VALUE module_loader;
57
58
  JSValue j_file_proxy_creator;
58
59
  } VMData;
59
60
 
@@ -85,6 +86,7 @@ static void vm_mark(void *ptr)
85
86
  rb_gc_mark_movable(data->defined_functions);
86
87
  rb_gc_mark_movable(data->log_listener);
87
88
  rb_gc_mark_movable(data->alive_objects);
89
+ rb_gc_mark_movable(data->module_loader);
88
90
  }
89
91
 
90
92
  static void vm_compact(void *ptr)
@@ -93,6 +95,7 @@ static void vm_compact(void *ptr)
93
95
  data->defined_functions = rb_gc_location(data->defined_functions);
94
96
  data->log_listener = rb_gc_location(data->log_listener);
95
97
  data->alive_objects = rb_gc_location(data->alive_objects);
98
+ data->module_loader = rb_gc_location(data->module_loader);
96
99
  }
97
100
 
98
101
  static const rb_data_type_t vm_type = {
@@ -113,6 +116,7 @@ static VALUE vm_alloc(VALUE r_self)
113
116
  data->defined_functions = rb_hash_new();
114
117
  data->log_listener = Qnil;
115
118
  data->alive_objects = rb_hash_new();
119
+ data->module_loader = Qnil;
116
120
  data->j_file_proxy_creator = JS_UNDEFINED;
117
121
 
118
122
  EvalTime *eval_time = malloc(sizeof(EvalTime));
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quickjs
4
- VERSION = "0.15.2"
4
+ VERSION = "0.16.0"
5
5
  end
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "quickjs-rb-polyfills",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "quickjs-rb-polyfills",
9
- "version": "0.15.1",
9
+ "version": "0.16.0",
10
10
  "dependencies": {
11
11
  "@formatjs/intl-datetimeformat": "^7.3.1",
12
12
  "@formatjs/intl-getcanonicallocales": "^3.2.2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quickjs-rb-polyfills",
3
- "version": "0.15.2",
3
+ "version": "0.16.0",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "build": "rolldown -c rolldown.config.mjs",
data/sig/quickjs.rbs CHANGED
@@ -29,6 +29,11 @@ module Quickjs
29
29
  | (Array[String | Symbol] path, *Symbol flags) { (*untyped) -> untyped } -> Array[Symbol]
30
30
 
31
31
  def import: (String | Array[String] | Hash[Symbol, String] imported, from: String, ?code_to_expose: String?) -> true
32
+ | (String | Array[String] | Hash[Symbol, String] imported, filename: String) -> true
33
+
34
+ def module_loader: () -> (^(String) -> String? | nil)
35
+
36
+ def module_loader=: (^(String) -> String? | nil) -> (^(String) -> String? | nil)
32
37
 
33
38
  def on_log: () { (Log) -> void } -> nil
34
39
 
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.15.2
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - hmsk