#include // IWYU pragma: keep #include "esm/loader.h" #include "esm/commonjs.h" #include "esm/library.h" #include "esm/remote.h" #include "esm/builtin_bundle.h" #include "modules/json.h" #include "modules/napi.h" #include "modules/uri.h" #include "errors.h" #include "gc/modules.h" #include "internal.h" #include "reactor.h" #include "utils.h" #include #include #include #include #ifndef _WIN32 #include #include #endif #include #include typedef enum { ESM_MODULE_KIND_CODE = 0, ESM_MODULE_KIND_JSON, ESM_MODULE_KIND_TEXT, ESM_MODULE_KIND_IMAGE, ESM_MODULE_KIND_NATIVE, ESM_MODULE_KIND_URL, } esm_module_kind_t; typedef struct esm_module { char *path; char *cache_key; char *resolved_path; char *url_content; size_t url_content_len; const uint8_t *embedded_code; size_t embedded_code_len; ant_value_t namespace_obj; ant_value_t default_export; ant_value_t tla_promise; UT_hash_handle hh; esm_module_kind_t kind; ant_module_format_t format; bool is_loaded; bool is_loading; bool has_tla; } esm_module_t; typedef struct { esm_module_t *modules; int count; } esm_module_cache_t; typedef struct { char *data; size_t size; } esm_file_data_t; static esm_module_cache_t global_module_cache = {NULL, 0}; static int esm_dynamic_import_depth = 0; static char *esm_resolve_node_module(const char *specifier, const char *base_path); static char *esm_canonicalize_path(const char *path); static char *esm_file_url_to_path(ant_t *js, const char *specifier) { if (!specifier || strncmp(specifier, "file:", 5) != 0) return NULL; const char *p = specifier + 5; if (strncmp(p, "///", 3) == 0) p += 2; else if (strncmp(p, "//localhost/", 12) == 0) p += 11; if (*p == '\0') return NULL; ant_value_t encoded = js_mkstr(js, p, strlen(p)); ant_value_t decoded = js_decodeURI(js, &encoded, 1); size_t len = 0; char *str = js_getstr(js, decoded, &len); return str ? strndup(str, len) : NULL; } static char *esm_make_cache_key(const char *module_key) { if (!module_key) return NULL; if (esm_has_builtin_scheme(module_key)) return strdup(module_key); if (esm_is_data_url(module_key)) return strdup(module_key); if (esm_is_url(module_key)) return strdup(module_key); return esm_canonicalize_path(module_key); } static char *esm_get_extension(const char *path) { const char *dot = strrchr(path, '.'); const char *slash = strrchr(path, '/'); if (dot && (!slash || dot > slash)) { return strdup(dot); } return strdup(".js"); } static bool esm_is_relative_specifier(const char *specifier) { return strcmp(specifier, ".") == 0 || strcmp(specifier, "..") == 0 || strncmp(specifier, "./", 2) == 0 || strncmp(specifier, "../", 3) == 0; } static char *esm_try_resolve(const char *dir, const char *spec, const char *suffix) { char path[PATH_MAX]; snprintf(path, PATH_MAX, "%s/%s%s", dir, spec, suffix); char *resolved = realpath(path, NULL); if (resolved) { struct stat st; if (stat(resolved, &st) == 0 && S_ISREG(st.st_mode)) return resolved; free(resolved); } return NULL; } static bool esm_has_extension(const char *spec) { const char *slash = strrchr(spec, '/'); const char *dot = strrchr(slash ? slash : spec, '.'); if (!dot) return false; for ( const char *const *ext = module_resolve_extensions; *ext; ext++ ) if (strcmp(dot, *ext) == 0) return true; return false; } static char *esm_try_resolve_with_exts(const char *dir, const char *spec, bool has_ext) { const char *const *exts = module_resolve_extensions; char *result = NULL; if ((result = esm_try_resolve(dir, spec, ""))) return result; if (has_ext) return NULL; for (int i = 0; exts[i]; i++) { if ((result = esm_try_resolve(dir, spec, exts[i]))) return result; } return NULL; } static char *esm_try_resolve_index_with_exts(const char *dir, const char *spec) { char idx[PATH_MAX]; snprintf(idx, sizeof(idx), "%s/index", spec); return esm_try_resolve_with_exts(dir, idx, false); } static char *esm_try_resolve_relative_typescript_source_fallback( const char *dir, const char *spec, const char *base_path ) { if (!is_typescript_file(base_path)) return NULL; char *ts_spec = resolve_typescript_source_fallback(spec); if (!ts_spec) return NULL; char *resolved = esm_try_resolve(dir, ts_spec, ""); free(ts_spec); return resolved; } static ant_value_t esm_default_export_or_namespace(ant_t *js, ant_value_t ns) { ant_value_t default_val = js_get_slot(ns, SLOT_DEFAULT); return vtype(default_val) != T_UNDEF ? default_val : ns; } static ant_value_t esm_make_namespace_object(ant_t *js) { ant_value_t ns = js_mkobj(js); js_set_slot(ns, SLOT_BRAND, js_mknum(BRAND_MODULE_NAMESPACE)); js_set_slot(ns, SLOT_MODULE_LOADING, js_true); return ns; } static ant_value_t esm_complete_value_module(esm_module_t *mod, ant_value_t value) { if (is_err(value)) { mod->is_loading = false; return value; } mod->namespace_obj = value; mod->default_export = value; mod->is_loaded = true; mod->is_loading = false; return value; } static ant_value_t esm_complete_namespace_module(ant_t *js, esm_module_t *mod, ant_value_t ns) { mod->namespace_obj = ns; mod->default_export = esm_default_export_or_namespace(js, ns); mod->is_loaded = true; mod->is_loading = false; js_set_slot(ns, SLOT_MODULE_LOADING, js_mkundef()); return ns; } static ant_value_t esm_prepare_eval_ctx( ant_t *js, const char *resolved_path, ant_value_t ns, ant_module_format_t format, bool is_main, const char *fallback_parent_path, ant_module_t *out_ctx ) { ant_value_t module_ctx = js_create_module_context(js, resolved_path, is_main); if (is_err(module_ctx)) return module_ctx; if (is_object_type(ns)) js_set_slot_wb(js, ns, SLOT_MODULE_CTX, module_ctx); *out_ctx = (ant_module_t){ .module_ns = ns, .module_ctx = module_ctx, .prev_import_meta_prop = js_mkundef(), .format = format, .prev = NULL, }; return js_mkundef(); } static char *esm_try_resolve_from_extension_list( const char *dir, const char *spec, const char *first_ext, const char *skip_ext ) { char *result = NULL; if (first_ext && first_ext[0]) { if ((result = esm_try_resolve(dir, spec, first_ext))) return result; } const char *const *exts = module_resolve_extensions; for (int i = 0; exts[i]; i++) { if (skip_ext && strcmp(skip_ext, exts[i]) == 0) continue; if ((result = esm_try_resolve(dir, spec, exts[i]))) return result; } return NULL; } static char *esm_resolve_absolute(const char *specifier) { char *result = esm_try_resolve_with_exts("", specifier, esm_has_extension(specifier)); if (result) return result; return esm_try_resolve_index_with_exts("", specifier); } static char *esm_get_base_dir(const char *base_path) { if (!base_path || !base_path[0]) { char cwd[PATH_MAX]; if (!getcwd(cwd, sizeof(cwd))) return NULL; return strdup(cwd); } char candidate[PATH_MAX]; if (base_path[0] == '/') { snprintf(candidate, sizeof(candidate), "%s", base_path); } else { char cwd[PATH_MAX]; if (!getcwd(cwd, sizeof(cwd))) return NULL; snprintf(candidate, sizeof(candidate), "%s/%s", cwd, base_path); } char *resolved = realpath(candidate, NULL); const char *resolved_or_candidate = resolved ? resolved : candidate; struct stat st; if (stat(resolved_or_candidate, &st) == 0 && S_ISDIR(st.st_mode)) { char *out = realpath(resolved_or_candidate, NULL); if (!out) out = strdup(resolved_or_candidate); if (resolved) free(resolved); return out; } char *tmp = strdup(resolved_or_candidate); if (resolved) free(resolved); if (!tmp) return NULL; char *dir = dirname(tmp); char *out = realpath(dir, NULL); if (!out) out = strdup(dir); free(tmp); return out; } static bool esm_split_package_specifier( const char *specifier, char *package_name, size_t package_name_size, const char **subpath_out ) { if (!specifier || !specifier[0]) return false; if (specifier[0] == '.' || specifier[0] == '/' || specifier[0] == '#') return false; const char *slash = NULL; if (specifier[0] == '@') { const char *first = strchr(specifier, '/'); if (!first || first == specifier + 1) return false; slash = strchr(first + 1, '/'); if (first[1] == '\0') return false; if (!slash) { if (strlen(specifier) >= package_name_size) return false; strcpy(package_name, specifier); *subpath_out = NULL; return true; } } else slash = strchr(specifier, '/'); size_t name_len = slash ? (size_t)(slash - specifier) : strlen(specifier); if (name_len == 0 || name_len >= package_name_size) return false; memcpy(package_name, specifier, name_len); package_name[name_len] = '\0'; if (slash && slash[1] != '\0') *subpath_out = slash + 1; else *subpath_out = NULL; return true; } static char *esm_find_node_module_dir(const char *start_dir, const char *package_name) { if (!start_dir || !package_name) return NULL; char current[PATH_MAX]; snprintf(current, sizeof(current), "%s", start_dir); while (true) { char candidate[PATH_MAX]; snprintf(candidate, sizeof(candidate), "%s/node_modules/%s", current, package_name); struct stat st; if (stat(candidate, &st) == 0 && S_ISDIR(st.st_mode)) { char *resolved = realpath(candidate, NULL); return resolved ? resolved : strdup(candidate); } if (strcmp(current, "/") == 0) break; char *slash = strrchr(current, '/'); if (!slash) break; if (slash == current) current[1] = '\0'; else *slash = '\0'; } return NULL; } static bool esm_matches_pattern_key(const char *key, const char *request, const char **capture, size_t *capture_len) { const char *star = strchr(key, '*'); if (!star) return false; size_t key_len = strlen(key); size_t req_len = strlen(request); size_t prefix_len = (size_t)(star - key); size_t suffix_len = key_len - prefix_len - 1; if (req_len < prefix_len + suffix_len) return false; if (strncmp(request, key, prefix_len) != 0) return false; if (suffix_len > 0 && strcmp(request + req_len - suffix_len, star + 1) != 0) return false; *capture = request + prefix_len; *capture_len = req_len - prefix_len - suffix_len; return true; } static char *esm_replace_star(const char *pattern, const char *capture, size_t capture_len) { const char *star = strchr(pattern, '*'); if (!star) return strdup(pattern); size_t prefix_len = (size_t)(star - pattern); size_t suffix_len = strlen(star + 1); size_t out_len = prefix_len + capture_len + suffix_len; char *out = (char *)malloc(out_len + 1); if (!out) return NULL; memcpy(out, pattern, prefix_len); memcpy(out + prefix_len, capture, capture_len); memcpy(out + prefix_len + capture_len, star + 1, suffix_len); out[out_len] = '\0'; return out; } static char *esm_resolve_exports_target( yyjson_val *target, const char *package_dir, const char *capture, size_t capture_len, const char *base_path, bool allow_bare_specifiers, bool prefer_require ) { if (!target) return NULL; if (yyjson_is_str(target)) { const char *target_str = yyjson_get_str(target); if (!target_str || !target_str[0]) return NULL; if (target_str[0] == '.' && target_str[1] == '/') { char *mapped = esm_replace_star(target_str + 2, capture, capture_len); if (!mapped) return NULL; char *resolved = esm_try_resolve_with_exts(package_dir, mapped, esm_has_extension(mapped)); if (!resolved) resolved = esm_try_resolve_index_with_exts(package_dir, mapped); free(mapped); return resolved; } if (!allow_bare_specifiers) return NULL; if (target_str[0] == '#') return NULL; return esm_resolve_node_module(target_str, base_path); } if (yyjson_is_arr(target)) { size_t idx, max; yyjson_val *item; yyjson_arr_foreach(target, idx, max, item) { char *resolved = esm_resolve_exports_target( item, package_dir, capture, capture_len, base_path, allow_bare_specifiers, prefer_require ); if (resolved) return resolved; } return NULL; } if (yyjson_is_obj(target)) { static const char *const import_conditions[] = {"import", "node", "default"}; static const char *const require_conditions[] = {"require", "node", "default"}; const char *const *conditions = prefer_require ? require_conditions : import_conditions; size_t condition_count = prefer_require ? sizeof(require_conditions) / sizeof(require_conditions[0]) : sizeof(import_conditions) / sizeof(import_conditions[0]); for (size_t i = 0; i < condition_count; i++) { yyjson_val *cond_target = yyjson_obj_get(target, conditions[i]); if (!cond_target) continue; char *resolved = esm_resolve_exports_target( cond_target, package_dir, capture, capture_len, base_path, allow_bare_specifiers, prefer_require ); if (resolved) return resolved; } } return NULL; } static char *esm_resolve_package_map( yyjson_val *map_obj, const char *request_key, const char *package_dir, const char *base_path, bool allow_bare_specifiers, bool prefer_require ) { if (!map_obj || !yyjson_is_obj(map_obj)) return NULL; yyjson_val *exact = yyjson_obj_get(map_obj, request_key); if (exact) return esm_resolve_exports_target( exact, package_dir, "", 0, base_path, allow_bare_specifiers, prefer_require ); const char *best_capture = NULL; size_t best_capture_len = 0; yyjson_val *best_target = NULL; size_t best_prefix_len = 0; size_t idx, max; yyjson_val *k, *v; yyjson_obj_foreach(map_obj, idx, max, k, v) { if (!yyjson_is_str(k)) continue; const char *key = yyjson_get_str(k); if (!key) continue; const char *capture = NULL; size_t capture_len = 0; if (!esm_matches_pattern_key(key, request_key, &capture, &capture_len)) continue; const char *star = strchr(key, '*'); size_t prefix_len = (size_t)(star - key); if (best_target && prefix_len < best_prefix_len) continue; best_target = v; best_capture = capture; best_capture_len = capture_len; best_prefix_len = prefix_len; } if (!best_target) return NULL; return esm_resolve_exports_target( best_target, package_dir, best_capture, best_capture_len, base_path, allow_bare_specifiers, prefer_require ); } static char *esm_resolve_package_main_entry(yyjson_val *root, const char *package_dir) { if (!root || !yyjson_is_obj(root)) return NULL; yyjson_val *main = yyjson_obj_get(root, "main"); if (!main || !yyjson_is_str(main)) return NULL; const char *main_str = yyjson_get_str(main); if (!main_str || !main_str[0]) return NULL; char *resolved = esm_try_resolve_with_exts(package_dir, main_str, esm_has_extension(main_str)); if (!resolved) resolved = esm_try_resolve_index_with_exts(package_dir, main_str); return resolved; } static char *esm_resolve_package_entrypoint(const char *package_dir, const char *subpath, const char *base_path, bool prefer_require) { char pkg_json_path[PATH_MAX]; snprintf(pkg_json_path, sizeof(pkg_json_path), "%s/package.json", package_dir); yyjson_doc *doc = yyjson_read_file(pkg_json_path, 0, NULL, NULL); yyjson_val *root = doc ? yyjson_doc_get_root(doc) : NULL; yyjson_val *exports = (root && yyjson_is_obj(root)) ? yyjson_obj_get(root, "exports") : NULL; if (exports) { char subpath_key[PATH_MAX]; if (subpath && subpath[0]) snprintf(subpath_key, sizeof(subpath_key), "./%s", subpath); else snprintf(subpath_key, sizeof(subpath_key), "."); char *resolved = NULL; if (yyjson_is_obj(exports)) { bool has_subpath_keys = false; size_t idx, max; yyjson_val *k, *v; yyjson_obj_foreach(exports, idx, max, k, v) { if (!yyjson_is_str(k)) continue; const char *key = yyjson_get_str(k); if (key && key[0] == '.') { has_subpath_keys = true; break; } } if (has_subpath_keys) resolved = esm_resolve_package_map(exports, subpath_key, package_dir, base_path, false, prefer_require); else if (!subpath || !subpath[0]) resolved = esm_resolve_exports_target(exports, package_dir, "", 0, base_path, false, prefer_require); } else if (!subpath || !subpath[0]) resolved = esm_resolve_exports_target(exports, package_dir, "", 0, base_path, false, prefer_require); if (doc) yyjson_doc_free(doc); return resolved; } if (!subpath || !subpath[0]) { char *resolved = esm_resolve_package_main_entry(root, package_dir); if (resolved) { if (doc) yyjson_doc_free(doc); return resolved; } if (doc) yyjson_doc_free(doc); return esm_try_resolve_index_with_exts(package_dir, "."); } char *resolved = esm_try_resolve_with_exts(package_dir, subpath, esm_has_extension(subpath)); if (!resolved) resolved = esm_try_resolve_index_with_exts(package_dir, subpath); if (doc) yyjson_doc_free(doc); return resolved; } static char *esm_resolve_package_imports(const char *specifier, const char *base_path, bool prefer_require) { char *start_dir = esm_get_base_dir(base_path); if (!start_dir) return NULL; char current[PATH_MAX]; snprintf(current, sizeof(current), "%s", start_dir); free(start_dir); while (true) { char pkg_json_path[PATH_MAX]; snprintf(pkg_json_path, sizeof(pkg_json_path), "%s/package.json", current); yyjson_doc *doc = yyjson_read_file(pkg_json_path, 0, NULL, NULL); if (doc) { yyjson_val *root = yyjson_doc_get_root(doc); yyjson_val *imports = (root && yyjson_is_obj(root)) ? yyjson_obj_get(root, "imports") : NULL; char *resolved = NULL; if (imports && yyjson_is_obj(imports)) { resolved = esm_resolve_package_map( imports, specifier, current, base_path, true, prefer_require ); } yyjson_doc_free(doc); return resolved; } if (strcmp(current, "/") == 0) break; char *slash = strrchr(current, '/'); if (!slash) break; if (slash == current) current[1] = '\0'; else *slash = '\0'; } return NULL; } static char *esm_resolve_node_module_cond(const char *specifier, const char *base_path, bool prefer_require) { char package_name[PATH_MAX]; const char *subpath = NULL; if (!esm_split_package_specifier(specifier, package_name, sizeof(package_name), &subpath)) { return NULL; } char *start_dir = esm_get_base_dir(base_path); if (!start_dir) return NULL; char *package_dir = esm_find_node_module_dir(start_dir, package_name); free(start_dir); if (!package_dir) return NULL; char *resolved = esm_resolve_package_entrypoint(package_dir, subpath, base_path, prefer_require); free(package_dir); return resolved; } static char *esm_resolve_node_module(const char *specifier, const char *base_path) { return esm_resolve_node_module_cond(specifier, base_path, false); } static char *esm_resolve_relative_path(const char *specifier, const char *base_path) { char *base_copy = strdup(base_path); if (!base_copy) return NULL; char *dir = dirname(base_copy); char *result = NULL; const char *spec = specifier; if (strncmp(specifier, "./", 2) == 0) spec = specifier + 2; bool has_ext = esm_has_extension(spec); if ((result = esm_try_resolve(dir, spec, ""))) goto cleanup; if (has_ext) { result = esm_try_resolve_relative_typescript_source_fallback(dir, spec, base_path); goto cleanup; } char *base_ext = esm_get_extension(base_path); if (!base_ext) goto cleanup; if ((result = esm_try_resolve_from_extension_list(dir, spec, base_ext, base_ext))) goto cleanup_ext; char idx[PATH_MAX]; snprintf(idx, sizeof(idx), "%s/index%s", spec, base_ext); if ((result = esm_try_resolve(dir, idx, ""))) goto cleanup_ext; snprintf(idx, sizeof(idx), "%s/index", spec); if ((result = esm_try_resolve_from_extension_list(dir, idx, base_ext, base_ext))) goto cleanup_ext; cleanup_ext: { free(base_ext); } cleanup: { free(base_copy); return result; } } static char *esm_resolve_path_cond(const char *specifier, const char *base_path, bool prefer_require) { if (!specifier || !specifier[0]) return NULL; if (specifier[0] == '/') { return esm_resolve_absolute(specifier); } if (esm_is_relative_specifier(specifier)) { return esm_resolve_relative_path(specifier, base_path); } if (specifier[0] == '#') { return esm_resolve_package_imports(specifier, base_path, prefer_require); } return esm_resolve_node_module_cond(specifier, base_path, prefer_require); } static char *esm_resolve_path(const char *specifier, const char *base_path) { return esm_resolve_path_cond(specifier, base_path, false); } static char *esm_resolve_path_require(const char *specifier, const char *base_path) { return esm_resolve_path_cond(specifier, base_path, true); } static bool esm_has_suffix(const char *path, const char *ext) { size_t len = strlen(path); size_t elen = strlen(ext); return len > elen && strcmp(path + len - elen, ext) == 0; } static inline bool esm_is_json(const char *path) { return esm_has_suffix(path, ".json"); } static inline bool esm_is_text(const char *path) { return esm_has_suffix(path, ".txt") || esm_has_suffix(path, ".md") || esm_has_suffix(path, ".html") || esm_has_suffix(path, ".css"); } static inline bool esm_is_image(const char *path) { return esm_has_suffix(path, ".png") || esm_has_suffix(path, ".jpg") || esm_has_suffix(path, ".jpeg") || esm_has_suffix(path, ".gif") || esm_has_suffix(path, ".svg") || esm_has_suffix(path, ".webp"); } static inline bool esm_is_native(const char *path) { return esm_has_suffix(path, ".node"); } static inline bool esm_is_cjs_extension(const char *path) { return esm_has_suffix(path, ".cjs") || esm_has_suffix(path, ".cts"); } static inline bool esm_is_esm_extension(const char *path) { return esm_has_suffix(path, ".mjs") || esm_has_suffix(path, ".mts"); } static bool esm_path_contains_node_modules(const char *path) { if (!path) return false; if (strstr(path, "/node_modules/")) return true; return strstr(path, "\\node_modules\\") != NULL; } static bool esm_read_package_json_type_module(const char *pkg_json_path, bool *has_type) { if (has_type) *has_type = false; yyjson_doc *doc = yyjson_read_file(pkg_json_path, 0, NULL, NULL); if (!doc) return false; yyjson_val *root = yyjson_doc_get_root(doc); yyjson_val *type = (root && yyjson_is_obj(root)) ? yyjson_obj_get(root, "type") : NULL; if (!type || !yyjson_is_str(type)) { yyjson_doc_free(doc); return false; } const char *type_str = yyjson_get_str(type); if (has_type) *has_type = true; bool is_module = type_str && strcmp(type_str, "module") == 0; yyjson_doc_free(doc); return is_module; } static bool esm_lookup_package_type_module(const char *resolved_path, bool *is_module) { if (is_module) *is_module = false; if (!resolved_path || !resolved_path[0]) return false; char path_copy[PATH_MAX]; snprintf(path_copy, sizeof(path_copy), "%s", resolved_path); char *dir = dirname(path_copy); if (!dir || !dir[0]) return false; char current[PATH_MAX]; snprintf(current, sizeof(current), "%s", dir); while (true) { char pkg_json_path[PATH_MAX]; snprintf(pkg_json_path, sizeof(pkg_json_path), "%s/package.json", current); struct stat st; if (stat(pkg_json_path, &st) == 0 && S_ISREG(st.st_mode)) { bool has_type = false; bool pkg_is_module = esm_read_package_json_type_module(pkg_json_path, &has_type); if (is_module) *is_module = has_type && pkg_is_module; return true; } if (strcmp(current, "/") == 0) break; char *slash = strrchr(current, '/'); if (!slash) break; if (slash == current) current[1] = '\0'; else *slash = '\0'; } return false; } static ant_module_format_t esm_decide_module_format(const char *resolved_path) { if (!resolved_path || !resolved_path[0]) return MODULE_EVAL_FORMAT_ESM; if (esm_is_cjs_extension(resolved_path)) return MODULE_EVAL_FORMAT_CJS; if (esm_is_esm_extension(resolved_path)) return MODULE_EVAL_FORMAT_ESM; if (esm_has_suffix(resolved_path, ".js")) { bool pkg_is_module = false; bool has_package_json = esm_lookup_package_type_module(resolved_path, &pkg_is_module); if (!has_package_json) return MODULE_EVAL_FORMAT_CJS; return pkg_is_module ? MODULE_EVAL_FORMAT_ESM : MODULE_EVAL_FORMAT_CJS; } return MODULE_EVAL_FORMAT_ESM; } static ant_value_t esm_eval_module_with_format( ant_t *js, const char *resolved_path, const char *js_code, size_t js_len, ant_value_t ns, ant_module_format_t format ) { if (format == MODULE_EVAL_FORMAT_CJS) { return esm_load_commonjs_module(js, resolved_path, js_code, js_len, ns); } return js_eval_bytecode_module(js, js_code, js_len); } ant_value_t js_esm_eval_module_source( ant_t *js, const char *resolved_path, const char *js_code, size_t js_len, ant_value_t ns ) { ant_module_format_t format = esm_decide_module_format(resolved_path); ant_module_t eval_ctx; ant_value_t prep_res = esm_prepare_eval_ctx( js, resolved_path, ns, format, js->module == NULL, NULL, &eval_ctx ); if (is_err(prep_res)) return prep_res; js_module_eval_ctx_push(js, &eval_ctx); ant_value_t result = esm_eval_module_with_format( js, resolved_path, js_code, js_len, ns, format ); js_module_eval_ctx_pop(js, &eval_ctx); return result; } static esm_module_kind_t esm_classify_module_kind(const char *resolved_path) { if (esm_is_data_url(resolved_path)) return ESM_MODULE_KIND_URL; if (esm_is_url(resolved_path)) return ESM_MODULE_KIND_URL; if (esm_is_json(resolved_path)) return ESM_MODULE_KIND_JSON; if (esm_is_text(resolved_path)) return ESM_MODULE_KIND_TEXT; if (esm_is_image(resolved_path)) return ESM_MODULE_KIND_IMAGE; if (esm_is_native(resolved_path)) return ESM_MODULE_KIND_NATIVE; return ESM_MODULE_KIND_CODE; } static char *esm_canonicalize_path(const char *path) { if (!path) return NULL; char *canonical = strdup(path); if (!canonical) return NULL; char *src = canonical, *dst = canonical; while (*src) { if (*src == '/') { *dst++ = '/'; while (*src == '/') src++; if (strncmp(src, "./", 2) == 0) { src += 2; } else if (strncmp(src, "../", 3) == 0) { src += 3; if (dst > canonical + 1) { dst--; while (dst > canonical && *(dst - 1) != '/') dst--; } } } else { *dst++ = *src++; } } *dst = '\0'; if (strlen(canonical) > 1 && canonical[strlen(canonical) - 1] == '/') { canonical[strlen(canonical) - 1] = '\0'; } return canonical; } static esm_module_t *esm_find_module(const char *module_key) { char *cache_key = esm_make_cache_key(module_key); if (!cache_key) return NULL; esm_module_t *mod = NULL; HASH_FIND_STR(global_module_cache.modules, cache_key, mod); free(cache_key); return mod; } static esm_module_t *esm_create_module( const char *path, const char *resolved_path, const char *module_key, ant_module_format_t format, const uint8_t *embedded_code, size_t embedded_code_len ) { char *cache_key = esm_make_cache_key(module_key); if (!cache_key) return NULL; esm_module_t *existing_mod = NULL; HASH_FIND_STR(global_module_cache.modules, cache_key, existing_mod); if (existing_mod) { free(cache_key); return existing_mod; } esm_module_t *mod = (esm_module_t *)malloc(sizeof(esm_module_t)); if (!mod) { free(cache_key); return NULL; } *mod = (esm_module_t){ .path = strdup(path), .cache_key = cache_key, .resolved_path = strdup(resolved_path), .namespace_obj = js_mkundef(), .default_export = js_mkundef(), .is_loaded = false, .is_loading = false, .kind = esm_classify_module_kind(resolved_path), .format = format, .url_content = NULL, .url_content_len = 0, .embedded_code = embedded_code, .embedded_code_len = embedded_code_len, .tla_promise = js_mkundef(), .has_tla = false, }; if (!mod->path || !mod->resolved_path) { free(mod->path); free(mod->cache_key); free(mod->resolved_path); free(mod); return NULL; } HASH_ADD_STR(global_module_cache.modules, cache_key, mod); global_module_cache.count++; return mod; } void js_esm_cleanup_module_cache(void) { esm_module_t *current, *tmp; HASH_ITER(hh, global_module_cache.modules, current, tmp) { HASH_DEL(global_module_cache.modules, current); if (current->path) free(current->path); if (current->cache_key) free(current->cache_key); if (current->resolved_path) free(current->resolved_path); if (current->url_content) free(current->url_content); free(current); } global_module_cache.count = 0; } static ant_value_t esm_read_file(ant_t *js, const char *path, const char *kind, esm_file_data_t *out) { FILE *fp = fopen(path, "rb"); if (!fp) return js_mkerr(js, "Cannot open %s: %s", kind, path); fseek(fp, 0, SEEK_END); long fsize = ftell(fp); fseek(fp, 0, SEEK_SET); char *buf = (char *)malloc((size_t)fsize + 1); if (!buf) { fclose(fp); return js_mkerr(js, "OOM loading %s", kind); } fread(buf, 1, (size_t)fsize, fp); fclose(fp); buf[fsize] = '\0'; out->data = buf; out->size = (size_t)fsize; return js_mkundef(); } static ant_value_t esm_load_json(ant_t *js, const char *path) { esm_file_data_t file; ant_value_t err = esm_read_file(js, path, "JSON file", &file); if (is_err(err)) return err; ant_value_t json_str = js_mkstr(js, file.data, file.size); free(file.data); return json_parse_value(js, json_str); } static ant_value_t esm_load_text(ant_t *js, const char *path) { esm_file_data_t file; ant_value_t err = esm_read_file(js, path, "text file", &file); if (is_err(err)) return err; ant_value_t result = js_mkstr(js, file.data, file.size); free(file.data); return result; } static ant_value_t esm_load_image(ant_t *js, const char *path) { esm_file_data_t file; ant_value_t err = esm_read_file(js, path, "image file", &file); if (is_err(err)) return err; unsigned char *content = (unsigned char *)file.data; size_t size = file.size; ant_value_t obj = js_mkobj(js); ant_value_t data_arr = js_mkarr(js); for (size_t i = 0; i < size; i++) { js_arr_push(js, data_arr, tov((double)content[i])); } js_setprop(js, obj, js_mkstr(js, "data", 4), data_arr); js_setprop(js, obj, js_mkstr(js, "path", 4), js_mkstr(js, path, strlen(path))); js_setprop(js, obj, js_mkstr(js, "size", 4), tov((double)size)); free(file.data); return obj; } static ant_value_t esm_load_module(ant_t *js, esm_module_t *mod) { if (mod->is_loaded) return mod->namespace_obj; if (mod->is_loading) return mod->namespace_obj; mod->is_loading = true; switch (mod->kind) { case ESM_MODULE_KIND_JSON: { ant_value_t json_val = esm_load_json(js, mod->resolved_path); return esm_complete_value_module(mod, json_val); } case ESM_MODULE_KIND_TEXT: { ant_value_t text_val = esm_load_text(js, mod->resolved_path); return esm_complete_value_module(mod, text_val); } case ESM_MODULE_KIND_IMAGE: { ant_value_t img_val = esm_load_image(js, mod->resolved_path); return esm_complete_value_module(mod, img_val); } case ESM_MODULE_KIND_NATIVE: { ant_value_t ns = esm_make_namespace_object(js); mod->namespace_obj = ns; ant_value_t module_ctx = js_create_module_context(js, mod->resolved_path, false); if (is_err(module_ctx)) { mod->is_loading = false; return module_ctx; } js_set_slot_wb(js, ns, SLOT_MODULE_CTX, module_ctx); ant_value_t native_exports = napi_load_native_module(js, mod->resolved_path, ns); if (is_err(native_exports)) { mod->is_loading = false; return native_exports; } return esm_complete_namespace_module(js, mod, ns); } case ESM_MODULE_KIND_CODE: case ESM_MODULE_KIND_URL: break; } char *content = NULL; size_t size = 0; if (mod->embedded_code) { content = (char *)malloc(mod->embedded_code_len + 1); if (!content) { mod->is_loading = false; return js_mkerr(js, "OOM loading bundled module"); } memcpy(content, mod->embedded_code, mod->embedded_code_len); size = mod->embedded_code_len; } else if (mod->kind == ESM_MODULE_KIND_URL && esm_is_data_url(mod->resolved_path)) { content = esm_parse_data_url(mod->resolved_path, &size); if (!content) { mod->is_loading = false; return js_mkerr(js, "Cannot parse data URL module"); } } else if (mod->kind == ESM_MODULE_KIND_URL) { if (mod->url_content) { content = strdup(mod->url_content); size = mod->url_content_len; } else { char *error = NULL; content = esm_fetch_url(mod->resolved_path, &size, &error); if (!content) { mod->is_loading = false; ant_value_t err = js_mkerr(js, "Cannot fetch module %s: %s", mod->resolved_path, error ? error : "unknown error"); if (error) free(error); return err; } mod->url_content = strdup(content); mod->url_content_len = size; } } else { esm_file_data_t file; ant_value_t err = esm_read_file(js, mod->resolved_path, "module", &file); if (is_err(err)) { mod->is_loading = false; return err; } content = file.data; size = file.size; } content[size] = '\0'; size_t js_len = size; const char *strip_detail = NULL; if (!mod->embedded_code) { int strip_result = strip_typescript_inplace( &content, size, mod->resolved_path, mod->format != MODULE_EVAL_FORMAT_CJS, &js_len, &strip_detail ); if (strip_result < 0) { ant_value_t err = js_mkerr( js, "TypeScript error: strip failed (%d): %s", strip_result, strip_detail ); free(content); mod->is_loading = false; return err; }} char *js_code = content; ant_value_t ns = esm_make_namespace_object(js); mod->namespace_obj = ns; const char *prev_filename = js->filename; ant_module_t eval_ctx; ant_value_t prep_res = esm_prepare_eval_ctx( js, mod->resolved_path, ns, mod->format, false, prev_filename, &eval_ctx ); if (is_err(prep_res)) { free(content); mod->is_loading = false; return prep_res; } js_set_filename(js, mod->resolved_path); js_module_eval_ctx_push(js, &eval_ctx); if (mod->format == MODULE_EVAL_FORMAT_UNKNOWN) { mod->format = esm_decide_module_format(mod->resolved_path); eval_ctx.format = mod->format; } ant_value_t result = esm_eval_module_with_format( js, mod->resolved_path, js_code, js_len, ns, mod->format ); free(content); if (vtype(result) == T_PROMISE) { if (esm_dynamic_import_depth > 0) { mod->has_tla = true; mod->tla_promise = result; } else js_run_event_loop(js); } js_module_eval_ctx_pop(js, &eval_ctx); js_set_filename(js, prev_filename); if (is_err(result)) { mod->is_loading = false; return result; } return esm_complete_namespace_module(js, mod, ns); } static ant_value_t esm_get_or_load( ant_t *js, const char *specifier, const char *resolved_path, const char *module_key, ant_module_format_t format, const uint8_t *embedded_code, size_t embedded_code_len ) { esm_module_t *mod = esm_find_module(module_key); if (!mod) { mod = esm_create_module( specifier, resolved_path, module_key, format, embedded_code, embedded_code_len ); if (!mod) return js_mkerr(js, "Cannot create module"); } return esm_load_module(js, mod); } static const char *esm_default_base_path(ant_t *js) { const char *active = js_module_eval_active_filename(js); return (active && active[0]) ? active : "."; } ant_value_t js_esm_import_sync_cstr_from( ant_t *js, const char *specifier, size_t spec_len, const char *base_path ) { const ant_builtin_bundle_alias_t *bundle = NULL; const ant_builtin_bundle_module_t *module = NULL; char *spec_copy = strndup(specifier, spec_len); if (!spec_copy) return js_mkerr(js, "oom"); char *file_url_path = esm_file_url_to_path(js, spec_copy); if (file_url_path) { free(spec_copy); spec_copy = file_url_path; spec_len = strlen(spec_copy); } bundle = esm_lookup_builtin_alias(spec_copy, spec_len); if (bundle) { module = esm_lookup_builtin_module(bundle->module_id); if (!module) { free(spec_copy); return js_mkerr(js, "Invalid builtin module id"); } ant_value_t ns = esm_get_or_load( js, spec_copy, bundle->source_name, bundle->source_name, module->format, module->code, module->code_len ); free(spec_copy); return ns; } bool loaded = false; ant_value_t lib = js_esm_load_registered_library(js, spec_copy, spec_len, &loaded); if (loaded) { free(spec_copy); return lib; } if (!base_path || !base_path[0]) base_path = esm_default_base_path(js); char *resolved_path = esm_resolve(spec_copy, base_path, esm_resolve_path); if (!resolved_path) { ant_value_t err = js_mkerr(js, "Cannot resolve module: %s", spec_copy); free(spec_copy); return err; } ant_value_t ns = esm_get_or_load( js, spec_copy, resolved_path, resolved_path, MODULE_EVAL_FORMAT_UNKNOWN, NULL, 0 ); free(resolved_path); free(spec_copy); return ns; } ant_value_t js_esm_import_sync_cstr_from_require( ant_t *js, const char *specifier, size_t spec_len, const char *base_path ) { const ant_builtin_bundle_alias_t *bundle = NULL; const ant_builtin_bundle_module_t *module = NULL; char *spec_copy = strndup(specifier, spec_len); if (!spec_copy) return js_mkerr(js, "oom"); char *file_url_path = esm_file_url_to_path(js, spec_copy); if (file_url_path) { free(spec_copy); spec_copy = file_url_path; spec_len = strlen(spec_copy); } bundle = esm_lookup_builtin_alias(spec_copy, spec_len); if (bundle) { module = esm_lookup_builtin_module(bundle->module_id); if (!module) { free(spec_copy); return js_mkerr(js, "Invalid builtin module id"); } ant_value_t ns = esm_get_or_load( js, spec_copy, bundle->source_name, bundle->source_name, module->format, module->code, module->code_len ); free(spec_copy); return ns; } bool loaded = false; ant_value_t lib = js_esm_load_registered_library(js, spec_copy, spec_len, &loaded); if (loaded) { free(spec_copy); return lib; } if (!base_path || !base_path[0]) base_path = esm_default_base_path(js); char *resolved_path = esm_resolve(spec_copy, base_path, esm_resolve_path_require); if (!resolved_path) { ant_value_t err = js_mkerr(js, "Cannot resolve module: %s", spec_copy); free(spec_copy); return err; } ant_value_t ns = esm_get_or_load( js, spec_copy, resolved_path, resolved_path, MODULE_EVAL_FORMAT_UNKNOWN, NULL, 0 ); free(resolved_path); free(spec_copy); return ns; } ant_value_t js_esm_import_sync_cstr(ant_t *js, const char *specifier, size_t spec_len) { return js_esm_import_sync_cstr_from(js, specifier, spec_len, NULL); } ant_value_t js_esm_import_sync_from(ant_t *js, ant_value_t specifier, const char *base_path) { if (vtype(specifier) != T_STR) return js_mkerr(js, "import() requires a string specifier"); ant_offset_t spec_len = 0; ant_offset_t spec_off = vstr(js, specifier, &spec_len); const char *spec_str = (const char *)(uintptr_t)(spec_off); return js_esm_import_sync_cstr_from(js, spec_str, (size_t)spec_len, base_path); } ant_value_t js_esm_import_sync_from_require(ant_t *js, ant_value_t specifier, const char *base_path) { if (vtype(specifier) != T_STR) return js_mkerr(js, "require() expects a string specifier"); ant_offset_t spec_len = 0; ant_offset_t spec_off = vstr(js, specifier, &spec_len); const char *spec_str = (const char *)(uintptr_t)(spec_off); return js_esm_import_sync_cstr_from_require(js, spec_str, (size_t)spec_len, base_path); } ant_value_t js_esm_import_sync(ant_t *js, ant_value_t specifier) { return js_esm_import_sync_from(js, specifier, NULL); } ant_value_t js_esm_import_dynamic(ant_t *js, ant_value_t specifier, const char *base_path, ant_value_t *out_tla_promise) { *out_tla_promise = js_mkundef(); esm_dynamic_import_depth++; ant_value_t ns = js_esm_import_sync_from(js, specifier, base_path); esm_dynamic_import_depth--; if (is_err(ns)) return ns; esm_module_t *mod = NULL, *tmp = NULL; HASH_ITER(hh, global_module_cache.modules, mod, tmp) { if (mod->has_tla && mod->namespace_obj == ns) { *out_tla_promise = mod->tla_promise; mod->has_tla = false; mod->tla_promise = js_mkundef(); break; }} return ns; } ant_value_t js_esm_make_file_url(ant_t *js, const char *path) { size_t path_len = strlen(path); size_t raw_len = 7 + path_len; char *raw = malloc(raw_len + 1); if (!raw) return js_mkerr(js, "oom"); snprintf(raw, raw_len + 1, "file://%s", path); ant_value_t raw_val = js_mkstr(js, raw, raw_len); free(raw); return js_encodeURI(js, &raw_val, 1); } void gc_mark_esm(ant_t *js, gc_mark_fn mark) { esm_module_t *mod = NULL, *tmp = NULL; HASH_ITER(hh, global_module_cache.modules, mod, tmp) { mark(js, mod->namespace_obj); mark(js, mod->default_export); if (mod->has_tla) mark(js, mod->tla_promise); }} ant_value_t js_esm_resolve_specifier(ant_t *js, ant_value_t specifier, const char *base_path) { const ant_builtin_bundle_alias_t *bundle = NULL; if (vtype(specifier) != T_STR) { return js_mkerr(js, "import.meta.resolve() requires a string specifier"); } ant_offset_t spec_len = 0; ant_offset_t spec_off = vstr(js, specifier, &spec_len); const char *spec_str = (const char *)(uintptr_t)(spec_off); char *spec_copy = strndup(spec_str, (size_t)spec_len); if (!spec_copy) return js_mkerr(js, "oom"); bundle = esm_lookup_builtin_alias(spec_copy, (size_t)spec_len); if (bundle) { ant_value_t result = js_mkstr(js, bundle->source_name, strlen(bundle->source_name)); free(spec_copy); return result; } if (!base_path || !base_path[0]) base_path = esm_default_base_path(js); char *resolved_path = esm_resolve(spec_copy, base_path, esm_resolve_path); free(spec_copy); if (!resolved_path) { return js_mkerr(js, "Cannot resolve module"); } if (esm_is_url(resolved_path)) { ant_value_t result = js_mkstr(js, resolved_path, strlen(resolved_path)); free(resolved_path); return result; } ant_value_t result = js_esm_make_file_url(js, resolved_path); free(resolved_path); return result; } ant_value_t js_esm_resolve_specifier_require(ant_t *js, ant_value_t specifier, const char *base_path) { const ant_builtin_bundle_alias_t *bundle = NULL; if (vtype(specifier) != T_STR) { return js_mkerr(js, "require.resolve() expects a string specifier"); } ant_offset_t spec_len = 0; ant_offset_t spec_off = vstr(js, specifier, &spec_len); const char *spec_str = (const char *)(uintptr_t)(spec_off); char *spec_copy = strndup(spec_str, (size_t)spec_len); if (!spec_copy) return js_mkerr(js, "oom"); bundle = esm_lookup_builtin_alias(spec_copy, (size_t)spec_len); if (bundle) { ant_value_t result = js_mkstr(js, bundle->source_name, strlen(bundle->source_name)); free(spec_copy); return result; } if (!base_path || !base_path[0]) base_path = esm_default_base_path(js); char *resolved_path = esm_resolve(spec_copy, base_path, esm_resolve_path_require); free(spec_copy); if (!resolved_path) { return js_mkerr(js, "Cannot resolve module"); } if (esm_is_url(resolved_path)) { ant_value_t result = js_mkstr(js, resolved_path, strlen(resolved_path)); free(resolved_path); return result; } ant_value_t result = js_esm_make_file_url(js, resolved_path); free(resolved_path); return result; }