From 713c813be029768cb53c0fb59b18095f1e3cedb2 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 4 Nov 2025 01:55:48 +0100 Subject: [PATCH 1/3] src: move import.meta initializer to native land When the source text module is compiled without custom callbacks, instead of calling into JS land from the per-isolate import.meta initializer and then back to C++ land to set up lazy data properties, just do the initialization all in C++ land. Only import.meta.resolve initialization will call back into JS land to generate a closure that call the cascaded loader for resolution. In addition, simplify the loader structure by merging allowImportMetaResolve into isForAsyncLoaderHookWorker - the two are essentially equivalent, as import.meta.resolve is only allowed in a non-loader-hook worker thread's loader. --- lib/internal/modules/esm/hooks.js | 22 --- .../modules/esm/initialize_import_meta.js | 83 -------- lib/internal/modules/esm/loader.js | 81 +++++--- lib/internal/modules/esm/utils.js | 19 +- lib/internal/modules/package_json_reader.js | 2 +- src/env_properties.h | 1 + src/module_wrap.cc | 178 ++++++++++++++++++ src/module_wrap.h | 2 + src/node_modules.cc | 72 ------- 9 files changed, 242 insertions(+), 218 deletions(-) delete mode 100644 lib/internal/modules/esm/initialize_import_meta.js diff --git a/lib/internal/modules/esm/hooks.js b/lib/internal/modules/esm/hooks.js index 462a5dd7656984..78ffb5f834a989 100644 --- a/lib/internal/modules/esm/hooks.js +++ b/lib/internal/modules/esm/hooks.js @@ -58,7 +58,6 @@ const { let debug = require('internal/util/debuglog').debuglog('async_loader_worker', (fn) => { debug = fn; }); -let importMetaInitializer; let importAssertionAlreadyWarned = false; @@ -111,7 +110,6 @@ function defineImportAssertionAlias(context) { * Interface for classes that implement asynchronous loader hooks that can be attached to the ModuleLoader * via `ModuleLoader.#setAsyncLoaderHooks()`. * @typedef {object} AsyncLoaderHooks - * @property {boolean} allowImportMetaResolve Whether to allow the use of `import.meta.resolve`. * @property {boolean} isForAsyncLoaderHookWorker Whether the instance is running on the loader hook worker thread. * @property {(url: string, context: object, defaultLoad: Function) => Promise} load * Calling the asynchronous `load` hook asynchronously. @@ -163,8 +161,6 @@ class AsyncLoaderHooksOnLoaderHookWorker { // Cache URLs we've already validated to avoid repeated validation #validatedUrls = new SafeSet(); - allowImportMetaResolve = false; - isForAsyncLoaderHookWorker = true; /** @@ -480,12 +476,6 @@ class AsyncLoaderHooksOnLoaderHookWorker { waitForLoaderHookInitialization() { // No-op } - - importMetaInitialize(meta, context, loader) { - importMetaInitializer ??= require('internal/modules/esm/initialize_import_meta').initializeImportMeta; - meta = importMetaInitializer(meta, context, loader); - return meta; - } } ObjectSetPrototypeOf(AsyncLoaderHooksOnLoaderHookWorker.prototype, null); @@ -674,12 +664,6 @@ class AsyncLoaderHookWorker { return body; } } - - #importMetaInitializer = require('internal/modules/esm/initialize_import_meta').initializeImportMeta; - - importMetaInitialize(meta, context, loader) { - this.#importMetaInitializer(meta, context, loader); - } } ObjectSetPrototypeOf(AsyncLoaderHookWorker.prototype, null); @@ -817,8 +801,6 @@ function getAsyncLoaderHookWorker() { */ class AsyncLoaderHooksProxiedToLoaderHookWorker { - allowImportMetaResolve = true; - isForAsyncLoaderHookWorker = false; /** @@ -876,10 +858,6 @@ class AsyncLoaderHooksProxiedToLoaderHookWorker { return asyncLoaderHookWorker.makeSyncRequest('load', undefined, url, context); } - importMetaInitialize(meta, context, loader) { - asyncLoaderHookWorker.importMetaInitialize(meta, context, loader); - } - waitForLoaderHookInitialization() { asyncLoaderHookWorker.waitForWorker(); } diff --git a/lib/internal/modules/esm/initialize_import_meta.js b/lib/internal/modules/esm/initialize_import_meta.js deleted file mode 100644 index 9646c7c8b674ce..00000000000000 --- a/lib/internal/modules/esm/initialize_import_meta.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -const { - StringPrototypeStartsWith, -} = primordials; - -const { getOptionValue } = require('internal/options'); -const { - setLazyPathHelpers, -} = internalBinding('modules'); - -const experimentalImportMetaResolve = getOptionValue('--experimental-import-meta-resolve'); - -/** - * Generate a function to be used as import.meta.resolve for a particular module. - * @param {string} defaultParentURL The default base to use for resolution - * @param {typeof import('./loader.js').ModuleLoader} loader Reference to the current module loader - * @param {bool} allowParentURL Whether to permit parentURL second argument for contextual resolution - * @returns {(specifier: string) => string} Function to assign to import.meta.resolve - */ -function createImportMetaResolve(defaultParentURL, loader, allowParentURL) { - /** - * @param {string} specifier - * @param {URL['href']} [parentURL] When `--experimental-import-meta-resolve` is specified, a - * second argument can be provided. - * @returns {string} - */ - return function resolve(specifier, parentURL = defaultParentURL) { - let url; - - if (!allowParentURL) { - parentURL = defaultParentURL; - } - - try { - const request = { specifier, __proto__: null }; - ({ url } = loader.resolveSync(parentURL, request)); - return url; - } catch (error) { - switch (error?.code) { - case 'ERR_UNSUPPORTED_DIR_IMPORT': - case 'ERR_MODULE_NOT_FOUND': - ({ url } = error); - if (url) { - return url; - } - } - throw error; - } - }; -} - -/** - * Create the `import.meta` object for a module. - * @param {object} meta - * @param {{url: string, isMain?: boolean}} context - * @param {typeof import('./loader.js').ModuleLoader} loader Reference to the current module loader - * @returns {{dirname?: string, filename?: string, url: string, resolve?: Function}} - */ -function initializeImportMeta(meta, context, loader) { - const { url, isMain } = context; - - // Alphabetical - if (StringPrototypeStartsWith(url, 'file:') === true) { - // dirname - // filename - setLazyPathHelpers(meta, url); - } - - meta.main = !!isMain; - - if (!loader || loader.allowImportMetaResolve) { - meta.resolve = createImportMetaResolve(url, loader, experimentalImportMetaResolve); - } - - meta.url = url; - - return meta; -} - -module.exports = { - initializeImportMeta, -}; diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index abfe88c272dcc4..d30c824ce387a7 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -51,6 +51,7 @@ const { kErrored, kSourcePhase, throwIfPromiseRejected, + setImportMetaResolveInitializer, } = internalBinding('module_wrap'); const { urlToFilename, @@ -62,7 +63,8 @@ const { loadWithHooks: loadWithSyncHooks, validateLoadSloppy, } = require('internal/modules/customization_hooks'); -let defaultResolve, defaultLoadSync, importMetaInitializer; + +let defaultResolve, defaultLoadSync; const { tracingChannel } = require('diagnostics_channel'); const onImport = tracingChannel('module.import'); @@ -183,13 +185,6 @@ class ModuleLoader { */ translators = getTranslators(); - /** - * Truthy to allow the use of `import.meta.resolve`. This is needed - * currently because the `Hooks` class does not have `resolveSync` - * implemented and `import.meta.resolve` requires it. - */ - allowImportMetaResolve; - /** * @see {AsyncLoaderHooks.isForAsyncLoaderHookWorker} * Shortcut to this.#asyncLoaderHooks.isForAsyncLoaderHookWorker. @@ -200,9 +195,10 @@ class ModuleLoader { * Asynchronous loader hooks to pass requests to. * * Note that this value _MUST_ be set with `#setAsyncLoaderHooks` - * because it needs to copy `#asyncLoaderHooks.allowImportMetaResolve` - * to this property and failure to do so will cause undefined - * behavior when invoking `import.meta.resolve`. + * because it needs to copy `#asyncLoaderHooks.isForAsyncLoaderHookWorker` + * to this property. + * TODO(joyeecheung): this was a legacy of the previous setup of import.meta.resolve + * configuration; put this information in the environment directly instead. * * When the ModuleLoader is created on a loader hook thread, this is * {@link AsyncLoaderHooksOnLoaderHookWorker}, and its methods directly call out @@ -234,10 +230,8 @@ class ModuleLoader { #setAsyncLoaderHooks(asyncLoaderHooks) { this.#asyncLoaderHooks = asyncLoaderHooks; if (asyncLoaderHooks) { - this.allowImportMetaResolve = asyncLoaderHooks.allowImportMetaResolve; this.isForAsyncLoaderHookWorker = asyncLoaderHooks.isForAsyncLoaderHookWorker; } else { - this.allowImportMetaResolve = true; this.isForAsyncLoaderHookWorker = false; } } @@ -821,15 +815,6 @@ class ModuleLoader { } } - importMetaInitialize(meta, context) { - if (this.#asyncLoaderHooks) { - return this.#asyncLoaderHooks.importMetaInitialize(meta, context, this); - } - importMetaInitializer ??= require('internal/modules/esm/initialize_import_meta').initializeImportMeta; - meta = importMetaInitializer(meta, context, this); - return meta; - } - /** * Block until the async loader hooks have been initialized. * @@ -883,8 +868,47 @@ function createModuleLoader(asyncLoaderHooks) { return new ModuleLoader(asyncLoaderHooks); } -let cascadedLoader; +let allowImportMetaResolveParentURL; +/** + * This is only called from the native ImportMetaObjectInitialize function to set up import.meta.resolve + * when import.meta.resolve is accessed for the first time in a module. + * @param {ModuleLoader} loader The cascaded loader to use. Bound when this function gets passed to native land. + * @param {string} moduleURL URL of the module accessing import.meta + * @returns {function(string, URL['href']=): string} The import.meta.resolve function + */ +function createImportMetaResolve(loader, moduleURL) { + /** + * @param {string} specifier The module specifier to resolve. + * @param {URL['href']} [parentURL] Optional parent URL to resolve against. Ignored unless + * `--experimental-import-meta-resolve` is enabled. + * @returns {string} + */ + return function resolve(specifier, parentURL) { + // The second argument is ignored unless --experimental-import-meta-resolve is enabled. + // Even then, if it's not provided, parentURL defaults to the url of the module accessing + // import.meta.resolve. + allowImportMetaResolveParentURL ??= getOptionValue('--experimental-import-meta-resolve'); + parentURL = allowImportMetaResolveParentURL ? (parentURL ?? moduleURL) : moduleURL; + + let url; + try { + ({ url } = loader.resolveSync(parentURL, { specifier, __proto__: null })); + return url; + } catch (error) { + switch (error?.code) { + case 'ERR_UNSUPPORTED_DIR_IMPORT': + case 'ERR_MODULE_NOT_FOUND': + ({ url } = error); + if (url) { + return url; + } + } + throw error; + } + }; +} +let cascadedLoader; /** * This is a singleton ESM loader that integrates the loader hooks, if any. * It it used by other internal built-ins when they need to load user-land ESM code @@ -898,7 +922,16 @@ let cascadedLoader; * @returns {ModuleLoader} */ function getOrInitializeCascadedLoader(asyncLoaderHooks) { - cascadedLoader ??= createModuleLoader(asyncLoaderHooks); + if (!cascadedLoader) { + cascadedLoader = createModuleLoader(asyncLoaderHooks); + // import.meta.resolve is not allowed in the async loader hook worker thread. + // So only set up the import.meta.resolve initializer when we are initializing + // the non-loader-hook-thread cascaded loader. When the native land doesn't see it, + // it knows the loader is running on the loader hook thread. + if (!(asyncLoaderHooks?.isForAsyncLoaderHookWorker)) { + setImportMetaResolveInitializer(createImportMetaResolve.bind(null, cascadedLoader)); + } + } return cascadedLoader; } diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js index 0af25ebbf6c3f2..27d96fae86d935 100644 --- a/lib/internal/modules/esm/utils.js +++ b/lib/internal/modules/esm/utils.js @@ -183,27 +183,14 @@ function registerModule(referrer, registry) { } /** - * Proxy the import meta handling to the default loader for source text modules. - * @param {Record} meta - The import.meta object to initialize. - * @param {ModuleWrap} wrap - The ModuleWrap of the SourceTextModule where `import.meta` is referenced. - * @returns {object} - */ -function defaultInitializeImportMetaForModule(meta, wrap) { - const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); - return cascadedLoader.importMetaInitialize(meta, { url: wrap.url, isMain: wrap.isMain }); -} - -/** - * Defines the `import.meta` object for a given module. + * Initializes the `import.meta` object for a given module. This is only called when the module + * is compiled with custom callbacks. Ordinary user-land source text modules are + * initialized by the native DefaultImportMetaObjectInitializer directly. * @param {symbol} symbol - Reference to the module. * @param {Record} meta - The import.meta object to initialize. * @param {ModuleWrap} wrap - The ModuleWrap of the SourceTextModule where `import.meta` is referenced. */ function initializeImportMetaObject(symbol, meta, wrap) { - if (symbol === source_text_module_default_hdo) { - defaultInitializeImportMetaForModule(meta, wrap); - return; - } const data = moduleRegistries.get(symbol); assert(data, `import.meta registry not found for ${wrap.url}`); const { initializeImportMeta, callbackReferrer } = data; diff --git a/lib/internal/modules/package_json_reader.js b/lib/internal/modules/package_json_reader.js index 43a131f57cb187..af8fcd28c5259e 100644 --- a/lib/internal/modules/package_json_reader.js +++ b/lib/internal/modules/package_json_reader.js @@ -350,7 +350,7 @@ function findPackageJSON(specifier, base = 'data:') { try { // TODO(@JakobJingleheimer): Detect whether findPackageJSON is being used within a loader - // (possibly piggyback on `allowImportMetaResolve`) + // (possibly piggyback on `isForAsyncLoaderHookWorker` from the loader?) and if so: // - When inside, use the default resolve // - (I think it's impossible to use the chain because of re-entry & a deadlock from atomics). // - When outside, use cascadedLoader.resolveSync (not implemented yet, but the pieces exist). diff --git a/src/env_properties.h b/src/env_properties.h index 114f23c9860dcf..5a83392120d096 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -470,6 +470,7 @@ V(enhance_fatal_stack_before_inspector, v8::Function) \ V(get_source_map_error_source, v8::Function) \ V(host_import_module_dynamically_callback, v8::Function) \ + V(host_import_meta_resolve_initializer, v8::Function) \ V(host_initialize_import_meta_object_callback, v8::Function) \ V(http2session_on_altsvc_function, v8::Function) \ V(http2session_on_error_function, v8::Function) \ diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 8870b643e0be55..d9711500f178c7 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -7,6 +7,7 @@ #include "node_external_reference.h" #include "node_internals.h" #include "node_process-inl.h" +#include "node_url.h" #include "node_watchdog.h" #include "util-inl.h" @@ -1207,6 +1208,167 @@ void ModuleWrap::SetImportModuleDynamicallyCallback( ImportModuleDynamicallyWithPhase); } +void ModuleWrap::SetImportMetaResolveInitializer( + const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Realm* realm = Realm::GetCurrent(args); + HandleScope handle_scope(isolate); + + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsFunction()); + Local initializer = args[0].As(); + realm->set_host_import_meta_resolve_initializer(initializer); +} + +static void ImportMetaResolveLazyGetter( + Local name, const PropertyCallbackInfo& info) { + Isolate* isolate = info.GetIsolate(); + Local receiver_val = info.This(); + if (!receiver_val->IsObject()) { + THROW_ERR_INVALID_INVOCATION(isolate); + return; + } + Local receiver = receiver_val.As(); + Local context; + if (!receiver->GetCreationContext().ToLocal(&context)) { + THROW_ERR_INVALID_INVOCATION(isolate); + return; + } + Realm* realm = Realm::GetCurrent(context); + if (realm == nullptr) { + THROW_ERR_INVALID_INVOCATION(isolate); + } + Local initializer = realm->host_import_meta_resolve_initializer(); + if (initializer.IsEmpty()) { + THROW_ERR_INVALID_INVOCATION(isolate); + return; + } + + // This should be createImportMetaResolve(). The loader argument is already + // bound at initialization time. + Local args[] = {info.Data()}; + Local ret; + if (!initializer + ->Call(context, Undefined(realm->isolate()), arraysize(args), args) + .ToLocal(&ret)) { + return; + } + info.GetReturnValue().Set(ret); +} + +static void PathHelpersLazyGetter(Local name, + const PropertyCallbackInfo& info) { + Isolate* isolate = info.GetIsolate(); + // This getter has no JavaScript function representation and is not + // invoked in the creation context. + // When this getter is invoked in a vm context, the `Realm::GetCurrent(info)` + // returns a nullptr and retrieve the creation context via `this` object and + // get the creation Realm. + Local receiver_val = info.This(); + if (!receiver_val->IsObject()) { + THROW_ERR_INVALID_INVOCATION(isolate); + return; + } + Local receiver = receiver_val.As(); + Local context; + if (!receiver->GetCreationContext().ToLocal(&context)) { + THROW_ERR_INVALID_INVOCATION(isolate); + return; + } + Environment* env = Environment::GetCurrent(context); + + node::Utf8Value url(isolate, info.Data()); + auto file_url = ada::parse(url.ToStringView()); + CHECK(file_url); + auto file_path = url::FileURLToPath(env, *file_url); + CHECK(file_path.has_value()); + std::string_view ret_view = file_path.value(); + + node::Utf8Value utf8name(isolate, name); + auto plain_name = utf8name.ToStringView(); + if (plain_name == "dirname") { +#ifdef _WIN32 +#define PATH_SEPARATOR '\\' +#else +#define PATH_SEPARATOR '/' +#endif + auto index = ret_view.rfind(PATH_SEPARATOR); + CHECK(index != std::string_view::npos); + ret_view.remove_suffix(ret_view.size() - index); +#undef PATH_SEPARATOR + } + Local ret; + if (!ToV8Value(context, ret_view, isolate).ToLocal(&ret)) { + return; + } + info.GetReturnValue().Set(ret); +} + +static Maybe DefaultImportMetaObjectInitializer(Realm* realm, + Local wrap, + Local meta) { + Local context = realm->context(); + Isolate* isolate = realm->isolate(); + Environment* env = realm->env(); + + Local url; + if (!wrap->Get(context, env->url_string()).ToLocal(&url)) { + return Nothing(); + } + + // N.B.: Order is important to keep keys in alphabetical order. + + Utf8Value url_utf8(isolate, url); + if (url_utf8.ToStringView().starts_with("file:")) { + // Set a lazy getter of import.meta.dirname + if (meta->SetLazyDataProperty( + context, env->dirname_string(), PathHelpersLazyGetter, url) + .IsNothing()) { + return Nothing(); + } + + // Set a lazy getter of import.meta.filename + if (meta->SetLazyDataProperty( + context, env->filename_string(), PathHelpersLazyGetter, url) + .IsNothing()) { + return Nothing(); + } + } + + // Set import.meta.main = moduleWrap.isMain + Local is_main; + if (!wrap->Get(context, FIXED_ONE_BYTE_STRING(isolate, "isMain")) + .ToLocal(&is_main)) { + return Nothing(); + } + if (meta->Set(context, + FIXED_ONE_BYTE_STRING(isolate, "main"), + Boolean::New(isolate, is_main->IsTrue())) + .IsEmpty()) { + return Nothing(); + } + + // Set a lazy getter of import.meta.resolve - only if the initializer is set, + // which is only the case when run on a non-loader-hook thread. + Local import_meta_resolve_initializer = + realm->host_import_meta_resolve_initializer(); + if (!import_meta_resolve_initializer.IsEmpty() && + meta->SetLazyDataProperty(context, + FIXED_ONE_BYTE_STRING(isolate, "resolve"), + ImportMetaResolveLazyGetter, + url) + .IsNothing()) { + return Nothing(); + } + + // Set import.meta.url = moduleWrap.url + if (meta->Set(context, env->url_string(), url).IsEmpty()) { + return Nothing(); + } + + return JustVoid(); +} + void ModuleWrap::HostInitializeImportMetaObjectCallback( Local context, Local module, Local meta) { Environment* env = Environment::GetCurrent(context); @@ -1232,8 +1394,19 @@ void ModuleWrap::HostInitializeImportMetaObjectCallback( return; } DCHECK(id->IsSymbol()); + + // Use the default initializer for source text modules without custom + // callbacks. + if (id == env->source_text_module_default_hdo()) { + USE(DefaultImportMetaObjectInitializer(realm, wrap, meta)); + return; + } + + // For modules that have custom callbacks, call into JS land + // to look up the callback from the registry. Local args[] = {id, meta, wrap}; TryCatchScope try_catch(env); + USE(callback->Call( context, Undefined(realm->isolate()), arraysize(args), args)); if (try_catch.HasCaught() && !try_catch.HasTerminated()) { @@ -1452,6 +1625,10 @@ void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data, target, "setInitializeImportMetaObjectCallback", SetInitializeImportMetaObjectCallback); + SetMethod(isolate, + target, + "setImportMetaResolveInitializer", + SetImportMetaResolveInitializer); SetMethod(isolate, target, "createRequiredModuleFacade", @@ -1505,6 +1682,7 @@ void ModuleWrap::RegisterExternalReferences( registry->Register(SetImportModuleDynamicallyCallback); registry->Register(SetInitializeImportMetaObjectCallback); + registry->Register(SetImportMetaResolveInitializer); registry->Register(ThrowIfPromiseRejected); } } // namespace loader diff --git a/src/module_wrap.h b/src/module_wrap.h index 50703dc0269a9f..4c18eef502b070 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -182,6 +182,8 @@ class ModuleWrap : public BaseObject { static void SetImportModuleDynamicallyCallback( const v8::FunctionCallbackInfo& args); + static void SetImportMetaResolveInitializer( + const v8::FunctionCallbackInfo& args); static void SetInitializeImportMetaObjectCallback( const v8::FunctionCallbackInfo& args); static v8::MaybeLocal SyntheticModuleEvaluationStepsCallback( diff --git a/src/node_modules.cc b/src/node_modules.cc index 7e615a691a5a14..f037c022354660 100644 --- a/src/node_modules.cc +++ b/src/node_modules.cc @@ -555,76 +555,6 @@ void GetCompileCacheEntry(const FunctionCallbackInfo& args) { isolate, v8::Null(isolate), names.data(), values.data(), names.size())); } -static void PathHelpersLazyGetter(Local name, - const PropertyCallbackInfo& info) { - Isolate* isolate = info.GetIsolate(); - // This getter has no JavaScript function representation and is not - // invoked in the creation context. - // When this getter is invoked in a vm context, the `Realm::GetCurrent(info)` - // returns a nullptr and retrieve the creation context via `this` object and - // get the creation Realm. - Local receiver_val = info.This(); - if (!receiver_val->IsObject()) { - THROW_ERR_INVALID_INVOCATION(isolate); - return; - } - Local receiver = receiver_val.As(); - Local context; - if (!receiver->GetCreationContext().ToLocal(&context)) { - THROW_ERR_INVALID_INVOCATION(isolate); - return; - } - Environment* env = Environment::GetCurrent(context); - - node::Utf8Value url(isolate, info.Data()); - auto file_url = ada::parse(url.ToStringView()); - CHECK(file_url); - auto file_path = url::FileURLToPath(env, *file_url); - CHECK(file_path.has_value()); - std::string_view ret_view = file_path.value(); - - node::Utf8Value utf8name(isolate, name); - auto plain_name = utf8name.ToStringView(); - if (plain_name == "dirname") { -#ifdef _WIN32 -#define PATH_SEPARATOR '\\' -#else -#define PATH_SEPARATOR '/' -#endif - auto index = ret_view.rfind(PATH_SEPARATOR); - CHECK(index != std::string_view::npos); - ret_view.remove_suffix(ret_view.size() - index); -#undef PATH_SEPARATOR - } - Local ret; - if (!ToV8Value(context, ret_view, isolate).ToLocal(&ret)) { - return; - } - info.GetReturnValue().Set(ret); -} -void InitImportMetaPathHelpers(const FunctionCallbackInfo& args) { - // target, url, shouldSetDirnameAndFilename, resolve - CHECK_GE(args.Length(), 2); - CHECK(args[0]->IsObject()); - CHECK(args[1]->IsString()); - - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - Environment* env = Environment::GetCurrent(context); - - auto target = args[0].As(); - - // N.B.: Order is important to keep keys in alphabetical order. - if (target - ->SetLazyDataProperty( - context, env->dirname_string(), PathHelpersLazyGetter, args[1]) - .IsNothing() || - target - ->SetLazyDataProperty( - context, env->filename_string(), PathHelpersLazyGetter, args[1]) - .IsNothing()) - return; -} void SaveCompileCacheEntry(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local context = isolate->GetCurrentContext(); @@ -654,7 +584,6 @@ void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, SetMethod(isolate, target, "flushCompileCache", FlushCompileCache); SetMethod(isolate, target, "getCompileCacheEntry", GetCompileCacheEntry); SetMethod(isolate, target, "saveCompileCacheEntry", SaveCompileCacheEntry); - SetMethod(isolate, target, "setLazyPathHelpers", InitImportMetaPathHelpers); } void BindingData::CreatePerContextProperties(Local target, @@ -709,7 +638,6 @@ void BindingData::RegisterExternalReferences( registry->Register(FlushCompileCache); registry->Register(GetCompileCacheEntry); registry->Register(SaveCompileCacheEntry); - registry->Register(InitImportMetaPathHelpers); } } // namespace modules From 357f4744882604f059851249de673729ffc60add Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 6 Nov 2025 23:18:48 +0100 Subject: [PATCH 2/3] fixup! src: move import.meta initializer to native land --- src/node_modules.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/node_modules.cc b/src/node_modules.cc index f037c022354660..fec3cd455371f2 100644 --- a/src/node_modules.cc +++ b/src/node_modules.cc @@ -35,7 +35,6 @@ using v8::Null; using v8::Object; using v8::ObjectTemplate; using v8::Primitive; -using v8::PropertyCallbackInfo; using v8::String; using v8::Undefined; using v8::Value; From 5d486b73b5aab3ea523db5cbe771f318f9960d0c Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 7 Nov 2025 01:12:10 +0100 Subject: [PATCH 3/3] benchmark: focus on import.meta intialization in import-meta benchmark Instead of measuring the performance of the entire module initialization, focus only on the import.meta initialization. --- benchmark/esm/import-meta.js | 40 +++++++++++++------ .../import-meta-dirname-and-filename.mjs | 3 +- benchmark/fixtures/import-meta-dirname.mjs | 2 +- benchmark/fixtures/import-meta-filename.mjs | 2 +- benchmark/fixtures/import-meta-url.mjs | 1 + 5 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 benchmark/fixtures/import-meta-url.mjs diff --git a/benchmark/esm/import-meta.js b/benchmark/esm/import-meta.js index 0e56d8a34a353d..1f405a59a0a757 100644 --- a/benchmark/esm/import-meta.js +++ b/benchmark/esm/import-meta.js @@ -10,28 +10,44 @@ const bench = common.createBenchmark(main, { 'dirname-and-filename', 'dirname', 'filename', + 'url', ], }); -const fixtureDir = path.resolve(__filename, '../../fixtures'); -const fixtureDirURL = pathToFileURL(fixtureDir); -async function load(array, n, valuesToRead) { +async function load(n, fixtureFileURL) { + const array = []; for (let i = 0; i < n; i++) { - array[i] = await import(`${fixtureDirURL}/import-meta-${valuesToRead}.mjs?i=${i}`); + array[i] = await import(`${fixtureFileURL}?i=${i}`); } return array; } function main({ n, valuesToRead }) { - const array = []; - for (let i = 0; i < n; ++i) { - array.push({ dirname: '', filename: '', i: 0 }); - } + const fixtureDir = path.resolve(__filename, '../../fixtures'); + const fixtureFile = path.join(fixtureDir, `import-meta-${valuesToRead}.mjs`); + const fixtureFileURL = pathToFileURL(fixtureFile); - bench.start(); - load(array, n, valuesToRead).then((arr) => { + load(n, fixtureFileURL).then((array) => { + const results = new Array(n); + bench.start(); + for (let i = 0; i < n; i++) { + results[i] = array[i].default(); + } bench.end(n); - if (valuesToRead.includes('dirname')) assert.strictEqual(arr[n - 1].dirname, fixtureDir); - if (valuesToRead.includes('filename')) assert.strictEqual(arr[n - 1].filename, path.join(fixtureDir, `import-meta-${valuesToRead}.mjs`)); + + switch (valuesToRead) { + case 'dirname-and-filename': + assert.deepStrictEqual(results[n - 1], [fixtureDir, fixtureFile]); + break; + case 'dirname': + assert.strictEqual(results[n - 1], fixtureDir); + break; + case 'filename': + assert.strictEqual(results[n - 1], fixtureFile); + break; + case 'url': + assert.strictEqual(results[n - 1], `${fixtureFileURL}?i=${n - 1}`); + break; + } }); } diff --git a/benchmark/fixtures/import-meta-dirname-and-filename.mjs b/benchmark/fixtures/import-meta-dirname-and-filename.mjs index a1028d57afd56b..1f92bc85f52692 100644 --- a/benchmark/fixtures/import-meta-dirname-and-filename.mjs +++ b/benchmark/fixtures/import-meta-dirname-and-filename.mjs @@ -1,2 +1 @@ -export const dirname = import.meta.dirname; -export const filename = import.meta.filename; +export default () => [ import.meta.dirname, import.meta.filename ]; diff --git a/benchmark/fixtures/import-meta-dirname.mjs b/benchmark/fixtures/import-meta-dirname.mjs index 8429b1f3cf84ae..f8ee699d31416e 100644 --- a/benchmark/fixtures/import-meta-dirname.mjs +++ b/benchmark/fixtures/import-meta-dirname.mjs @@ -1 +1 @@ -export const dirname = import.meta.dirname; +export default () => import.meta.dirname; diff --git a/benchmark/fixtures/import-meta-filename.mjs b/benchmark/fixtures/import-meta-filename.mjs index 73e495e19b76ab..ecec26d09c74c7 100644 --- a/benchmark/fixtures/import-meta-filename.mjs +++ b/benchmark/fixtures/import-meta-filename.mjs @@ -1 +1 @@ -export const filename = import.meta.filename; +export default () => import.meta.filename; diff --git a/benchmark/fixtures/import-meta-url.mjs b/benchmark/fixtures/import-meta-url.mjs new file mode 100644 index 00000000000000..a924853bcc7113 --- /dev/null +++ b/benchmark/fixtures/import-meta-url.mjs @@ -0,0 +1 @@ +export default () => import.meta.url;