quickjs 0.15.2 → 0.16.1

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: ed97f8a3a6eb6b10b53cf9e3db69e0074cb962be7908c033c003c39a104ec239
4
+ data.tar.gz: cb75d33d9d098d79bfaba4f3ecb7f47ca03d2ee91c95c2a73f8fa2e5b49d1337
5
5
  SHA512:
6
- metadata.gz: 0ab5cb1e220ffcb01350c1ee43e7167710a5cda902945fee155e292352d7c10bd309959c8816a26bcb54fe0ac8291f910156c8d3ad6f64397b71c2e706a2cac0
7
- data.tar.gz: acfbac2d942837185453f203debc9280f5a77529307e6ca07f222a26b9377b93fad1531a4db3ccd3e85a18cd264536f73542d92696726ff4d4ed2f3db5c12478
6
+ metadata.gz: 3fb3817ae3bba2dae090a0a4906e3477c543fba0795d7763b33f66fa4409270a2148149d27a589907b8e4eae1eebc67d9be93d62feaa8f6f8475542e126c0489
7
+ data.tar.gz: 4645cf66429e2ef0ce2ec6b7f86f8a4ef8a210255d0ea7e3d7613b9974a4bca3b6a1374113fd19af4da69e8262c5f8ac5f6436e87f71c248fda23a863f73a376
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
@@ -474,6 +474,59 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
474
474
  }
475
475
  }
476
476
 
477
+ struct module_loader_call_args
478
+ {
479
+ VALUE proc;
480
+ VALUE r_module_name;
481
+ };
482
+
483
+ static VALUE r_module_loader_call(VALUE r_args_val)
484
+ {
485
+ struct module_loader_call_args *args = (struct module_loader_call_args *)r_args_val;
486
+ return rb_funcall(args->proc, rb_intern("call"), 1, args->r_module_name);
487
+ }
488
+
489
+ static JSModuleDef *quickjsrb_module_loader(JSContext *ctx, const char *module_name, void *opaque, JSValueConst attributes)
490
+ {
491
+ VMData *data = JS_GetContextOpaque(ctx);
492
+ if (NIL_P(data->module_loader))
493
+ return js_module_loader(ctx, module_name, opaque, attributes);
494
+
495
+ struct module_loader_call_args args = {data->module_loader, rb_str_new_cstr(module_name)};
496
+ int state;
497
+ VALUE r_source = rb_protect(r_module_loader_call, (VALUE)&args, &state);
498
+ if (state)
499
+ {
500
+ VALUE r_error = rb_errinfo();
501
+ rb_set_errinfo(Qnil);
502
+ JSValue j_error = j_error_from_ruby_error(ctx, r_error);
503
+ JS_Throw(ctx, j_error);
504
+ return NULL;
505
+ }
506
+
507
+ if (NIL_P(r_source) || r_source == Qfalse)
508
+ {
509
+ JS_ThrowReferenceError(ctx, "module loader returned no source for '%s'", module_name);
510
+ return NULL;
511
+ }
512
+
513
+ if (!RB_TYPE_P(r_source, T_STRING))
514
+ {
515
+ JS_ThrowTypeError(ctx, "module loader must return a String or nil, got %s", rb_obj_classname(r_source));
516
+ return NULL;
517
+ }
518
+
519
+ JSValue j_func = JS_Eval(ctx, RSTRING_PTR(r_source), RSTRING_LEN(r_source), module_name,
520
+ JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
521
+ if (JS_IsException(j_func))
522
+ return NULL;
523
+
524
+ js_module_set_import_meta(ctx, j_func, FALSE, FALSE);
525
+ JSModuleDef *m = JS_VALUE_GET_PTR(j_func);
526
+ JS_FreeValue(ctx, j_func);
527
+ return m;
528
+ }
529
+
477
530
  static VALUE r_try_call_proc(VALUE r_try_args)
478
531
  {
479
532
  return rb_funcall(
@@ -736,7 +789,7 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
736
789
  JS_SetMemoryLimit(runtime, NUM2UINT(r_memory_limit));
737
790
  JS_SetMaxStackSize(runtime, NUM2UINT(r_max_stack_size));
738
791
 
739
- JS_SetModuleLoaderFunc2(runtime, NULL, js_module_loader, js_module_check_attributes, NULL);
792
+ JS_SetModuleLoaderFunc2(runtime, NULL, quickjsrb_module_loader, js_module_check_attributes, NULL);
740
793
  js_std_init_handlers(runtime);
741
794
 
742
795
  JSValue j_global = JS_GetGlobalObject(data->context);
@@ -1147,6 +1200,25 @@ static VALUE vm_m_callGlobalFunction(int argc, VALUE *argv, VALUE r_self)
1147
1200
  return to_rb_return_value(data->context, js_std_await(data->context, j_result));
1148
1201
  }
1149
1202
 
1203
+ static VALUE vm_m_set_module_loader(VALUE r_self, VALUE r_loader)
1204
+ {
1205
+ VMData *data;
1206
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
1207
+
1208
+ if (!NIL_P(r_loader) && !rb_obj_is_kind_of(r_loader, rb_cProc))
1209
+ rb_raise(rb_eTypeError, "module_loader must be a Proc or nil");
1210
+
1211
+ data->module_loader = r_loader;
1212
+ return r_loader;
1213
+ }
1214
+
1215
+ static VALUE vm_m_get_module_loader(VALUE r_self)
1216
+ {
1217
+ VMData *data;
1218
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
1219
+ return data->module_loader;
1220
+ }
1221
+
1150
1222
  static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
1151
1223
  {
1152
1224
  VALUE r_import_string, r_opts;
@@ -1154,7 +1226,8 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
1154
1226
  if (NIL_P(r_opts))
1155
1227
  r_opts = rb_hash_new();
1156
1228
  VALUE r_from = rb_hash_aref(r_opts, ID2SYM(rb_intern("from")));
1157
- if (NIL_P(r_from))
1229
+ VALUE r_filename = rb_hash_aref(r_opts, ID2SYM(rb_intern("filename")));
1230
+ if (NIL_P(r_from) && NIL_P(r_filename))
1158
1231
  {
1159
1232
  VALUE r_error_message = rb_str_new2("missing import source");
1160
1233
  rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_ROOT_RUNTIME_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
@@ -1165,16 +1238,24 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
1165
1238
  VMData *data;
1166
1239
  TypedData_Get_Struct(r_self, VMData, &vm_type, data);
1167
1240
 
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))
1241
+ char *filename;
1242
+ if (!NIL_P(r_filename))
1172
1243
  {
1244
+ filename = StringValueCStr(r_filename);
1245
+ }
1246
+ else
1247
+ {
1248
+ filename = random_string();
1249
+ char *source = StringValueCStr(r_from);
1250
+ JSValue module = JS_Eval(data->context, source, strlen(source), filename, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
1251
+ if (JS_IsException(module))
1252
+ {
1253
+ JS_FreeValue(data->context, module);
1254
+ return to_rb_value(data->context, module);
1255
+ }
1256
+ js_module_set_import_meta(data->context, module, TRUE, FALSE);
1173
1257
  JS_FreeValue(data->context, module);
1174
- return to_rb_value(data->context, module);
1175
1258
  }
1176
- js_module_set_import_meta(data->context, module, TRUE, FALSE);
1177
- JS_FreeValue(data->context, module);
1178
1259
 
1179
1260
  VALUE r_import_settings = rb_funcall(
1180
1261
  rb_const_get(rb_cClass, rb_intern("Quickjs")),
@@ -1223,6 +1304,8 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
1223
1304
  rb_define_method(r_class_vm, "call", vm_m_callGlobalFunction, -1);
1224
1305
  rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
1225
1306
  rb_define_method(r_class_vm, "import", vm_m_import, -1);
1307
+ rb_define_method(r_class_vm, "module_loader", vm_m_get_module_loader, 0);
1308
+ rb_define_method(r_class_vm, "module_loader=", vm_m_set_module_loader, 1);
1226
1309
  rb_define_method(r_class_vm, "on_log", vm_m_on_log, 0);
1227
1310
  r_define_log_class(r_class_vm);
1228
1311
  }
@@ -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.1"
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.1",
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - hmsk