MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include "esm/loader.h"
4#include "esm/commonjs.h"
5#include "esm/library.h"
6#include "esm/remote.h"
7#include "esm/builtin_bundle.h"
8
9#include "modules/json.h"
10#include "modules/napi.h"
11#include "modules/uri.h"
12
13#include "errors.h"
14#include "gc/modules.h"
15#include "internal.h"
16#include "reactor.h"
17#include "utils.h"
18
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <sys/stat.h>
23#ifndef _WIN32
24#include <libgen.h>
25#include <unistd.h>
26#endif
27#include <uthash.h>
28#include <yyjson.h>
29
30typedef enum {
31 ESM_MODULE_KIND_CODE = 0,
32 ESM_MODULE_KIND_JSON,
33 ESM_MODULE_KIND_TEXT,
34 ESM_MODULE_KIND_IMAGE,
35 ESM_MODULE_KIND_NATIVE,
36 ESM_MODULE_KIND_URL,
37} esm_module_kind_t;
38
39typedef struct esm_module {
40 char *path;
41 char *cache_key;
42 char *resolved_path;
43 char *url_content;
44
45 size_t url_content_len;
46 const uint8_t *embedded_code;
47 size_t embedded_code_len;
48
49 ant_value_t namespace_obj;
50 ant_value_t default_export;
51 ant_value_t tla_promise;
52 UT_hash_handle hh;
53 esm_module_kind_t kind;
54 ant_module_format_t format;
55
56 bool is_loaded;
57 bool is_loading;
58 bool has_tla;
59} esm_module_t;
60
61typedef struct {
62 esm_module_t *modules;
63 int count;
64} esm_module_cache_t;
65
66typedef struct {
67 char *data;
68 size_t size;
69} esm_file_data_t;
70
71static esm_module_cache_t global_module_cache = {NULL, 0};
72static int esm_dynamic_import_depth = 0;
73
74static char *esm_resolve_node_module(const char *specifier, const char *base_path);
75static char *esm_canonicalize_path(const char *path);
76
77static char *esm_file_url_to_path(ant_t *js, const char *specifier) {
78 if (!specifier || strncmp(specifier, "file:", 5) != 0) return NULL;
79
80 const char *p = specifier + 5;
81 if (strncmp(p, "///", 3) == 0) p += 2;
82 else if (strncmp(p, "//localhost/", 12) == 0) p += 11;
83
84 if (*p == '\0') return NULL;
85
86 ant_value_t encoded = js_mkstr(js, p, strlen(p));
87 ant_value_t decoded = js_decodeURI(js, &encoded, 1);
88
89 size_t len = 0;
90 char *str = js_getstr(js, decoded, &len);
91
92 return str ? strndup(str, len) : NULL;
93}
94
95static char *esm_make_cache_key(const char *module_key) {
96 if (!module_key) return NULL;
97 if (esm_has_builtin_scheme(module_key)) return strdup(module_key);
98 if (esm_is_data_url(module_key)) return strdup(module_key);
99 if (esm_is_url(module_key)) return strdup(module_key);
100 return esm_canonicalize_path(module_key);
101}
102
103static char *esm_get_extension(const char *path) {
104 const char *dot = strrchr(path, '.');
105 const char *slash = strrchr(path, '/');
106
107 if (dot && (!slash || dot > slash)) {
108 return strdup(dot);
109 }
110 return strdup(".js");
111}
112
113static bool esm_is_relative_specifier(const char *specifier) {
114 return
115 strcmp(specifier, ".") == 0 ||
116 strcmp(specifier, "..") == 0 ||
117 strncmp(specifier, "./", 2) == 0 ||
118 strncmp(specifier, "../", 3) == 0;
119}
120
121static char *esm_try_resolve(const char *dir, const char *spec, const char *suffix) {
122 char path[PATH_MAX];
123 snprintf(path, PATH_MAX, "%s/%s%s", dir, spec, suffix);
124 char *resolved = realpath(path, NULL);
125 if (resolved) {
126 struct stat st;
127 if (stat(resolved, &st) == 0 && S_ISREG(st.st_mode)) return resolved;
128 free(resolved);
129 }
130 return NULL;
131}
132
133static bool esm_has_extension(const char *spec) {
134 const char *slash = strrchr(spec, '/');
135 const char *dot = strrchr(slash ? slash : spec, '.');
136
137 if (!dot) return false;
138 for (
139 const char *const *ext = module_resolve_extensions;
140 *ext; ext++
141 ) if (strcmp(dot, *ext) == 0) return true;
142
143 return false;
144}
145
146static char *esm_try_resolve_with_exts(const char *dir, const char *spec, bool has_ext) {
147 const char *const *exts = module_resolve_extensions;
148 char *result = NULL;
149
150 if ((result = esm_try_resolve(dir, spec, ""))) return result;
151 if (has_ext) return NULL;
152
153 for (int i = 0; exts[i]; i++) {
154 if ((result = esm_try_resolve(dir, spec, exts[i]))) return result;
155 }
156 return NULL;
157}
158
159static char *esm_try_resolve_index_with_exts(const char *dir, const char *spec) {
160 char idx[PATH_MAX];
161 snprintf(idx, sizeof(idx), "%s/index", spec);
162 return esm_try_resolve_with_exts(dir, idx, false);
163}
164
165static char *esm_try_resolve_relative_typescript_source_fallback(
166 const char *dir,
167 const char *spec,
168 const char *base_path
169) {
170 if (!is_typescript_file(base_path)) return NULL;
171
172 char *ts_spec = resolve_typescript_source_fallback(spec);
173 if (!ts_spec) return NULL;
174
175 char *resolved = esm_try_resolve(dir, ts_spec, "");
176 free(ts_spec);
177 return resolved;
178}
179
180static ant_value_t esm_default_export_or_namespace(ant_t *js, ant_value_t ns) {
181 ant_value_t default_val = js_get_slot(ns, SLOT_DEFAULT);
182 return vtype(default_val) != T_UNDEF ? default_val : ns;
183}
184
185static ant_value_t esm_make_namespace_object(ant_t *js) {
186 ant_value_t ns = js_mkobj(js);
187 js_set_slot(ns, SLOT_BRAND, js_mknum(BRAND_MODULE_NAMESPACE));
188 js_set_slot(ns, SLOT_MODULE_LOADING, js_true);
189 return ns;
190}
191
192static ant_value_t esm_complete_value_module(esm_module_t *mod, ant_value_t value) {
193 if (is_err(value)) {
194 mod->is_loading = false;
195 return value;
196 }
197 mod->namespace_obj = value;
198 mod->default_export = value;
199 mod->is_loaded = true;
200 mod->is_loading = false;
201 return value;
202}
203
204static ant_value_t esm_complete_namespace_module(ant_t *js, esm_module_t *mod, ant_value_t ns) {
205 mod->namespace_obj = ns;
206 mod->default_export = esm_default_export_or_namespace(js, ns);
207 mod->is_loaded = true;
208 mod->is_loading = false;
209 js_set_slot(ns, SLOT_MODULE_LOADING, js_mkundef());
210 return ns;
211}
212
213static ant_value_t esm_prepare_eval_ctx(
214 ant_t *js,
215 const char *resolved_path,
216 ant_value_t ns,
217 ant_module_format_t format,
218 bool is_main,
219 const char *fallback_parent_path,
220 ant_module_t *out_ctx
221) {
222 ant_value_t module_ctx = js_create_module_context(js, resolved_path, is_main);
223
224 if (is_err(module_ctx)) return module_ctx;
225 if (is_object_type(ns)) js_set_slot_wb(js, ns, SLOT_MODULE_CTX, module_ctx);
226
227 *out_ctx = (ant_module_t){
228 .module_ns = ns,
229 .module_ctx = module_ctx,
230 .prev_import_meta_prop = js_mkundef(),
231 .format = format,
232 .prev = NULL,
233 };
234
235 return js_mkundef();
236}
237
238static char *esm_try_resolve_from_extension_list(
239 const char *dir,
240 const char *spec,
241 const char *first_ext,
242 const char *skip_ext
243) {
244 char *result = NULL;
245 if (first_ext && first_ext[0]) {
246 if ((result = esm_try_resolve(dir, spec, first_ext))) return result;
247 }
248
249 const char *const *exts = module_resolve_extensions;
250 for (int i = 0; exts[i]; i++) {
251 if (skip_ext && strcmp(skip_ext, exts[i]) == 0) continue;
252 if ((result = esm_try_resolve(dir, spec, exts[i]))) return result;
253 }
254 return NULL;
255}
256
257static char *esm_resolve_absolute(const char *specifier) {
258 char *result = esm_try_resolve_with_exts("", specifier, esm_has_extension(specifier));
259 if (result) return result;
260 return esm_try_resolve_index_with_exts("", specifier);
261}
262
263static char *esm_get_base_dir(const char *base_path) {
264 if (!base_path || !base_path[0]) {
265 char cwd[PATH_MAX];
266 if (!getcwd(cwd, sizeof(cwd))) return NULL;
267 return strdup(cwd);
268 }
269
270 char candidate[PATH_MAX];
271 if (base_path[0] == '/') {
272 snprintf(candidate, sizeof(candidate), "%s", base_path);
273 } else {
274 char cwd[PATH_MAX];
275 if (!getcwd(cwd, sizeof(cwd))) return NULL;
276 snprintf(candidate, sizeof(candidate), "%s/%s", cwd, base_path);
277 }
278
279 char *resolved = realpath(candidate, NULL);
280 const char *resolved_or_candidate = resolved ? resolved : candidate;
281
282 struct stat st;
283 if (stat(resolved_or_candidate, &st) == 0 && S_ISDIR(st.st_mode)) {
284 char *out = realpath(resolved_or_candidate, NULL);
285 if (!out) out = strdup(resolved_or_candidate);
286 if (resolved) free(resolved);
287 return out;
288 }
289
290 char *tmp = strdup(resolved_or_candidate);
291 if (resolved) free(resolved);
292 if (!tmp) return NULL;
293
294 char *dir = dirname(tmp);
295 char *out = realpath(dir, NULL);
296 if (!out) out = strdup(dir);
297
298 free(tmp);
299 return out;
300}
301
302static bool esm_split_package_specifier(
303 const char *specifier,
304 char *package_name,
305 size_t package_name_size,
306 const char **subpath_out
307) {
308 if (!specifier || !specifier[0]) return false;
309 if (specifier[0] == '.' || specifier[0] == '/' || specifier[0] == '#') return false;
310
311 const char *slash = NULL;
312 if (specifier[0] == '@') {
313 const char *first = strchr(specifier, '/');
314 if (!first || first == specifier + 1) return false;
315 slash = strchr(first + 1, '/');
316 if (first[1] == '\0') return false;
317 if (!slash) {
318 if (strlen(specifier) >= package_name_size) return false;
319 strcpy(package_name, specifier);
320 *subpath_out = NULL;
321 return true;
322 }
323 } else slash = strchr(specifier, '/');
324
325 size_t name_len = slash ? (size_t)(slash - specifier) : strlen(specifier);
326 if (name_len == 0 || name_len >= package_name_size) return false;
327 memcpy(package_name, specifier, name_len);
328 package_name[name_len] = '\0';
329
330 if (slash && slash[1] != '\0') *subpath_out = slash + 1;
331 else *subpath_out = NULL;
332
333 return true;
334}
335
336static char *esm_find_node_module_dir(const char *start_dir, const char *package_name) {
337 if (!start_dir || !package_name) return NULL;
338
339 char current[PATH_MAX];
340 snprintf(current, sizeof(current), "%s", start_dir);
341
342 while (true) {
343 char candidate[PATH_MAX];
344 snprintf(candidate, sizeof(candidate), "%s/node_modules/%s", current, package_name);
345
346 struct stat st;
347 if (stat(candidate, &st) == 0 && S_ISDIR(st.st_mode)) {
348 char *resolved = realpath(candidate, NULL);
349 return resolved ? resolved : strdup(candidate);
350 }
351
352 if (strcmp(current, "/") == 0) break;
353 char *slash = strrchr(current, '/');
354
355 if (!slash) break;
356 if (slash == current) current[1] = '\0';
357 else *slash = '\0';
358 }
359
360 return NULL;
361}
362
363static bool esm_matches_pattern_key(const char *key, const char *request, const char **capture, size_t *capture_len) {
364 const char *star = strchr(key, '*');
365 if (!star) return false;
366
367 size_t key_len = strlen(key);
368 size_t req_len = strlen(request);
369 size_t prefix_len = (size_t)(star - key);
370 size_t suffix_len = key_len - prefix_len - 1;
371
372 if (req_len < prefix_len + suffix_len) return false;
373 if (strncmp(request, key, prefix_len) != 0) return false;
374 if (suffix_len > 0 && strcmp(request + req_len - suffix_len, star + 1) != 0) return false;
375
376 *capture = request + prefix_len;
377 *capture_len = req_len - prefix_len - suffix_len;
378
379 return true;
380}
381
382static char *esm_replace_star(const char *pattern, const char *capture, size_t capture_len) {
383 const char *star = strchr(pattern, '*');
384 if (!star) return strdup(pattern);
385
386 size_t prefix_len = (size_t)(star - pattern);
387 size_t suffix_len = strlen(star + 1);
388 size_t out_len = prefix_len + capture_len + suffix_len;
389 char *out = (char *)malloc(out_len + 1);
390 if (!out) return NULL;
391
392 memcpy(out, pattern, prefix_len);
393 memcpy(out + prefix_len, capture, capture_len);
394 memcpy(out + prefix_len + capture_len, star + 1, suffix_len);
395 out[out_len] = '\0';
396
397 return out;
398}
399
400static char *esm_resolve_exports_target(
401 yyjson_val *target,
402 const char *package_dir,
403 const char *capture,
404 size_t capture_len,
405 const char *base_path,
406 bool allow_bare_specifiers,
407 bool prefer_require
408) {
409 if (!target) return NULL;
410
411 if (yyjson_is_str(target)) {
412 const char *target_str = yyjson_get_str(target);
413 if (!target_str || !target_str[0]) return NULL;
414
415 if (target_str[0] == '.' && target_str[1] == '/') {
416 char *mapped = esm_replace_star(target_str + 2, capture, capture_len);
417 if (!mapped) return NULL;
418
419 char *resolved = esm_try_resolve_with_exts(package_dir, mapped, esm_has_extension(mapped));
420 if (!resolved) resolved = esm_try_resolve_index_with_exts(package_dir, mapped);
421 free(mapped);
422 return resolved;
423 }
424
425 if (!allow_bare_specifiers) return NULL;
426 if (target_str[0] == '#') return NULL;
427 return esm_resolve_node_module(target_str, base_path);
428 }
429
430 if (yyjson_is_arr(target)) {
431 size_t idx, max;
432 yyjson_val *item;
433 yyjson_arr_foreach(target, idx, max, item) {
434 char *resolved = esm_resolve_exports_target(
435 item, package_dir, capture,
436 capture_len, base_path, allow_bare_specifiers, prefer_require
437 );
438 if (resolved) return resolved;
439 }
440 return NULL;
441 }
442
443 if (yyjson_is_obj(target)) {
444 static const char *const import_conditions[] = {"import", "node", "default"};
445 static const char *const require_conditions[] = {"require", "node", "default"};
446
447 const char *const *conditions = prefer_require
448 ? require_conditions
449 : import_conditions;
450
451 size_t condition_count = prefer_require
452 ? sizeof(require_conditions) / sizeof(require_conditions[0])
453 : sizeof(import_conditions) / sizeof(import_conditions[0]);
454
455 for (size_t i = 0; i < condition_count; i++) {
456 yyjson_val *cond_target = yyjson_obj_get(target, conditions[i]);
457 if (!cond_target) continue;
458 char *resolved = esm_resolve_exports_target(
459 cond_target, package_dir, capture,
460 capture_len, base_path, allow_bare_specifiers, prefer_require
461 );
462 if (resolved) return resolved;
463 }
464 }
465
466 return NULL;
467}
468
469static char *esm_resolve_package_map(
470 yyjson_val *map_obj,
471 const char *request_key,
472 const char *package_dir,
473 const char *base_path,
474 bool allow_bare_specifiers,
475 bool prefer_require
476) {
477 if (!map_obj || !yyjson_is_obj(map_obj)) return NULL;
478
479 yyjson_val *exact = yyjson_obj_get(map_obj, request_key);
480 if (exact) return esm_resolve_exports_target(
481 exact, package_dir, "", 0,
482 base_path, allow_bare_specifiers, prefer_require
483 );
484
485 const char *best_capture = NULL;
486 size_t best_capture_len = 0;
487
488 yyjson_val *best_target = NULL;
489 size_t best_prefix_len = 0;
490
491 size_t idx, max;
492 yyjson_val *k, *v;
493
494 yyjson_obj_foreach(map_obj, idx, max, k, v) {
495 if (!yyjson_is_str(k)) continue;
496 const char *key = yyjson_get_str(k);
497 if (!key) continue;
498
499 const char *capture = NULL;
500 size_t capture_len = 0;
501 if (!esm_matches_pattern_key(key, request_key, &capture, &capture_len)) continue;
502
503 const char *star = strchr(key, '*');
504 size_t prefix_len = (size_t)(star - key);
505 if (best_target && prefix_len < best_prefix_len) continue;
506
507 best_target = v;
508 best_capture = capture;
509 best_capture_len = capture_len;
510 best_prefix_len = prefix_len;
511 }
512
513 if (!best_target) return NULL;
514 return esm_resolve_exports_target(
515 best_target, package_dir, best_capture,
516 best_capture_len, base_path, allow_bare_specifiers, prefer_require
517 );
518}
519
520static char *esm_resolve_package_main_entry(yyjson_val *root, const char *package_dir) {
521 if (!root || !yyjson_is_obj(root)) return NULL;
522
523 yyjson_val *main = yyjson_obj_get(root, "main");
524 if (!main || !yyjson_is_str(main)) return NULL;
525
526 const char *main_str = yyjson_get_str(main);
527 if (!main_str || !main_str[0]) return NULL;
528
529 char *resolved = esm_try_resolve_with_exts(package_dir, main_str, esm_has_extension(main_str));
530 if (!resolved) resolved = esm_try_resolve_index_with_exts(package_dir, main_str);
531 return resolved;
532}
533
534static char *esm_resolve_package_entrypoint(const char *package_dir, const char *subpath, const char *base_path, bool prefer_require) {
535 char pkg_json_path[PATH_MAX];
536 snprintf(pkg_json_path, sizeof(pkg_json_path), "%s/package.json", package_dir);
537
538 yyjson_doc *doc = yyjson_read_file(pkg_json_path, 0, NULL, NULL);
539 yyjson_val *root = doc ? yyjson_doc_get_root(doc) : NULL;
540 yyjson_val *exports = (root && yyjson_is_obj(root)) ? yyjson_obj_get(root, "exports") : NULL;
541
542 if (exports) {
543 char subpath_key[PATH_MAX];
544 if (subpath && subpath[0]) snprintf(subpath_key, sizeof(subpath_key), "./%s", subpath);
545 else snprintf(subpath_key, sizeof(subpath_key), ".");
546
547 char *resolved = NULL;
548 if (yyjson_is_obj(exports)) {
549 bool has_subpath_keys = false;
550 size_t idx, max;
551 yyjson_val *k, *v;
552 yyjson_obj_foreach(exports, idx, max, k, v) {
553 if (!yyjson_is_str(k)) continue;
554 const char *key = yyjson_get_str(k);
555 if (key && key[0] == '.') { has_subpath_keys = true; break; }
556 }
557 if (has_subpath_keys) resolved = esm_resolve_package_map(exports, subpath_key, package_dir, base_path, false, prefer_require);
558 else if (!subpath || !subpath[0]) resolved = esm_resolve_exports_target(exports, package_dir, "", 0, base_path, false, prefer_require);
559 } else if (!subpath || !subpath[0]) resolved = esm_resolve_exports_target(exports, package_dir, "", 0, base_path, false, prefer_require);
560
561 if (doc) yyjson_doc_free(doc);
562 return resolved;
563 }
564
565 if (!subpath || !subpath[0]) {
566 char *resolved = esm_resolve_package_main_entry(root, package_dir);
567 if (resolved) {
568 if (doc) yyjson_doc_free(doc);
569 return resolved;
570 }
571 if (doc) yyjson_doc_free(doc);
572 return esm_try_resolve_index_with_exts(package_dir, ".");
573 }
574
575 char *resolved = esm_try_resolve_with_exts(package_dir, subpath, esm_has_extension(subpath));
576 if (!resolved) resolved = esm_try_resolve_index_with_exts(package_dir, subpath);
577 if (doc) yyjson_doc_free(doc);
578 return resolved;
579}
580
581static char *esm_resolve_package_imports(const char *specifier, const char *base_path, bool prefer_require) {
582 char *start_dir = esm_get_base_dir(base_path);
583 if (!start_dir) return NULL;
584
585 char current[PATH_MAX];
586 snprintf(current, sizeof(current), "%s", start_dir);
587 free(start_dir);
588
589 while (true) {
590 char pkg_json_path[PATH_MAX];
591 snprintf(pkg_json_path, sizeof(pkg_json_path), "%s/package.json", current);
592
593 yyjson_doc *doc = yyjson_read_file(pkg_json_path, 0, NULL, NULL);
594 if (doc) {
595 yyjson_val *root = yyjson_doc_get_root(doc);
596 yyjson_val *imports = (root && yyjson_is_obj(root)) ? yyjson_obj_get(root, "imports") : NULL;
597 char *resolved = NULL;
598 if (imports && yyjson_is_obj(imports)) {
599 resolved = esm_resolve_package_map(
600 imports, specifier, current,
601 base_path, true, prefer_require
602 );
603 }
604 yyjson_doc_free(doc);
605 return resolved;
606 }
607
608 if (strcmp(current, "/") == 0) break;
609 char *slash = strrchr(current, '/');
610 if (!slash) break;
611 if (slash == current) current[1] = '\0';
612 else *slash = '\0';
613 }
614
615 return NULL;
616}
617
618static char *esm_resolve_node_module_cond(const char *specifier, const char *base_path, bool prefer_require) {
619 char package_name[PATH_MAX];
620 const char *subpath = NULL;
621 if (!esm_split_package_specifier(specifier, package_name, sizeof(package_name), &subpath)) {
622 return NULL;
623 }
624
625 char *start_dir = esm_get_base_dir(base_path);
626 if (!start_dir) return NULL;
627
628 char *package_dir = esm_find_node_module_dir(start_dir, package_name);
629 free(start_dir);
630 if (!package_dir) return NULL;
631
632 char *resolved = esm_resolve_package_entrypoint(package_dir, subpath, base_path, prefer_require);
633 free(package_dir);
634 return resolved;
635}
636
637static char *esm_resolve_node_module(const char *specifier, const char *base_path) {
638 return esm_resolve_node_module_cond(specifier, base_path, false);
639}
640
641static char *esm_resolve_relative_path(const char *specifier, const char *base_path) {
642 char *base_copy = strdup(base_path);
643 if (!base_copy) return NULL;
644 char *dir = dirname(base_copy);
645 char *result = NULL;
646
647 const char *spec = specifier;
648 if (strncmp(specifier, "./", 2) == 0) spec = specifier + 2;
649
650 bool has_ext = esm_has_extension(spec);
651 if ((result = esm_try_resolve(dir, spec, ""))) goto cleanup;
652
653 if (has_ext) {
654 result = esm_try_resolve_relative_typescript_source_fallback(dir, spec, base_path);
655 goto cleanup;
656 }
657
658 char *base_ext = esm_get_extension(base_path);
659 if (!base_ext) goto cleanup;
660
661 if ((result = esm_try_resolve_from_extension_list(dir, spec, base_ext, base_ext))) goto cleanup_ext;
662
663 char idx[PATH_MAX];
664 snprintf(idx, sizeof(idx), "%s/index%s", spec, base_ext);
665 if ((result = esm_try_resolve(dir, idx, ""))) goto cleanup_ext;
666
667 snprintf(idx, sizeof(idx), "%s/index", spec);
668 if ((result = esm_try_resolve_from_extension_list(dir, idx, base_ext, base_ext))) goto cleanup_ext;
669
670 cleanup_ext: {
671 free(base_ext);
672 }
673
674 cleanup: {
675 free(base_copy);
676 return result;
677 }
678}
679
680static char *esm_resolve_path_cond(const char *specifier, const char *base_path, bool prefer_require) {
681 if (!specifier || !specifier[0]) return NULL;
682
683 if (specifier[0] == '/') {
684 return esm_resolve_absolute(specifier);
685 }
686
687 if (esm_is_relative_specifier(specifier)) {
688 return esm_resolve_relative_path(specifier, base_path);
689 }
690
691 if (specifier[0] == '#') {
692 return esm_resolve_package_imports(specifier, base_path, prefer_require);
693 }
694
695 return esm_resolve_node_module_cond(specifier, base_path, prefer_require);
696}
697
698static char *esm_resolve_path(const char *specifier, const char *base_path) {
699 return esm_resolve_path_cond(specifier, base_path, false);
700}
701
702static char *esm_resolve_path_require(const char *specifier, const char *base_path) {
703 return esm_resolve_path_cond(specifier, base_path, true);
704}
705
706static bool esm_has_suffix(const char *path, const char *ext) {
707 size_t len = strlen(path);
708 size_t elen = strlen(ext);
709 return len > elen && strcmp(path + len - elen, ext) == 0;
710}
711
712static inline bool esm_is_json(const char *path) {
713 return esm_has_suffix(path, ".json");
714}
715
716static inline bool esm_is_text(const char *path) {
717 return
718 esm_has_suffix(path, ".txt") ||
719 esm_has_suffix(path, ".md") ||
720 esm_has_suffix(path, ".html") ||
721 esm_has_suffix(path, ".css");
722}
723
724static inline bool esm_is_image(const char *path) {
725 return
726 esm_has_suffix(path, ".png") ||
727 esm_has_suffix(path, ".jpg") ||
728 esm_has_suffix(path, ".jpeg") ||
729 esm_has_suffix(path, ".gif") ||
730 esm_has_suffix(path, ".svg") ||
731 esm_has_suffix(path, ".webp");
732}
733
734static inline bool esm_is_native(const char *path) {
735 return esm_has_suffix(path, ".node");
736}
737
738static inline bool esm_is_cjs_extension(const char *path) {
739 return
740 esm_has_suffix(path, ".cjs") ||
741 esm_has_suffix(path, ".cts");
742}
743
744static inline bool esm_is_esm_extension(const char *path) {
745 return
746 esm_has_suffix(path, ".mjs") ||
747 esm_has_suffix(path, ".mts");
748}
749
750static bool esm_path_contains_node_modules(const char *path) {
751 if (!path) return false;
752 if (strstr(path, "/node_modules/")) return true;
753 return strstr(path, "\\node_modules\\") != NULL;
754}
755
756static bool esm_read_package_json_type_module(const char *pkg_json_path, bool *has_type) {
757 if (has_type) *has_type = false;
758
759 yyjson_doc *doc = yyjson_read_file(pkg_json_path, 0, NULL, NULL);
760 if (!doc) return false;
761
762 yyjson_val *root = yyjson_doc_get_root(doc);
763 yyjson_val *type = (root && yyjson_is_obj(root)) ? yyjson_obj_get(root, "type") : NULL;
764
765 if (!type || !yyjson_is_str(type)) {
766 yyjson_doc_free(doc);
767 return false;
768 }
769
770 const char *type_str = yyjson_get_str(type);
771 if (has_type) *has_type = true;
772
773 bool is_module = type_str && strcmp(type_str, "module") == 0;
774 yyjson_doc_free(doc);
775
776 return is_module;
777}
778
779static bool esm_lookup_package_type_module(const char *resolved_path, bool *is_module) {
780 if (is_module) *is_module = false;
781 if (!resolved_path || !resolved_path[0]) return false;
782
783 char path_copy[PATH_MAX];
784 snprintf(path_copy, sizeof(path_copy), "%s", resolved_path);
785 char *dir = dirname(path_copy);
786 if (!dir || !dir[0]) return false;
787
788 char current[PATH_MAX];
789 snprintf(current, sizeof(current), "%s", dir);
790
791 while (true) {
792 char pkg_json_path[PATH_MAX];
793 snprintf(pkg_json_path, sizeof(pkg_json_path), "%s/package.json", current);
794
795 struct stat st;
796 if (stat(pkg_json_path, &st) == 0 && S_ISREG(st.st_mode)) {
797 bool has_type = false;
798 bool pkg_is_module = esm_read_package_json_type_module(pkg_json_path, &has_type);
799 if (is_module) *is_module = has_type && pkg_is_module;
800 return true;
801 }
802
803 if (strcmp(current, "/") == 0) break;
804 char *slash = strrchr(current, '/');
805 if (!slash) break;
806 if (slash == current) current[1] = '\0';
807 else *slash = '\0';
808 }
809
810 return false;
811}
812
813static ant_module_format_t esm_decide_module_format(const char *resolved_path) {
814 if (!resolved_path || !resolved_path[0]) return MODULE_EVAL_FORMAT_ESM;
815 if (esm_is_cjs_extension(resolved_path)) return MODULE_EVAL_FORMAT_CJS;
816 if (esm_is_esm_extension(resolved_path)) return MODULE_EVAL_FORMAT_ESM;
817
818 if (esm_has_suffix(resolved_path, ".js")) {
819 bool pkg_is_module = false;
820 bool has_package_json = esm_lookup_package_type_module(resolved_path, &pkg_is_module);
821 if (!has_package_json) return MODULE_EVAL_FORMAT_CJS;
822 return pkg_is_module ? MODULE_EVAL_FORMAT_ESM : MODULE_EVAL_FORMAT_CJS;
823 }
824
825 return MODULE_EVAL_FORMAT_ESM;
826}
827
828static ant_value_t esm_eval_module_with_format(
829 ant_t *js,
830 const char *resolved_path,
831 const char *js_code,
832 size_t js_len,
833 ant_value_t ns,
834 ant_module_format_t format
835) {
836 if (format == MODULE_EVAL_FORMAT_CJS) {
837 return esm_load_commonjs_module(js, resolved_path, js_code, js_len, ns);
838 }
839 return js_eval_bytecode_module(js, js_code, js_len);
840}
841
842ant_value_t js_esm_eval_module_source(
843 ant_t *js,
844 const char *resolved_path, const char *js_code,
845 size_t js_len, ant_value_t ns
846) {
847 ant_module_format_t format = esm_decide_module_format(resolved_path);
848 ant_module_t eval_ctx;
849
850 ant_value_t prep_res = esm_prepare_eval_ctx(
851 js, resolved_path, ns, format,
852 js->module == NULL, NULL, &eval_ctx
853 );
854
855 if (is_err(prep_res)) return prep_res;
856 js_module_eval_ctx_push(js, &eval_ctx);
857
858 ant_value_t result = esm_eval_module_with_format(
859 js, resolved_path, js_code,
860 js_len, ns, format
861 );
862
863 js_module_eval_ctx_pop(js, &eval_ctx);
864 return result;
865}
866
867static esm_module_kind_t esm_classify_module_kind(const char *resolved_path) {
868 if (esm_is_data_url(resolved_path)) return ESM_MODULE_KIND_URL;
869 if (esm_is_url(resolved_path)) return ESM_MODULE_KIND_URL;
870 if (esm_is_json(resolved_path)) return ESM_MODULE_KIND_JSON;
871 if (esm_is_text(resolved_path)) return ESM_MODULE_KIND_TEXT;
872 if (esm_is_image(resolved_path)) return ESM_MODULE_KIND_IMAGE;
873 if (esm_is_native(resolved_path)) return ESM_MODULE_KIND_NATIVE;
874 return ESM_MODULE_KIND_CODE;
875}
876
877static char *esm_canonicalize_path(const char *path) {
878 if (!path) return NULL;
879
880 char *canonical = strdup(path);
881 if (!canonical) return NULL;
882
883 char *src = canonical, *dst = canonical;
884
885 while (*src) {
886 if (*src == '/') {
887 *dst++ = '/';
888 while (*src == '/') src++;
889
890 if (strncmp(src, "./", 2) == 0) {
891 src += 2;
892 } else if (strncmp(src, "../", 3) == 0) {
893 src += 3;
894 if (dst > canonical + 1) {
895 dst--;
896 while (dst > canonical && *(dst - 1) != '/') dst--;
897 }
898 }
899 } else {
900 *dst++ = *src++;
901 }
902 }
903
904 *dst = '\0';
905
906 if (strlen(canonical) > 1 && canonical[strlen(canonical) - 1] == '/') {
907 canonical[strlen(canonical) - 1] = '\0';
908 }
909
910 return canonical;
911}
912
913static esm_module_t *esm_find_module(const char *module_key) {
914 char *cache_key = esm_make_cache_key(module_key);
915 if (!cache_key) return NULL;
916
917 esm_module_t *mod = NULL;
918 HASH_FIND_STR(global_module_cache.modules, cache_key, mod);
919
920 free(cache_key);
921 return mod;
922}
923
924static esm_module_t *esm_create_module(
925 const char *path,
926 const char *resolved_path,
927 const char *module_key,
928 ant_module_format_t format,
929 const uint8_t *embedded_code,
930 size_t embedded_code_len
931) {
932 char *cache_key = esm_make_cache_key(module_key);
933 if (!cache_key) return NULL;
934
935 esm_module_t *existing_mod = NULL;
936 HASH_FIND_STR(global_module_cache.modules, cache_key, existing_mod);
937 if (existing_mod) {
938 free(cache_key);
939 return existing_mod;
940 }
941
942 esm_module_t *mod = (esm_module_t *)malloc(sizeof(esm_module_t));
943 if (!mod) {
944 free(cache_key);
945 return NULL;
946 }
947
948 *mod = (esm_module_t){
949 .path = strdup(path),
950 .cache_key = cache_key,
951 .resolved_path = strdup(resolved_path),
952 .namespace_obj = js_mkundef(),
953 .default_export = js_mkundef(),
954 .is_loaded = false,
955 .is_loading = false,
956 .kind = esm_classify_module_kind(resolved_path),
957 .format = format,
958 .url_content = NULL,
959 .url_content_len = 0,
960 .embedded_code = embedded_code,
961 .embedded_code_len = embedded_code_len,
962 .tla_promise = js_mkundef(),
963 .has_tla = false,
964 };
965
966 if (!mod->path || !mod->resolved_path) {
967 free(mod->path);
968 free(mod->cache_key);
969 free(mod->resolved_path);
970 free(mod);
971 return NULL;
972 }
973
974 HASH_ADD_STR(global_module_cache.modules, cache_key, mod);
975 global_module_cache.count++;
976
977 return mod;
978}
979
980void js_esm_cleanup_module_cache(void) {
981 esm_module_t *current, *tmp;
982 HASH_ITER(hh, global_module_cache.modules, current, tmp) {
983 HASH_DEL(global_module_cache.modules, current);
984 if (current->path) free(current->path);
985 if (current->cache_key) free(current->cache_key);
986 if (current->resolved_path) free(current->resolved_path);
987 if (current->url_content) free(current->url_content);
988 free(current);
989 }
990 global_module_cache.count = 0;
991}
992
993static ant_value_t esm_read_file(ant_t *js, const char *path, const char *kind, esm_file_data_t *out) {
994 FILE *fp = fopen(path, "rb");
995 if (!fp) return js_mkerr(js, "Cannot open %s: %s", kind, path);
996
997 fseek(fp, 0, SEEK_END);
998 long fsize = ftell(fp);
999 fseek(fp, 0, SEEK_SET);
1000
1001 char *buf = (char *)malloc((size_t)fsize + 1);
1002 if (!buf) {
1003 fclose(fp);
1004 return js_mkerr(js, "OOM loading %s", kind);
1005 }
1006
1007 fread(buf, 1, (size_t)fsize, fp);
1008 fclose(fp);
1009 buf[fsize] = '\0';
1010
1011 out->data = buf;
1012 out->size = (size_t)fsize;
1013 return js_mkundef();
1014}
1015
1016static ant_value_t esm_load_json(ant_t *js, const char *path) {
1017 esm_file_data_t file;
1018 ant_value_t err = esm_read_file(js, path, "JSON file", &file);
1019 if (is_err(err)) return err;
1020
1021 ant_value_t json_str = js_mkstr(js, file.data, file.size);
1022 free(file.data);
1023 return json_parse_value(js, json_str);
1024}
1025
1026static ant_value_t esm_load_text(ant_t *js, const char *path) {
1027 esm_file_data_t file;
1028 ant_value_t err = esm_read_file(js, path, "text file", &file);
1029 if (is_err(err)) return err;
1030
1031 ant_value_t result = js_mkstr(js, file.data, file.size);
1032 free(file.data);
1033 return result;
1034}
1035
1036static ant_value_t esm_load_image(ant_t *js, const char *path) {
1037 esm_file_data_t file;
1038 ant_value_t err = esm_read_file(js, path, "image file", &file);
1039 if (is_err(err)) return err;
1040
1041 unsigned char *content = (unsigned char *)file.data;
1042 size_t size = file.size;
1043
1044 ant_value_t obj = js_mkobj(js);
1045 ant_value_t data_arr = js_mkarr(js);
1046
1047 for (size_t i = 0; i < size; i++) {
1048 js_arr_push(js, data_arr, tov((double)content[i]));
1049 }
1050
1051 js_setprop(js, obj, js_mkstr(js, "data", 4), data_arr);
1052 js_setprop(js, obj, js_mkstr(js, "path", 4), js_mkstr(js, path, strlen(path)));
1053 js_setprop(js, obj, js_mkstr(js, "size", 4), tov((double)size));
1054
1055 free(file.data);
1056 return obj;
1057}
1058
1059static ant_value_t esm_load_module(ant_t *js, esm_module_t *mod) {
1060 if (mod->is_loaded) return mod->namespace_obj;
1061 if (mod->is_loading) return mod->namespace_obj;
1062
1063 mod->is_loading = true;
1064
1065 switch (mod->kind) {
1066 case ESM_MODULE_KIND_JSON: {
1067 ant_value_t json_val = esm_load_json(js, mod->resolved_path);
1068 return esm_complete_value_module(mod, json_val);
1069 }
1070 case ESM_MODULE_KIND_TEXT: {
1071 ant_value_t text_val = esm_load_text(js, mod->resolved_path);
1072 return esm_complete_value_module(mod, text_val);
1073 }
1074 case ESM_MODULE_KIND_IMAGE: {
1075 ant_value_t img_val = esm_load_image(js, mod->resolved_path);
1076 return esm_complete_value_module(mod, img_val);
1077 }
1078 case ESM_MODULE_KIND_NATIVE: {
1079 ant_value_t ns = esm_make_namespace_object(js);
1080 mod->namespace_obj = ns;
1081
1082 ant_value_t module_ctx = js_create_module_context(js, mod->resolved_path, false);
1083 if (is_err(module_ctx)) {
1084 mod->is_loading = false;
1085 return module_ctx;
1086 }
1087 js_set_slot_wb(js, ns, SLOT_MODULE_CTX, module_ctx);
1088
1089 ant_value_t native_exports = napi_load_native_module(js, mod->resolved_path, ns);
1090 if (is_err(native_exports)) {
1091 mod->is_loading = false;
1092 return native_exports;
1093 }
1094 return esm_complete_namespace_module(js, mod, ns);
1095 }
1096 case ESM_MODULE_KIND_CODE:
1097 case ESM_MODULE_KIND_URL: break;
1098 }
1099
1100 char *content = NULL;
1101 size_t size = 0;
1102
1103 if (mod->embedded_code) {
1104 content = (char *)malloc(mod->embedded_code_len + 1);
1105 if (!content) {
1106 mod->is_loading = false;
1107 return js_mkerr(js, "OOM loading bundled module");
1108 }
1109 memcpy(content, mod->embedded_code, mod->embedded_code_len);
1110 size = mod->embedded_code_len;
1111 } else if (mod->kind == ESM_MODULE_KIND_URL && esm_is_data_url(mod->resolved_path)) {
1112 content = esm_parse_data_url(mod->resolved_path, &size);
1113 if (!content) {
1114 mod->is_loading = false;
1115 return js_mkerr(js, "Cannot parse data URL module");
1116 }
1117 } else if (mod->kind == ESM_MODULE_KIND_URL) {
1118 if (mod->url_content) {
1119 content = strdup(mod->url_content);
1120 size = mod->url_content_len;
1121 } else {
1122 char *error = NULL;
1123 content = esm_fetch_url(mod->resolved_path, &size, &error);
1124 if (!content) {
1125 mod->is_loading = false;
1126 ant_value_t err = js_mkerr(js, "Cannot fetch module %s: %s", mod->resolved_path, error ? error : "unknown error");
1127 if (error) free(error);
1128 return err;
1129 }
1130 mod->url_content = strdup(content);
1131 mod->url_content_len = size;
1132 }
1133 } else {
1134 esm_file_data_t file;
1135 ant_value_t err = esm_read_file(js, mod->resolved_path, "module", &file);
1136 if (is_err(err)) {
1137 mod->is_loading = false;
1138 return err;
1139 }
1140 content = file.data;
1141 size = file.size;
1142 }
1143 content[size] = '\0';
1144
1145 size_t js_len = size;
1146 const char *strip_detail = NULL;
1147
1148 if (!mod->embedded_code) {
1149 int strip_result = strip_typescript_inplace(
1150 &content, size, mod->resolved_path,
1151 mod->format != MODULE_EVAL_FORMAT_CJS,
1152 &js_len, &strip_detail
1153 );
1154
1155 if (strip_result < 0) {
1156 ant_value_t err = js_mkerr(
1157 js, "TypeScript error: strip failed (%d): %s",
1158 strip_result, strip_detail
1159 );
1160 free(content);
1161 mod->is_loading = false;
1162 return err;
1163 }}
1164
1165 char *js_code = content;
1166 ant_value_t ns = esm_make_namespace_object(js);
1167 mod->namespace_obj = ns;
1168
1169 const char *prev_filename = js->filename;
1170 ant_module_t eval_ctx;
1171
1172 ant_value_t prep_res = esm_prepare_eval_ctx(
1173 js, mod->resolved_path,
1174 ns, mod->format,
1175 false, prev_filename,
1176 &eval_ctx
1177 );
1178
1179 if (is_err(prep_res)) {
1180 free(content);
1181 mod->is_loading = false;
1182 return prep_res;
1183 }
1184
1185 js_set_filename(js, mod->resolved_path);
1186 js_module_eval_ctx_push(js, &eval_ctx);
1187
1188 if (mod->format == MODULE_EVAL_FORMAT_UNKNOWN) {
1189 mod->format = esm_decide_module_format(mod->resolved_path);
1190 eval_ctx.format = mod->format;
1191 }
1192
1193 ant_value_t result = esm_eval_module_with_format(
1194 js, mod->resolved_path, js_code, js_len, ns, mod->format
1195 );
1196
1197 free(content);
1198 if (vtype(result) == T_PROMISE) {
1199 if (esm_dynamic_import_depth > 0) {
1200 mod->has_tla = true;
1201 mod->tla_promise = result;
1202 } else js_run_event_loop(js); }
1203
1204 js_module_eval_ctx_pop(js, &eval_ctx);
1205 js_set_filename(js, prev_filename);
1206
1207 if (is_err(result)) {
1208 mod->is_loading = false;
1209 return result;
1210 }
1211 return esm_complete_namespace_module(js, mod, ns);
1212}
1213
1214static ant_value_t esm_get_or_load(
1215 ant_t *js,
1216 const char *specifier,
1217 const char *resolved_path,
1218 const char *module_key,
1219 ant_module_format_t format,
1220 const uint8_t *embedded_code,
1221 size_t embedded_code_len
1222) {
1223 esm_module_t *mod = esm_find_module(module_key);
1224 if (!mod) {
1225 mod = esm_create_module(
1226 specifier,
1227 resolved_path,
1228 module_key,
1229 format,
1230 embedded_code,
1231 embedded_code_len
1232 );
1233 if (!mod) return js_mkerr(js, "Cannot create module");
1234 }
1235 return esm_load_module(js, mod);
1236}
1237
1238static const char *esm_default_base_path(ant_t *js) {
1239 const char *active = js_module_eval_active_filename(js);
1240 return (active && active[0]) ? active : ".";
1241}
1242
1243ant_value_t js_esm_import_sync_cstr_from(
1244 ant_t *js,
1245 const char *specifier,
1246 size_t spec_len,
1247 const char *base_path
1248) {
1249 const ant_builtin_bundle_alias_t *bundle = NULL;
1250 const ant_builtin_bundle_module_t *module = NULL;
1251
1252 char *spec_copy = strndup(specifier, spec_len);
1253 if (!spec_copy) return js_mkerr(js, "oom");
1254
1255 char *file_url_path = esm_file_url_to_path(js, spec_copy);
1256 if (file_url_path) {
1257 free(spec_copy);
1258 spec_copy = file_url_path;
1259 spec_len = strlen(spec_copy);
1260 }
1261
1262 bundle = esm_lookup_builtin_alias(spec_copy, spec_len);
1263 if (bundle) {
1264 module = esm_lookup_builtin_module(bundle->module_id);
1265 if (!module) {
1266 free(spec_copy);
1267 return js_mkerr(js, "Invalid builtin module id");
1268 }
1269
1270 ant_value_t ns = esm_get_or_load(
1271 js, spec_copy,
1272 bundle->source_name,
1273 bundle->source_name,
1274 module->format,
1275 module->code,
1276 module->code_len
1277 );
1278
1279 free(spec_copy);
1280 return ns;
1281 }
1282
1283 bool loaded = false;
1284 ant_value_t lib = js_esm_load_registered_library(js, spec_copy, spec_len, &loaded);
1285 if (loaded) {
1286 free(spec_copy);
1287 return lib;
1288 }
1289
1290 if (!base_path || !base_path[0]) base_path = esm_default_base_path(js);
1291 char *resolved_path = esm_resolve(spec_copy, base_path, esm_resolve_path);
1292 if (!resolved_path) {
1293 ant_value_t err = js_mkerr(js, "Cannot resolve module: %s", spec_copy);
1294 free(spec_copy);
1295 return err;
1296 }
1297
1298 ant_value_t ns = esm_get_or_load(
1299 js,
1300 spec_copy,
1301 resolved_path,
1302 resolved_path,
1303 MODULE_EVAL_FORMAT_UNKNOWN,
1304 NULL,
1305 0
1306 );
1307 free(resolved_path);
1308 free(spec_copy);
1309 return ns;
1310}
1311
1312ant_value_t js_esm_import_sync_cstr_from_require(
1313 ant_t *js,
1314 const char *specifier,
1315 size_t spec_len,
1316 const char *base_path
1317) {
1318 const ant_builtin_bundle_alias_t *bundle = NULL;
1319 const ant_builtin_bundle_module_t *module = NULL;
1320
1321 char *spec_copy = strndup(specifier, spec_len);
1322 if (!spec_copy) return js_mkerr(js, "oom");
1323
1324 char *file_url_path = esm_file_url_to_path(js, spec_copy);
1325 if (file_url_path) {
1326 free(spec_copy);
1327 spec_copy = file_url_path;
1328 spec_len = strlen(spec_copy);
1329 }
1330
1331 bundle = esm_lookup_builtin_alias(spec_copy, spec_len);
1332 if (bundle) {
1333 module = esm_lookup_builtin_module(bundle->module_id);
1334 if (!module) {
1335 free(spec_copy);
1336 return js_mkerr(js, "Invalid builtin module id");
1337 }
1338
1339 ant_value_t ns = esm_get_or_load(
1340 js, spec_copy,
1341 bundle->source_name,
1342 bundle->source_name,
1343 module->format,
1344 module->code,
1345 module->code_len
1346 );
1347
1348 free(spec_copy);
1349 return ns;
1350 }
1351
1352 bool loaded = false;
1353 ant_value_t lib = js_esm_load_registered_library(js, spec_copy, spec_len, &loaded);
1354 if (loaded) {
1355 free(spec_copy);
1356 return lib;
1357 }
1358
1359 if (!base_path || !base_path[0]) base_path = esm_default_base_path(js);
1360 char *resolved_path = esm_resolve(spec_copy, base_path, esm_resolve_path_require);
1361 if (!resolved_path) {
1362 ant_value_t err = js_mkerr(js, "Cannot resolve module: %s", spec_copy);
1363 free(spec_copy);
1364 return err;
1365 }
1366
1367 ant_value_t ns = esm_get_or_load(
1368 js, spec_copy,
1369 resolved_path,
1370 resolved_path,
1371 MODULE_EVAL_FORMAT_UNKNOWN,
1372 NULL, 0
1373 );
1374
1375 free(resolved_path);
1376 free(spec_copy);
1377
1378 return ns;
1379}
1380
1381ant_value_t js_esm_import_sync_cstr(ant_t *js, const char *specifier, size_t spec_len) {
1382 return js_esm_import_sync_cstr_from(js, specifier, spec_len, NULL);
1383}
1384
1385ant_value_t js_esm_import_sync_from(ant_t *js, ant_value_t specifier, const char *base_path) {
1386 if (vtype(specifier) != T_STR)
1387 return js_mkerr(js, "import() requires a string specifier");
1388
1389 ant_offset_t spec_len = 0;
1390 ant_offset_t spec_off = vstr(js, specifier, &spec_len);
1391 const char *spec_str = (const char *)(uintptr_t)(spec_off);
1392
1393 return js_esm_import_sync_cstr_from(js, spec_str, (size_t)spec_len, base_path);
1394}
1395
1396ant_value_t js_esm_import_sync_from_require(ant_t *js, ant_value_t specifier, const char *base_path) {
1397 if (vtype(specifier) != T_STR)
1398 return js_mkerr(js, "require() expects a string specifier");
1399
1400 ant_offset_t spec_len = 0;
1401 ant_offset_t spec_off = vstr(js, specifier, &spec_len);
1402 const char *spec_str = (const char *)(uintptr_t)(spec_off);
1403
1404 return js_esm_import_sync_cstr_from_require(js, spec_str, (size_t)spec_len, base_path);
1405}
1406
1407ant_value_t js_esm_import_sync(ant_t *js, ant_value_t specifier) {
1408 return js_esm_import_sync_from(js, specifier, NULL);
1409}
1410
1411ant_value_t js_esm_import_dynamic(ant_t *js, ant_value_t specifier, const char *base_path, ant_value_t *out_tla_promise) {
1412 *out_tla_promise = js_mkundef();
1413 esm_dynamic_import_depth++;
1414
1415 ant_value_t ns = js_esm_import_sync_from(js, specifier, base_path);
1416 esm_dynamic_import_depth--;
1417 if (is_err(ns)) return ns;
1418
1419 esm_module_t *mod = NULL, *tmp = NULL;
1420 HASH_ITER(hh, global_module_cache.modules, mod, tmp) {
1421 if (mod->has_tla && mod->namespace_obj == ns) {
1422 *out_tla_promise = mod->tla_promise;
1423 mod->has_tla = false;
1424 mod->tla_promise = js_mkundef();
1425 break;
1426 }}
1427
1428 return ns;
1429}
1430
1431ant_value_t js_esm_make_file_url(ant_t *js, const char *path) {
1432 size_t path_len = strlen(path);
1433 size_t raw_len = 7 + path_len;
1434
1435 char *raw = malloc(raw_len + 1);
1436 if (!raw) return js_mkerr(js, "oom");
1437
1438 snprintf(raw, raw_len + 1, "file://%s", path);
1439 ant_value_t raw_val = js_mkstr(js, raw, raw_len);
1440 free(raw);
1441
1442 return js_encodeURI(js, &raw_val, 1);
1443}
1444
1445void gc_mark_esm(ant_t *js, gc_mark_fn mark) {
1446esm_module_t *mod = NULL, *tmp = NULL;
1447HASH_ITER(hh, global_module_cache.modules, mod, tmp) {
1448 mark(js, mod->namespace_obj);
1449 mark(js, mod->default_export);
1450 if (mod->has_tla) mark(js, mod->tla_promise);
1451}}
1452
1453ant_value_t js_esm_resolve_specifier(ant_t *js, ant_value_t specifier, const char *base_path) {
1454 const ant_builtin_bundle_alias_t *bundle = NULL;
1455 if (vtype(specifier) != T_STR) {
1456 return js_mkerr(js, "import.meta.resolve() requires a string specifier");
1457 }
1458
1459 ant_offset_t spec_len = 0;
1460 ant_offset_t spec_off = vstr(js, specifier, &spec_len);
1461 const char *spec_str = (const char *)(uintptr_t)(spec_off);
1462 char *spec_copy = strndup(spec_str, (size_t)spec_len);
1463 if (!spec_copy) return js_mkerr(js, "oom");
1464
1465 bundle = esm_lookup_builtin_alias(spec_copy, (size_t)spec_len);
1466 if (bundle) {
1467 ant_value_t result = js_mkstr(js, bundle->source_name, strlen(bundle->source_name));
1468 free(spec_copy);
1469 return result;
1470 }
1471
1472 if (!base_path || !base_path[0]) base_path = esm_default_base_path(js);
1473 char *resolved_path = esm_resolve(spec_copy, base_path, esm_resolve_path);
1474 free(spec_copy);
1475
1476 if (!resolved_path) {
1477 return js_mkerr(js, "Cannot resolve module");
1478 }
1479
1480 if (esm_is_url(resolved_path)) {
1481 ant_value_t result = js_mkstr(js, resolved_path, strlen(resolved_path));
1482 free(resolved_path);
1483 return result;
1484 }
1485
1486 ant_value_t result = js_esm_make_file_url(js, resolved_path);
1487 free(resolved_path);
1488 return result;
1489}
1490
1491ant_value_t js_esm_resolve_specifier_require(ant_t *js, ant_value_t specifier, const char *base_path) {
1492 const ant_builtin_bundle_alias_t *bundle = NULL;
1493 if (vtype(specifier) != T_STR) {
1494 return js_mkerr(js, "require.resolve() expects a string specifier");
1495 }
1496
1497 ant_offset_t spec_len = 0;
1498 ant_offset_t spec_off = vstr(js, specifier, &spec_len);
1499 const char *spec_str = (const char *)(uintptr_t)(spec_off);
1500 char *spec_copy = strndup(spec_str, (size_t)spec_len);
1501 if (!spec_copy) return js_mkerr(js, "oom");
1502
1503 bundle = esm_lookup_builtin_alias(spec_copy, (size_t)spec_len);
1504 if (bundle) {
1505 ant_value_t result = js_mkstr(js, bundle->source_name, strlen(bundle->source_name));
1506 free(spec_copy);
1507 return result;
1508 }
1509
1510 if (!base_path || !base_path[0]) base_path = esm_default_base_path(js);
1511 char *resolved_path = esm_resolve(spec_copy, base_path, esm_resolve_path_require);
1512 free(spec_copy);
1513
1514 if (!resolved_path) {
1515 return js_mkerr(js, "Cannot resolve module");
1516 }
1517
1518 if (esm_is_url(resolved_path)) {
1519 ant_value_t result = js_mkstr(js, resolved_path, strlen(resolved_path));
1520 free(resolved_path);
1521 return result;
1522 }
1523
1524 ant_value_t result = js_esm_make_file_url(js, resolved_path);
1525 free(resolved_path);
1526
1527 return result;
1528}