quickjs 0.15.0 → 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: e13c4664328e497bb8c2179807e8c110c4709b6df84a9a60f888e5a822075de2
4
- data.tar.gz: c43d43f3c0b928798545ecf57bf9956df1912e3867220915dd5b13c8aeb0031f
3
+ metadata.gz: e8f0513a5e0a54c24bdc8c20000f8cf9d1611e394683a710a2260ebedb57913a
4
+ data.tar.gz: 335ab02e13a0b8b137342f5895bc821641ac33d6f8370e05b9c24f0ca3ce7416
5
5
  SHA512:
6
- metadata.gz: 581463e6df8d5559f9aa1f91e4512b68f745942d771b2246202d3137a378876c6662db800381abd4dc065b4a6a8acb03f247feda4b78aa9ce1fc31cdd6ed6836
7
- data.tar.gz: cd4f64a7d98a75c5e388b99e1cdbd44c6ad4cfc4cd96f8e23259f12aa8cbe18f8c580ad41633cb65c9f77a6f9732fbb6e042b6805cc034a0c4c060f4ecdb3635
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
@@ -40,6 +40,13 @@ Quickjs.eval_code(code,
40
40
  )
41
41
  ```
42
42
 
43
+ #### Filename
44
+
45
+ ```rb
46
+ # Label shown in JS stack traces (default: "<code>")
47
+ Quickjs.eval_code(code, filename: 'my_script.js')
48
+ ```
49
+
43
50
  #### Timeout
44
51
 
45
52
  ```rb
@@ -122,6 +129,26 @@ vm.import('DefaultExport', from: File.read('exports.esm.js'))
122
129
  vm.import('* as all', from: File.read('exports.esm.js'))
123
130
  ```
124
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
+
125
152
  #### `Quickjs::VM#define_function`: 💎 Define a global function for JS by Ruby
126
153
 
127
154
  ```rb
@@ -133,6 +160,23 @@ end
133
160
  vm.eval_code("greetingTo('Rick')") #=> 'Hello! Rick'
134
161
  ```
135
162
 
163
+ Pass an `Array` as the name to register the function on an existing JS object (the last element is the method name; preceding elements are the object path):
164
+
165
+ ```rb
166
+ vm = Quickjs::VM.new
167
+ vm.eval_code("const myLib = {}")
168
+ vm.define_function(["myLib", "greetingTo"]) { |name| "Hello, #{name}!" }
169
+
170
+ vm.eval_code("myLib.greetingTo('Rick')") #=> 'Hello! Rick'
171
+
172
+ # Deeply nested
173
+ vm.eval_code("const a = { b: { c: {} } }")
174
+ vm.define_function(["a", "b", "c", "double"]) { |x| x * 2 }
175
+ vm.eval_code("a.b.c.double(21)") #=> 42
176
+ ```
177
+
178
+ `define_function` returns the registered name as a `Symbol` (or an `Array` of `Symbol`s for array paths).
179
+
136
180
  A Ruby exception raised inside the block is catchable in JS as an `Error`, and propagates back to Ruby as the original exception type if uncaught in JS.
137
181
 
138
182
  ```rb
@@ -177,14 +221,20 @@ vm.eval_code('console.log("hello", 42)')
177
221
  | `string` | ↔ | `String` | |
178
222
  | `true` / `false` | ↔ | `true` / `false` | |
179
223
  | `null` | ↔ | `nil` | |
180
- | `Array` | ↔ | `Array` | via JSON |
181
- | `Object` | ↔ | `Hash` | via JSON |
224
+ | `Array` | ↔ | `Array` | recursively converted |
225
+ | `Object` | ↔ | `Hash` | recursively converted; keys are always `String` |
226
+ | `function` | → | `Quickjs::Function` — `.source`, `.call(*args, on:)` | |
182
227
  | `undefined` | → | `Quickjs::Value::UNDEFINED` | |
183
228
  | `NaN` | → | `Quickjs::Value::NAN` | |
184
229
  | `Blob` | → | `Quickjs::Blob` — `.size`, `.type`, `.content` | requires `POLYFILL_FILE` |
185
230
  | `File` | → | `Quickjs::File` — `.name`, `.last_modified` + Blob attrs | requires `POLYFILL_FILE` |
186
231
  | `File` proxy | ← | `::File` | requires `POLYFILL_FILE`; applies to `define_function` return values |
187
232
 
233
+ ## Acknowledgements
234
+
235
+ - [@ursm](https://github.com/ursm) — for continuous contributions improving performance and developer experience
236
+ - [@persona-id](https://github.com/persona-id) — for providing real-world use cases that shape the direction of this project
237
+
188
238
  ## License
189
239
 
190
240
  - `ext/quickjsrb/quickjs`
@@ -57,8 +57,21 @@ typedef struct
57
57
  static int rb_hash_entry_to_js(VALUE r_key, VALUE r_val, VALUE extra)
58
58
  {
59
59
  RbHashToJsArg *arg = (RbHashToJsArg *)extra;
60
- VALUE r_key_str = rb_funcall(r_key, rb_intern("to_s"), 0);
61
- JS_SetPropertyStr(arg->ctx, arg->j_obj, StringValueCStr(r_key_str), to_js_value(arg->ctx, r_val));
60
+ const char *key_cstr;
61
+ if (SYMBOL_P(r_key))
62
+ {
63
+ key_cstr = rb_id2name(SYM2ID(r_key));
64
+ }
65
+ else if (RB_TYPE_P(r_key, T_STRING))
66
+ {
67
+ key_cstr = StringValueCStr(r_key);
68
+ }
69
+ else
70
+ {
71
+ VALUE r_key_str = rb_funcall(r_key, rb_intern("to_s"), 0);
72
+ key_cstr = StringValueCStr(r_key_str);
73
+ }
74
+ JS_SetPropertyStr(arg->ctx, arg->j_obj, key_cstr, to_js_value(arg->ctx, r_val));
62
75
  return ST_CONTINUE;
63
76
  }
64
77
 
@@ -69,26 +82,23 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
69
82
  case T_NIL:
70
83
  return JS_NULL;
71
84
  case T_FIXNUM:
85
+ return JS_NewInt64(ctx, NUM2LL(r_value));
72
86
  case T_FLOAT:
87
+ return JS_NewFloat64(ctx, NUM2DBL(r_value));
88
+ case T_BIGNUM:
73
89
  {
74
90
  VALUE r_str = rb_funcall(r_value, rb_intern("to_s"), 0);
75
- char *str = StringValueCStr(r_str);
91
+ JSValue j_str = JS_NewStringLen(ctx, RSTRING_PTR(r_str), RSTRING_LEN(r_str));
76
92
  JSValue j_global = JS_GetGlobalObject(ctx);
77
93
  JSValue j_numberClass = JS_GetPropertyStr(ctx, j_global, "Number");
78
- JSValue j_str = JS_NewString(ctx, str);
79
- JSValue j_stringified = JS_Call(ctx, j_numberClass, JS_UNDEFINED, 1, (JSValueConst *)&j_str);
80
- JS_FreeValue(ctx, j_global);
81
- JS_FreeValue(ctx, j_numberClass);
94
+ JSValue j_num = JS_Call(ctx, j_numberClass, JS_UNDEFINED, 1, (JSValueConst *)&j_str);
82
95
  JS_FreeValue(ctx, j_str);
83
-
84
- return j_stringified;
96
+ JS_FreeValue(ctx, j_numberClass);
97
+ JS_FreeValue(ctx, j_global);
98
+ return j_num;
85
99
  }
86
100
  case T_STRING:
87
- {
88
- char *str = StringValueCStr(r_value);
89
-
90
- return JS_NewString(ctx, str);
91
- }
101
+ return JS_NewStringLen(ctx, RSTRING_PTR(r_value), RSTRING_LEN(r_value));
92
102
  case T_SYMBOL:
93
103
  {
94
104
  if (r_value == QUICKJSRB_SYM(undefinedId))
@@ -100,10 +110,8 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
100
110
  JS_FreeValue(ctx, j_global);
101
111
  return j_nan;
102
112
  }
103
- VALUE r_str = rb_funcall(r_value, rb_intern("to_s"), 0);
104
- char *str = StringValueCStr(r_str);
105
-
106
- return JS_NewString(ctx, str);
113
+ const char *name = rb_id2name(SYM2ID(r_value));
114
+ return JS_NewString(ctx, name);
107
115
  }
108
116
  case T_TRUE:
109
117
  return JS_TRUE;
@@ -134,10 +142,7 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
134
142
  if (!JS_IsUndefined(data->j_file_proxy_creator))
135
143
  return quickjsrb_file_to_js(ctx, r_value);
136
144
  }
137
- if (TYPE(r_value) == T_OBJECT && RTEST(rb_funcall(
138
- r_value,
139
- rb_intern("is_a?"),
140
- 1, rb_const_get(rb_cClass, rb_intern("Exception")))))
145
+ if (rb_obj_is_kind_of(r_value, rb_eException))
141
146
  {
142
147
  return j_error_from_ruby_error(ctx, r_value);
143
148
  }
@@ -266,16 +271,13 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
266
271
  }
267
272
  case JS_TAG_STRING:
268
273
  {
269
- int couldntParse;
270
- VALUE r_result = rb_protect(r_try_json_parse, to_r_json(ctx, j_val), &couldntParse);
271
- if (couldntParse)
272
- {
274
+ size_t len;
275
+ const char *str = JS_ToCStringLen(ctx, &len, j_val);
276
+ if (str == NULL)
273
277
  return Qnil;
274
- }
275
- else
276
- {
277
- return r_result;
278
- }
278
+ VALUE r_str = rb_utf8_str_new(str, (long)len);
279
+ JS_FreeCString(ctx, str);
280
+ return r_str;
279
281
  }
280
282
  case JS_TAG_OBJECT:
281
283
  {
@@ -468,6 +470,59 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
468
470
  }
469
471
  }
470
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
+
471
526
  static VALUE r_try_call_proc(VALUE r_try_args)
472
527
  {
473
528
  return rb_funcall(
@@ -482,16 +537,19 @@ static VALUE r_try_call_proc(VALUE r_try_args)
482
537
 
483
538
  static JSValue js_quickjsrb_call_global(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv, int _magic, JSValue *func_data)
484
539
  {
485
- const char *funcName = JS_ToCString(ctx, func_data[0]);
540
+ // func_data[0] holds the Ruby Symbol ID for the defined function (stored by
541
+ // vm_m_defineGlobalFunction). Looking up by ID avoids a JS_ToCString +
542
+ // rb_intern round-trip on every call.
543
+ int64_t key_id;
544
+ JS_ToInt64(ctx, &key_id, func_data[0]);
486
545
 
487
546
  VMData *data = JS_GetContextOpaque(ctx);
488
- VALUE r_proc = rb_hash_aref(data->defined_functions, ID2SYM(rb_intern(funcName)));
547
+ VALUE r_proc = rb_hash_aref(data->defined_functions, ID2SYM((ID)key_id));
489
548
  // Shouldn't happen
490
549
  if (r_proc == Qnil)
491
550
  {
492
- return JS_ThrowReferenceError(ctx, "Proc `%s` is not defined", funcName); // TODO: Free funcnName
551
+ return JS_ThrowReferenceError(ctx, "Proc is not defined");
493
552
  }
494
- JS_FreeCString(ctx, funcName);
495
553
 
496
554
  VALUE r_call_args = rb_ary_new();
497
555
  rb_ary_push(r_call_args, r_proc);
@@ -727,7 +785,7 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
727
785
  JS_SetMemoryLimit(runtime, NUM2UINT(r_memory_limit));
728
786
  JS_SetMaxStackSize(runtime, NUM2UINT(r_max_stack_size));
729
787
 
730
- 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);
731
789
  js_std_init_handlers(runtime);
732
790
 
733
791
  JSValue j_global = JS_GetGlobalObject(data->context);
@@ -842,22 +900,36 @@ static VALUE to_rb_return_value(JSContext *ctx, JSValue j_val)
842
900
  return result;
843
901
  }
844
902
 
845
- static VALUE vm_m_evalCode(VALUE r_self, VALUE r_code)
903
+ static VALUE vm_m_evalCode(int argc, VALUE *argv, VALUE r_self)
846
904
  {
847
905
  VMData *data;
848
906
  TypedData_Get_Struct(r_self, VMData, &vm_type, data);
849
907
 
908
+ VALUE r_code, r_opts;
909
+ rb_scan_args(argc, argv, "1:", &r_code, &r_opts);
910
+
850
911
  if (!RB_TYPE_P(r_code, T_STRING))
851
912
  {
852
913
  VALUE r_code_class = rb_class_name(CLASS_OF(r_code));
853
914
  rb_raise(rb_eTypeError, "JavaScript code must be a String, got %s", StringValueCStr(r_code_class));
854
915
  }
855
916
 
917
+ const char *filename = "<code>";
918
+ if (!NIL_P(r_opts))
919
+ {
920
+ VALUE r_filename = rb_hash_aref(r_opts, ID2SYM(rb_intern("filename")));
921
+ if (!NIL_P(r_filename))
922
+ {
923
+ Check_Type(r_filename, T_STRING);
924
+ filename = StringValueCStr(r_filename);
925
+ }
926
+ }
927
+
856
928
  clock_gettime(CLOCK_MONOTONIC, &data->eval_time->started_at);
857
929
  JS_SetInterruptHandler(JS_GetRuntime(data->context), interrupt_handler, data->eval_time);
858
930
 
859
- char *code = StringValueCStr(r_code);
860
- JSValue j_codeResult = JS_Eval(data->context, code, strlen(code), "<code>", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_ASYNC);
931
+ StringValue(r_code);
932
+ JSValue j_codeResult = JS_Eval(data->context, RSTRING_PTR(r_code), RSTRING_LEN(r_code), filename, JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_ASYNC);
861
933
  JSValue j_awaitedResult = js_std_await(data->context, j_codeResult); // This frees j_codeResult
862
934
  // JS_EVAL_FLAG_ASYNC wraps the result in {value, done} — extract the actual value
863
935
  // Free j_awaitedResult before to_rb_return_value because it may raise (longjmp), which would skip cleanup
@@ -904,7 +976,7 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
904
976
  char *funcName = StringValueCStr(r_func_seg_str);
905
977
 
906
978
  JSValueConst ruby_data[2];
907
- ruby_data[0] = JS_NewString(data->context, StringValueCStr(r_key_str));
979
+ ruby_data[0] = JS_NewInt64(data->context, (int64_t)SYM2ID(r_key_sym));
908
980
  ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
909
981
 
910
982
  // Resolve the parent object to attach the function to.
@@ -969,7 +1041,7 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
969
1041
  char *funcName = StringValueCStr(r_name_str);
970
1042
 
971
1043
  JSValueConst ruby_data[2];
972
- ruby_data[0] = JS_NewString(data->context, funcName);
1044
+ ruby_data[0] = JS_NewInt64(data->context, (int64_t)SYM2ID(r_name_sym));
973
1045
  ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
974
1046
 
975
1047
  JSValue j_global = JS_GetGlobalObject(data->context);
@@ -1124,6 +1196,25 @@ static VALUE vm_m_callGlobalFunction(int argc, VALUE *argv, VALUE r_self)
1124
1196
  return to_rb_return_value(data->context, js_std_await(data->context, j_result));
1125
1197
  }
1126
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
+
1127
1218
  static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
1128
1219
  {
1129
1220
  VALUE r_import_string, r_opts;
@@ -1131,7 +1222,8 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
1131
1222
  if (NIL_P(r_opts))
1132
1223
  r_opts = rb_hash_new();
1133
1224
  VALUE r_from = rb_hash_aref(r_opts, ID2SYM(rb_intern("from")));
1134
- 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))
1135
1227
  {
1136
1228
  VALUE r_error_message = rb_str_new2("missing import source");
1137
1229
  rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_ROOT_RUNTIME_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
@@ -1142,16 +1234,24 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
1142
1234
  VMData *data;
1143
1235
  TypedData_Get_Struct(r_self, VMData, &vm_type, data);
1144
1236
 
1145
- char *filename = random_string();
1146
- char *source = StringValueCStr(r_from);
1147
- JSValue module = JS_Eval(data->context, source, strlen(source), filename, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
1148
- if (JS_IsException(module))
1237
+ char *filename;
1238
+ if (!NIL_P(r_filename))
1149
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);
1150
1253
  JS_FreeValue(data->context, module);
1151
- return to_rb_value(data->context, module);
1152
1254
  }
1153
- js_module_set_import_meta(data->context, module, TRUE, FALSE);
1154
- JS_FreeValue(data->context, module);
1155
1255
 
1156
1256
  VALUE r_import_settings = rb_funcall(
1157
1257
  rb_const_get(rb_cClass, rb_intern("Quickjs")),
@@ -1196,10 +1296,12 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
1196
1296
  VALUE r_class_vm = rb_define_class_under(r_module_quickjs, "VM", rb_cObject);
1197
1297
  rb_define_alloc_func(r_class_vm, vm_alloc);
1198
1298
  rb_define_method(r_class_vm, "initialize", vm_m_initialize, -1);
1199
- rb_define_method(r_class_vm, "eval_code", vm_m_evalCode, 1);
1299
+ rb_define_method(r_class_vm, "eval_code", vm_m_evalCode, -1);
1200
1300
  rb_define_method(r_class_vm, "call", vm_m_callGlobalFunction, -1);
1201
1301
  rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
1202
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);
1203
1305
  rb_define_method(r_class_vm, "on_log", vm_m_on_log, 0);
1204
1306
  r_define_log_class(r_class_vm);
1205
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.0"
4
+ VERSION = "0.16.0"
5
5
  end
data/lib/quickjs.rb CHANGED
@@ -18,8 +18,10 @@ module Quickjs
18
18
  end
19
19
 
20
20
  def eval_code(code, overwrite_opts = {})
21
+ eval_opts = {}
22
+ eval_opts[:filename] = overwrite_opts.delete(:filename) if overwrite_opts.key?(:filename)
21
23
  vm = Quickjs::VM.new(**overwrite_opts)
22
- res = vm.eval_code(code)
24
+ res = vm.eval_code(code, **eval_opts)
23
25
  vm = nil
24
26
  res
25
27
  end
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "quickjs-rb-polyfills",
3
- "version": "0.15.0",
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.0",
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.0",
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
@@ -21,7 +21,7 @@ module Quickjs
21
21
  class VM
22
22
  def initialize: (?features: Array[Symbol], ?memory_limit: Integer, ?max_stack_size: Integer, ?timeout_msec: Integer) -> void
23
23
 
24
- def eval_code: (String code) -> untyped
24
+ def eval_code: (String code, ?filename: String) -> untyped
25
25
 
26
26
  def call: (String | Symbol name, *untyped args) -> untyped
27
27
 
@@ -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.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - hmsk