quickjs 0.15.1 → 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: 54fe50175028245be99548bdb43a1884ceb956f77a999fca6c4cdf9ea9085a13
4
- data.tar.gz: ddbf8d46cfec89db687899ec083e7ef2859ebd4ff9d711c9a18801d15c8d4ad0
3
+ metadata.gz: e8f0513a5e0a54c24bdc8c20000f8cf9d1611e394683a710a2260ebedb57913a
4
+ data.tar.gz: 335ab02e13a0b8b137342f5895bc821641ac33d6f8370e05b9c24f0ca3ce7416
5
5
  SHA512:
6
- metadata.gz: ce86220556d6e2bbf06f2da42227876f972a991e4beca36ecce89efcfc84f11ee6d62172813442368af11632f33f970db97535eab435f3fa6046be955c98529d
7
- data.tar.gz: 255c23efa72230afbac3760e1cd8efd8ba29038263371ed4a4bc591dfbc58da9e48e2524bde82151cb8b10d14cda87255170fe0c46377efec8cb114ea8e9a855
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
@@ -470,6 +470,59 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
470
470
  }
471
471
  }
472
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
+
473
526
  static VALUE r_try_call_proc(VALUE r_try_args)
474
527
  {
475
528
  return rb_funcall(
@@ -732,7 +785,7 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
732
785
  JS_SetMemoryLimit(runtime, NUM2UINT(r_memory_limit));
733
786
  JS_SetMaxStackSize(runtime, NUM2UINT(r_max_stack_size));
734
787
 
735
- 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);
736
789
  js_std_init_handlers(runtime);
737
790
 
738
791
  JSValue j_global = JS_GetGlobalObject(data->context);
@@ -1143,6 +1196,25 @@ static VALUE vm_m_callGlobalFunction(int argc, VALUE *argv, VALUE r_self)
1143
1196
  return to_rb_return_value(data->context, js_std_await(data->context, j_result));
1144
1197
  }
1145
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
+
1146
1218
  static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
1147
1219
  {
1148
1220
  VALUE r_import_string, r_opts;
@@ -1150,7 +1222,8 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
1150
1222
  if (NIL_P(r_opts))
1151
1223
  r_opts = rb_hash_new();
1152
1224
  VALUE r_from = rb_hash_aref(r_opts, ID2SYM(rb_intern("from")));
1153
- 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))
1154
1227
  {
1155
1228
  VALUE r_error_message = rb_str_new2("missing import source");
1156
1229
  rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_ROOT_RUNTIME_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
@@ -1161,16 +1234,24 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
1161
1234
  VMData *data;
1162
1235
  TypedData_Get_Struct(r_self, VMData, &vm_type, data);
1163
1236
 
1164
- char *filename = random_string();
1165
- char *source = StringValueCStr(r_from);
1166
- JSValue module = JS_Eval(data->context, source, strlen(source), filename, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
1167
- if (JS_IsException(module))
1237
+ char *filename;
1238
+ if (!NIL_P(r_filename))
1168
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);
1169
1253
  JS_FreeValue(data->context, module);
1170
- return to_rb_value(data->context, module);
1171
1254
  }
1172
- js_module_set_import_meta(data->context, module, TRUE, FALSE);
1173
- JS_FreeValue(data->context, module);
1174
1255
 
1175
1256
  VALUE r_import_settings = rb_funcall(
1176
1257
  rb_const_get(rb_cClass, rb_intern("Quickjs")),
@@ -1219,6 +1300,8 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
1219
1300
  rb_define_method(r_class_vm, "call", vm_m_callGlobalFunction, -1);
1220
1301
  rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
1221
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);
1222
1305
  rb_define_method(r_class_vm, "on_log", vm_m_on_log, 0);
1223
1306
  r_define_log_class(r_class_vm);
1224
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.1"
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.1",
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.1
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - hmsk