diff --git a/examples/npm/react/index.js b/examples/npm/react/index.js index ceed62c..93d2c1e 100644 --- a/examples/npm/react/index.js +++ b/examples/npm/react/index.js @@ -1,7 +1,8 @@ import { build } from 'esbuild'; +import { join } from 'node:path'; const { outputFiles } = await build({ - entryPoints: ['app.jsx'], + entryPoints: [join(import.meta.dirname, 'app.jsx')], bundle: true, write: false, jsx: 'automatic', diff --git a/include/esm/loader.h b/include/esm/loader.h index ada275c..3b8d344 100644 --- a/include/esm/loader.h +++ b/include/esm/loader.h @@ -6,19 +6,36 @@ #include #include +typedef enum { + MODULE_EVAL_FORMAT_UNKNOWN = 0, + MODULE_EVAL_FORMAT_ESM, + MODULE_EVAL_FORMAT_CJS, +} ant_module_format_t; + +typedef struct ant_module_t { + ant_value_t module_ns; + ant_value_t import_meta; + ant_value_t prev_import_meta_prop; + const char *filename; + const char *parent_path; + ant_module_format_t format; + struct ant_module_t *prev; +} ant_module_t; + void js_esm_cleanup_module_cache(void); void js_esm_gc_roots(void (*visit)(void *ctx, ant_value_t *val), void *ctx); ant_value_t js_esm_import_sync(ant_t *js, ant_value_t specifier); ant_value_t js_esm_make_file_url(ant_t *js, const char *path); +ant_value_t js_esm_import_sync_from(ant_t *js, ant_value_t specifier, const char *base_path); 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_t *js, const char *resolved_path, + const char *js_code, size_t js_len, ant_value_t ns ); ant_value_t js_esm_import_sync_cstr(ant_t *js, const char *specifier, size_t spec_len); ant_value_t js_esm_resolve_specifier(ant_t *js, ant_value_t specifier, const char *base_path); +ant_value_t js_esm_import_sync_cstr_from(ant_t *js, const char *specifier, size_t spec_len, const char *base_path); #endif diff --git a/include/internal.h b/include/internal.h index 20e36ee..51c941b 100644 --- a/include/internal.h +++ b/include/internal.h @@ -3,6 +3,7 @@ #include "ant.h" #include "gc.h" +#include "esm/loader.h" #include #include @@ -119,7 +120,8 @@ typedef struct { struct ant { sv_vm_t *vm; - + ant_module_t *module; + #ifdef ANT_JIT void *jit_ctx; #endif @@ -135,9 +137,6 @@ struct ant { ant_value_t this_val; ant_value_t new_target; ant_value_t current_func; - - ant_value_t module_ns; - ant_value_t import_meta; ant_value_t length_str; uint8_t *mem; @@ -231,7 +230,10 @@ ant_value_t resolveprop(ant_t *js, ant_value_t v); ant_value_t mkprop(ant_t *js, ant_value_t obj, ant_value_t k, ant_value_t v, ant_offset_t flags); ant_value_t setprop_cstr(ant_t *js, ant_value_t obj, const char *key, size_t len, ant_value_t v); ant_value_t setprop_interned(ant_t *js, ant_value_t obj, const char *key, size_t len, ant_value_t v); + ant_value_t js_define_own_prop(ant_t *js, ant_value_t obj, const char *key, size_t klen, ant_value_t v); +ant_value_t js_create_import_meta(ant_t *js, const char *filename, bool is_main); +ant_value_t js_instance_proto_from_new_target(ant_t *js, ant_value_t fallback_proto); ant_value_t coerce_to_str(ant_t *js, ant_value_t v); ant_value_t coerce_to_str_concat(ant_t *js, ant_value_t v); @@ -243,7 +245,6 @@ bool same_ctor_identity(ant_t *js, ant_value_t a, ant_value_t b); js_intern_stats_t js_intern_stats(void); js_cstr_t js_to_cstr(ant_t *js, ant_value_t value, char *stack_buf, size_t stack_size); -ant_value_t js_instance_proto_from_new_target(ant_t *js, ant_value_t fallback_proto); ant_offset_t lkp(ant_t *js, ant_value_t obj, const char *buf, size_t len); ant_offset_t lkp_proto(ant_t *js, ant_value_t obj, const char *buf, size_t len); @@ -278,4 +279,32 @@ ant_value_t js_to_primitive(ant_t *js, ant_value_t value, int hint); ant_value_t do_instanceof(ant_t *js, ant_value_t l, ant_value_t r); ant_value_t do_in(ant_t *js, ant_value_t l, ant_value_t r); +void js_module_eval_ctx_push(ant_t *js, ant_module_t *ctx); +void js_module_eval_ctx_pop(ant_t *js, ant_module_t *ctx); + +static inline ant_value_t js_module_eval_active_ns(ant_t *js) { + ant_module_t *ctx = js->module; + return ctx ? ctx->module_ns : js_mkundef(); +} + +static inline ant_value_t js_module_eval_active_import_meta(ant_t *js) { + ant_module_t *ctx = js->module; + return ctx ? ctx->import_meta : js_mkundef(); +} + +static inline const char *js_module_eval_active_filename(ant_t *js) { + ant_module_t *ctx = js->module; + return ctx ? ctx->filename : js->filename; +} + +static inline const char *js_module_eval_active_parent_path(ant_t *js) { + ant_module_t *ctx = js->module; + return ctx ? ctx->parent_path : NULL; +} + +static inline ant_module_format_t js_module_eval_active_format(ant_t *js) { + ant_module_t *ctx = js->module; + return ctx ? ctx->format : MODULE_EVAL_FORMAT_UNKNOWN; +} + #endif diff --git a/src/ant.c b/src/ant.c index 01ba296..a1b4a33 100644 --- a/src/ant.c +++ b/src/ant.c @@ -10521,10 +10521,38 @@ static ant_value_t builtin_import(ant_t *js, ant_value_t *args, int nargs) { return builtin_Promise_resolve(js, promise_args, 1); } +static ant_value_t js_get_import_meta_prop(ant_t *js) { + ant_value_t glob = js_glob(js); + ant_offset_t import_off = lkp(js, glob, "import", 6); + if (import_off == 0) return js_mkundef(); + + ant_value_t import_fn = resolveprop(js, mkval(T_PROP, import_off)); + if (vtype(import_fn) != T_FUNC) return js_mkundef(); + return js_get(js, js_func_obj(import_fn), "meta"); +} + +static void js_set_import_meta_prop(ant_t *js, ant_value_t import_meta) { + ant_value_t glob = js_glob(js); + ant_offset_t import_off = lkp(js, glob, "import", 6); + if (import_off == 0) return; + + ant_value_t import_fn = resolveprop(js, mkval(T_PROP, import_off)); + if (vtype(import_fn) != T_FUNC) return; + js_setprop(js, js_func_obj(import_fn), js_mkstr(js, "meta", 4), import_meta); +} + +static ant_value_t js_get_current_import_meta(ant_t *js) { + ant_value_t import_meta = js_module_eval_active_import_meta(js); + if (vtype(import_meta) == T_OBJ) return import_meta; + return js_get_import_meta_prop(js); +} + static ant_value_t builtin_import_meta_resolve(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "import.meta.resolve() requires a string specifier"); - if (vtype(js->import_meta) == T_OBJ) { - ant_value_t filename = js_get(js, js->import_meta, "filename"); + + ant_value_t import_meta = js_get_current_import_meta(js); + if (vtype(import_meta) == T_OBJ) { + ant_value_t filename = js_get(js, import_meta, "filename"); if (vtype(filename) == T_STR) { ant_offset_t n = 0; ant_offset_t off = vstr(js, filename, &n); return js_esm_resolve_specifier(js, args[0], (const char *)&js->mem[off]); @@ -10532,19 +10560,19 @@ static ant_value_t builtin_import_meta_resolve(ant_t *js, ant_value_t *args, int } return js_esm_resolve_specifier(js, args[0], NULL); } -void js_setup_import_meta(ant_t *js, const char *filename) { - if (!filename) return; - +ant_value_t js_create_import_meta(ant_t *js, const char *filename, bool is_main) { + if (!filename) return js_mkundef(); + ant_value_t import_meta = mkobj(js, 0); - if (is_err(import_meta)) return; + if (is_err(import_meta)) return import_meta; bool is_url = esm_is_url(filename); - + ant_value_t url_val = is_url ? js_mkstr(js, filename, strlen(filename)) : js_esm_make_file_url(js, filename); if (!is_err(url_val)) js_setprop(js, import_meta, js_mkstr(js, "url", 3), url_val); - + ant_value_t filename_val = js_mkstr(js, filename, strlen(filename)); if (!is_err(filename_val)) js_setprop(js, import_meta, js_mkstr(js, "filename", 8), filename_val); - + if (is_url) { char *filename_copy = strdup(filename); if (filename_copy) { @@ -10568,20 +10596,38 @@ void js_setup_import_meta(ant_t *js, const char *filename) { free(filename_copy); } } - - js_setprop(js, import_meta, js_mkstr(js, "main", 4), js_true); + + js_setprop(js, import_meta, js_mkstr(js, "main", 4), is_main ? js_true : js_false); ant_value_t resolve_fn = js_mkfun(builtin_import_meta_resolve); js_setprop(js, import_meta, js_mkstr(js, "resolve", 7), resolve_fn); - - ant_value_t glob = js_glob(js); - ant_offset_t import_off = lkp(js, glob, "import", 6); - - if (import_off != 0) { - ant_value_t import_fn = resolveprop(js, mkval(T_PROP, import_off)); - if (vtype(import_fn) == T_FUNC) { - ant_value_t import_obj = js_func_obj(import_fn); - js_setprop(js, import_obj, js_mkstr(js, "meta", 4), import_meta); - } + return import_meta; +} + +void js_setup_import_meta(ant_t *js, const char *filename) { + if (!filename) return; + + ant_value_t import_meta = js_create_import_meta(js, filename, true); + if (is_err(import_meta)) return; + js_set_import_meta_prop(js, import_meta); +} + +void js_module_eval_ctx_push(ant_t *js, ant_module_t *ctx) { + if (!js || !ctx) return; + + ctx->prev = js->module; + ctx->prev_import_meta_prop = js_get_import_meta_prop(js); + js->module = ctx; + + if (vtype(ctx->import_meta) != T_UNDEF) + js_set_import_meta_prop(js, ctx->import_meta); +} + +void js_module_eval_ctx_pop(ant_t *js, ant_module_t *ctx) { + if (!js || !ctx) return; + + if (js->module == ctx) { + js_set_import_meta_prop(js, ctx->prev_import_meta_prop); + js->module = ctx->prev; } } @@ -11431,9 +11477,6 @@ ant_t *js_create(void *buf, size_t len) { set_slot(js, import_obj, SLOT_CFUNC, js_mkfun(builtin_import)); js_setprop(js, glob, js_mkstr(js, "import", 6), js_obj_to_func(import_obj)); - js->module_ns = js_mkundef(); - js->import_meta = js_mkundef(); - js_setprop(js, object_proto, js_mkstr(js, "constructor", 11), obj_func); js_set_descriptor(js, object_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); @@ -11710,10 +11753,11 @@ static bool js_try_get(ant_t *js, ant_value_t obj, const char *key, ant_value_t } ant_value_t func_obj = js_func_obj(obj); - if (key_len == 4 && memcmp(key, "meta", 4) == 0 && vtype(js->import_meta) != T_UNDEF) { + ant_value_t import_meta = js_get_current_import_meta(js); + if (key_len == 4 && memcmp(key, "meta", 4) == 0 && vtype(import_meta) != T_UNDEF) { ant_value_t cfunc = js_get_slot(js, func_obj, SLOT_CFUNC); if (vtype(cfunc) == T_CFUNC && js_as_cfunc(cfunc) == builtin_import) { - *out = js->import_meta; + *out = import_meta; return true; } } @@ -12005,7 +12049,13 @@ static void gc_roots_common(gc_off_op_t op_off, gc_val_op_t op_val, gc_cb_ctx_t } js_esm_gc_roots(op_val, c); - op_val(c, &c->js->import_meta); + + for (ant_module_t *ctx = c->js->module; ctx; ctx = ctx->prev) { + op_val(c, &ctx->module_ns); + op_val(c, &ctx->import_meta); + op_val(c, &ctx->prev_import_meta_prop); + } + timer_gc_update_roots(op_val, c); ffi_gc_update_roots(op_val, c); fetch_gc_update_roots(op_val, c); @@ -12023,7 +12073,6 @@ static void gc_roots_common(gc_off_op_t op_off, gc_val_op_t op_val, gc_cb_ctx_t op_val(c, &c->js->object); op_val(c, &c->js->this_val); op_val(c, &c->js->new_target); - op_val(c, &c->js->module_ns); op_val(c, &c->js->current_func); op_val(c, &c->js->thrown_value); op_val(c, &c->js->length_str); diff --git a/src/esm/commonjs.c b/src/esm/commonjs.c index c4c73fd..a53ce5c 100644 --- a/src/esm/commonjs.c +++ b/src/esm/commonjs.c @@ -18,17 +18,15 @@ static ant_value_t esm_cjs_require(ant_t *js, ant_value_t *args, int nargs) { ant_value_t fn = js_getcurrentfunc(js); ant_value_t data = js_get_slot(js, fn, SLOT_DATA); - const char *prev_filename = js->filename; + const char *base_path = js_module_eval_active_filename(js); if (vtype(data) == T_STR) { ant_offset_t path_len = 0; ant_offset_t path_off = vstr(js, data, &path_len); - (void)path_len; - js_set_filename(js, (const char *)&js->mem[path_off]); + base_path = (const char *)&js->mem[path_off]; } - ant_value_t ns = js_esm_import_sync(js, args[0]); - js_set_filename(js, prev_filename); + ant_value_t ns = js_esm_import_sync_from(js, args[0], base_path); if (is_err(ns)) return ns; if (vtype(ns) == T_OBJ) { @@ -45,7 +43,7 @@ static ant_value_t esm_cjs_require_resolve(ant_t *js, ant_value_t *args, int nar ant_value_t fn = js_getcurrentfunc(js); ant_value_t data = js_get_slot(js, fn, SLOT_DATA); - const char *base_path = js->filename ? js->filename : "."; + const char *base_path = js_module_eval_active_filename(js); if (vtype(data) == T_STR) { ant_offset_t data_len = 0; diff --git a/src/esm/loader.c b/src/esm/loader.c index 3128069..bf9a6b9 100644 --- a/src/esm/loader.c +++ b/src/esm/loader.c @@ -5,7 +5,6 @@ #include "esm/library.h" #include "esm/remote.h" -#include "silver/engine.h" #include "modules/json.h" #include "modules/napi.h" @@ -34,12 +33,6 @@ typedef enum { ESM_MODULE_KIND_URL, } esm_module_kind_t; -typedef enum { - ESM_MODULE_FORMAT_UNKNOWN = 0, - ESM_MODULE_FORMAT_ESM, - ESM_MODULE_FORMAT_CJS, -} esm_module_format_t; - typedef struct esm_module { char *path; char *resolved_path; @@ -49,7 +42,7 @@ typedef struct esm_module { ant_value_t default_export; UT_hash_handle hh; esm_module_kind_t kind; - esm_module_format_t format; + ant_module_format_t format; bool is_loaded; bool is_loading; } esm_module_t; @@ -67,18 +60,6 @@ typedef struct { static esm_module_cache_t global_module_cache = {NULL, 0}; static char *esm_resolve_node_module(const char *specifier, const char *base_path); -static ant_value_t esm_get_import_meta_raw(ant_t *js) { - ant_value_t import_fn = js_get(js, js->global, "import"); - if (vtype(import_fn) != T_FUNC) return js_mkundef(); - return js_get(js, js_func_obj(import_fn), "meta"); -} - -static void esm_set_import_meta_main_flag(ant_t *js, bool is_main) { - ant_value_t meta = esm_get_import_meta_raw(js); - if (vtype(meta) != T_OBJ) return; - js_set(js, meta, "main", is_main ? js_true : js_false); -} - static char *esm_file_url_to_path(const char *specifier) { if (!specifier || strncmp(specifier, "file:", 5) != 0) return NULL; @@ -697,19 +678,19 @@ static bool esm_lookup_package_type_module(const char *resolved_path, bool *is_m return false; } -static esm_module_format_t esm_decide_module_format(const char *resolved_path) { - if (!resolved_path || !resolved_path[0]) return ESM_MODULE_FORMAT_ESM; - if (esm_is_cjs_extension(resolved_path)) return ESM_MODULE_FORMAT_CJS; - if (esm_is_esm_extension(resolved_path)) return ESM_MODULE_FORMAT_ESM; +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") && esm_path_contains_node_modules(resolved_path)) { bool pkg_is_module = false; bool has_package_json = esm_lookup_package_type_module(resolved_path, &pkg_is_module); - if (!has_package_json) return ESM_MODULE_FORMAT_CJS; - return pkg_is_module ? ESM_MODULE_FORMAT_ESM : ESM_MODULE_FORMAT_CJS; + if (!has_package_json) return MODULE_EVAL_FORMAT_CJS; + return pkg_is_module ? MODULE_EVAL_FORMAT_ESM : MODULE_EVAL_FORMAT_CJS; } - return ESM_MODULE_FORMAT_ESM; + return MODULE_EVAL_FORMAT_ESM; } static ant_value_t esm_eval_module_with_format( @@ -718,9 +699,9 @@ static ant_value_t esm_eval_module_with_format( const char *js_code, size_t js_len, ant_value_t ns, - esm_module_format_t format + ant_module_format_t format ) { - if (format == ESM_MODULE_FORMAT_CJS) { + 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); @@ -728,20 +709,30 @@ static ant_value_t esm_eval_module_with_format( 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 + const char *resolved_path, const char *js_code, + size_t js_len, ant_value_t ns ) { - esm_module_format_t format = esm_decide_module_format(resolved_path); - ant_value_t prev_module = js->module_ns; - js->module_ns = ns; + ant_module_format_t format = esm_decide_module_format(resolved_path); + ant_module_t *parent_ctx = js->module; + ant_value_t import_meta = js_create_import_meta(js, resolved_path, parent_ctx == NULL); + if (is_err(import_meta)) return import_meta; + + ant_module_t eval_ctx = { + .module_ns = ns, + .import_meta = import_meta, + .prev_import_meta_prop = js_mkundef(), + .filename = resolved_path, + .parent_path = parent_ctx ? parent_ctx->filename : NULL, + .format = format, + .prev = NULL, + }; + 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_ns = prev_module; + + js_module_eval_ctx_pop(js, &eval_ctx); return result; } @@ -828,11 +819,11 @@ static esm_module_t *esm_create_module(const char *path, const char *resolved_pa .is_loaded = false, .is_loading = false, .kind = esm_classify_module_kind(resolved_path), - .format = ESM_MODULE_FORMAT_UNKNOWN, + .format = MODULE_EVAL_FORMAT_UNKNOWN, .url_content = NULL, .url_content_len = 0, }; - + HASH_ADD_STR(global_module_cache.modules, resolved_path, mod); global_module_cache.count++; @@ -1049,21 +1040,33 @@ static ant_value_t esm_load_module(ant_t *js, esm_module_t *mod) { char *js_code = content; ant_value_t ns = js_mkobj(js); mod->namespace_obj = ns; - - ant_value_t prev_module = js->module_ns; - js->module_ns = ns; const char *prev_filename = js->filename; - ant_handle_t prev_import_meta_h = js_root(js, js->import_meta); - + ant_module_t *parent_ctx = js->module; + + ant_value_t import_meta = js_create_import_meta(js, mod->resolved_path, false); + if (is_err(import_meta)) { + free(content); + mod->is_loading = false; + return import_meta; + } + + ant_module_t eval_ctx = { + .module_ns = ns, + .import_meta = import_meta, + .prev_import_meta_prop = js_mkundef(), + .filename = mod->resolved_path, + .parent_path = parent_ctx ? parent_ctx->filename : prev_filename, + .format = mod->format, + .prev = NULL, + }; + js_set_filename(js, mod->resolved_path); - js_setup_import_meta(js, mod->resolved_path); - - esm_set_import_meta_main_flag(js, false); - js->import_meta = esm_get_import_meta_raw(js); + js_module_eval_ctx_push(js, &eval_ctx); - if (mod->format == ESM_MODULE_FORMAT_UNKNOWN) { + 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( @@ -1073,10 +1076,8 @@ static ant_value_t esm_load_module(ant_t *js, esm_module_t *mod) { free(content); if (vtype(result) == T_PROMISE) js_run_event_loop(js); - js->import_meta = js_deref(js, prev_import_meta_h); - js_unroot(js, prev_import_meta_h); + js_module_eval_ctx_pop(js, &eval_ctx); js_set_filename(js, prev_filename); - js->module_ns = prev_module; if (is_err(result)) { mod->is_loading = false; @@ -1101,7 +1102,17 @@ static ant_value_t esm_get_or_load(ant_t *js, const char *specifier, const char return esm_load_module(js, mod); } -ant_value_t js_esm_import_sync_cstr(ant_t *js, const char *specifier, size_t spec_len) { +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 +) { char *spec_copy = strndup(specifier, spec_len); if (!spec_copy) return js_mkerr(js, "oom"); @@ -1118,7 +1129,7 @@ ant_value_t js_esm_import_sync_cstr(ant_t *js, const char *specifier, size_t spe return lib; } - const char *base_path = js->filename ? js->filename : "."; + 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); @@ -1132,7 +1143,11 @@ ant_value_t js_esm_import_sync_cstr(ant_t *js, const char *specifier, size_t spe return ns; } -ant_value_t js_esm_import_sync(ant_t *js, ant_value_t specifier) { +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"); @@ -1140,7 +1155,11 @@ ant_value_t js_esm_import_sync(ant_t *js, ant_value_t specifier) { ant_offset_t spec_off = vstr(js, specifier, &spec_len); const char *spec_str = (const char *)&js->mem[spec_off]; - return js_esm_import_sync_cstr(js, spec_str, (size_t)spec_len); + return js_esm_import_sync_cstr_from(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_make_file_url(ant_t *js, const char *path) { @@ -1165,7 +1184,7 @@ ant_value_t js_esm_resolve_specifier(ant_t *js, ant_value_t specifier, const cha char *spec_copy = strndup(spec_str, (size_t)spec_len); if (!spec_copy) return js_mkerr(js, "oom"); - if (!base_path) base_path = js->filename ? js->filename : "."; + 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); diff --git a/src/main.c b/src/main.c index adb9ffc..778fa02 100644 --- a/src/main.c +++ b/src/main.c @@ -336,16 +336,9 @@ static int execute_module(ant_t *js, const char *filename) { js_set_filename(js, use_path); js_setup_import_meta(js, use_path); - ant_value_t import_fn = js_get(js, js->global, "import"); - ant_value_t module_ns = mkobj(js, 0); - - js->import_meta = (vtype(import_fn) == T_FUNC) - ? js_get(js, import_fn, "meta") - : js_mkundef(); - ant_value_t result = js_esm_eval_module_source( js, use_path, js_code, - js_len, module_ns + js_len, mkobj(js, 0) ); free(js_code); diff --git a/src/modules/module.c b/src/modules/module.c index 984d442..e4b2ff6 100644 --- a/src/modules/module.c +++ b/src/modules/module.c @@ -25,16 +25,15 @@ static ant_value_t builtin_createRequire_call(ant_t *js, ant_value_t *args, int ant_value_t fn = js_getcurrentfunc(js); ant_value_t data = js_get_slot(js, fn, SLOT_DATA); - const char *prev_filename = js->filename; + const char *base_path = js_module_eval_active_filename(js); if (vtype(data) == T_STR) { ant_offset_t plen = 0; ant_offset_t poff = vstr(js, data, &plen); - js_set_filename(js, (const char *)&js->mem[poff]); + base_path = (const char *)&js->mem[poff]; } - ant_value_t ns = js_esm_import_sync(js, args[0]); - js_set_filename(js, prev_filename); + ant_value_t ns = js_esm_import_sync_from(js, args[0], base_path); if (is_err(ns)) return ns; if (vtype(ns) == T_OBJ) { @@ -51,7 +50,7 @@ static ant_value_t builtin_createRequire_resolve(ant_t *js, ant_value_t *args, i ant_value_t fn = js_getcurrentfunc(js); ant_value_t data = js_get_slot(js, fn, SLOT_DATA); - const char *base_path = js->filename ? js->filename : "."; + const char *base_path = js_module_eval_active_filename(js); if (vtype(data) == T_STR) { ant_offset_t dlen = 0; @@ -114,7 +113,7 @@ static ant_value_t builtin_resolveFilename(ant_t *js, ant_value_t *args, int nar if (nargs < 1 || vtype(args[0]) != T_STR) return js_mkerr(js, "Module._resolveFilename() requires a string request"); - const char *base_path = js->filename ? js->filename : "."; + const char *base_path = js_module_eval_active_filename(js); if (nargs >= 2 && vtype(args[1]) == T_OBJ) { ant_value_t parent_filename = js_get(js, args[1], "filename"); if (vtype(parent_filename) == T_STR) { diff --git a/src/silver/ops/coercion.h b/src/silver/ops/coercion.h index a108084..03811b8 100644 --- a/src/silver/ops/coercion.h +++ b/src/silver/ops/coercion.h @@ -15,14 +15,15 @@ static inline ant_value_t sv_module_export_cstr( const char *name, size_t len, ant_value_t value ) { - if (vtype(js->module_ns) != T_OBJ) + ant_value_t module_ns = js_module_eval_active_ns(js); + if (vtype(module_ns) != T_OBJ) return js_mkerr_typed(js, JS_ERR_SYNTAX, "export used outside module"); - ant_value_t set_res = setprop_cstr(js, js->module_ns, name, len, value); + ant_value_t set_res = setprop_cstr(js, module_ns, name, len, value); if (is_err(set_res)) return set_res; if (len == 7 && memcmp(name, "default", 7) == 0) - js_set_slot(js, js->module_ns, SLOT_DEFAULT, value); + js_set_slot(js, module_ns, SLOT_DEFAULT, value); return tov(0); } diff --git a/tests/cjs_accessor_module.cjs b/tests/cjs_accessor_module.cjs new file mode 100644 index 0000000..c3d23d8 --- /dev/null +++ b/tests/cjs_accessor_module.cjs @@ -0,0 +1,16 @@ +let getterCalls = 0; + +Object.defineProperty(module.exports, "answer", { + enumerable: true, + get() { + getterCalls += 1; + return 42; + }, +}); + +Object.defineProperty(module.exports, "getterCalls", { + enumerable: true, + get() { + return getterCalls; + }, +}); diff --git a/tests/cjs_esbuild_like_module.cjs b/tests/cjs_esbuild_like_module.cjs new file mode 100644 index 0000000..904df46 --- /dev/null +++ b/tests/cjs_esbuild_like_module.cjs @@ -0,0 +1,13 @@ +function build(options) { + return { ok: true, options: options ?? null }; +} + +function transform(source) { + return String(source).toUpperCase(); +} + +module.exports = { + build, + transform, + version: "0.0-test", +}; diff --git a/tests/cjs_esm_circular_bridge.cjs b/tests/cjs_esm_circular_bridge.cjs new file mode 100644 index 0000000..9025f20 --- /dev/null +++ b/tests/cjs_esm_circular_bridge.cjs @@ -0,0 +1,9 @@ +const esm = require("./cjs_esm_circular_entry.mjs"); + +exports.cjsValue = "cjs-value"; +Object.defineProperty(exports, "esmValueSeen", { + enumerable: true, + get() { + return esm.esmValue; + }, +}); diff --git a/tests/cjs_esm_circular_entry.mjs b/tests/cjs_esm_circular_entry.mjs new file mode 100644 index 0000000..d68b583 --- /dev/null +++ b/tests/cjs_esm_circular_entry.mjs @@ -0,0 +1,4 @@ +import bridge from "./cjs_esm_circular_bridge.cjs"; + +export const esmValue = "esm-value"; +export const fromCjs = bridge.cjsValue; diff --git a/tests/module_eval_reentrant_leaf.mjs b/tests/module_eval_reentrant_leaf.mjs new file mode 100644 index 0000000..5bb3f93 --- /dev/null +++ b/tests/module_eval_reentrant_leaf.mjs @@ -0,0 +1 @@ +export const leaf = "leaf-ok"; diff --git a/tests/module_eval_reentrant_mid.mjs b/tests/module_eval_reentrant_mid.mjs new file mode 100644 index 0000000..b442f9e --- /dev/null +++ b/tests/module_eval_reentrant_mid.mjs @@ -0,0 +1,4 @@ +import { leaf } from "./module_eval_reentrant_leaf.mjs"; + +export const mid = "mid-ok"; +export const fromLeaf = leaf; diff --git a/tests/module_eval_reentrant_root.mjs b/tests/module_eval_reentrant_root.mjs new file mode 100644 index 0000000..3c10001 --- /dev/null +++ b/tests/module_eval_reentrant_root.mjs @@ -0,0 +1,8 @@ +import { + mid as midValue, + fromLeaf as leafValue, +} from "./module_eval_reentrant_mid.mjs"; + +export const root = "root-ok"; +export const fromMid = midValue; +export const fromLeaf = leafValue; diff --git a/tests/test_cjs_accessor_exports.mjs b/tests/test_cjs_accessor_exports.mjs new file mode 100644 index 0000000..9aad5e5 --- /dev/null +++ b/tests/test_cjs_accessor_exports.mjs @@ -0,0 +1,14 @@ +import * as ns from "./cjs_accessor_module.cjs"; +import def from "./cjs_accessor_module.cjs"; + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +assert(ns.answer === 42, "namespace accessor export should resolve to 42"); +assert(ns.getterCalls >= 1, "accessor getter should have been invoked"); + +assert(def.answer === 42, "default CJS export accessor should resolve to 42"); +assert(ns.default === def, "namespace.default should match default import"); + +console.log("test_cjs_accessor_exports: OK"); diff --git a/tests/test_cjs_esbuild_named_exports.mjs b/tests/test_cjs_esbuild_named_exports.mjs new file mode 100644 index 0000000..3783f71 --- /dev/null +++ b/tests/test_cjs_esbuild_named_exports.mjs @@ -0,0 +1,19 @@ +import * as ns from "./cjs_esbuild_like_module.cjs"; +import def from "./cjs_esbuild_like_module.cjs"; + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +assert(typeof ns.build === "function", "missing named build export"); +assert(typeof ns.transform === "function", "missing named transform export"); +assert(ns.version === "0.0-test", "missing named version export"); + +const buildResult = ns.build({ minify: false }); +assert(buildResult && buildResult.ok === true, "named build export returned wrong value"); +assert(ns.transform("ab") === "AB", "named transform export returned wrong value"); + +assert(def.build === ns.build, "default and named build exports diverged"); +assert(ns.default === def, "namespace.default should match default import"); + +console.log("test_cjs_esbuild_named_exports: OK"); diff --git a/tests/test_cjs_esm_circular.mjs b/tests/test_cjs_esm_circular.mjs new file mode 100644 index 0000000..eade629 --- /dev/null +++ b/tests/test_cjs_esm_circular.mjs @@ -0,0 +1,21 @@ +import * as esmNs from "./cjs_esm_circular_entry.mjs"; +import cjsDefault from "./cjs_esm_circular_bridge.cjs"; +import * as cjsNs from "./cjs_esm_circular_bridge.cjs"; + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +assert(esmNs.esmValue === "esm-value", "esm circular export mismatch"); +assert(esmNs.fromCjs === "cjs-value", "esm should read cjs export in circular edge"); + +assert(cjsDefault.cjsValue === "cjs-value", "cjs default export mismatch"); +assert(cjsDefault.esmValueSeen === "esm-value", "cjs getter should see resolved esm export"); + +assert(cjsNs.default === cjsDefault, "namespace.default should match cjs default import"); +assert( + cjsNs.esmValueSeen === "esm-value" || cjsNs.esmValueSeen === undefined, + "cjs named accessor export should be stable under circular loading" +); + +console.log("test_cjs_esm_circular: OK"); diff --git a/tests/test_module_eval_reentrancy.mjs b/tests/test_module_eval_reentrancy.mjs new file mode 100644 index 0000000..c4b4457 --- /dev/null +++ b/tests/test_module_eval_reentrancy.mjs @@ -0,0 +1,22 @@ +import * as root from "./module_eval_reentrant_root.mjs"; +import * as mid from "./module_eval_reentrant_mid.mjs"; +import * as leaf from "./module_eval_reentrant_leaf.mjs"; + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +assert(root.root === "root-ok", "root export mismatch"); +assert(root.fromMid === "mid-ok", "root->mid export mismatch"); +assert(root.fromLeaf === "leaf-ok", "root->leaf export mismatch"); + +assert(mid.mid === "mid-ok", "mid export mismatch"); +assert(mid.fromLeaf === "leaf-ok", "mid->leaf export mismatch"); + +assert(leaf.leaf === "leaf-ok", "leaf export mismatch"); + +assert(root.mid === undefined, "root namespace was polluted by mid exports"); +assert(mid.root === undefined, "mid namespace was polluted by root exports"); +assert(leaf.root === undefined, "leaf namespace was polluted by root exports"); + +console.log("test_module_eval_reentrancy: OK");