Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions benchmark/esm/import-meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
});
}
3 changes: 1 addition & 2 deletions benchmark/fixtures/import-meta-dirname-and-filename.mjs
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export const dirname = import.meta.dirname;
export const filename = import.meta.filename;
export default () => [ import.meta.dirname, import.meta.filename ];
2 changes: 1 addition & 1 deletion benchmark/fixtures/import-meta-dirname.mjs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const dirname = import.meta.dirname;
export default () => import.meta.dirname;
2 changes: 1 addition & 1 deletion benchmark/fixtures/import-meta-filename.mjs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const filename = import.meta.filename;
export default () => import.meta.filename;
1 change: 1 addition & 0 deletions benchmark/fixtures/import-meta-url.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => import.meta.url;
22 changes: 0 additions & 22 deletions lib/internal/modules/esm/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ const {
let debug = require('internal/util/debuglog').debuglog('async_loader_worker', (fn) => {
debug = fn;
});
let importMetaInitializer;

let importAssertionAlreadyWarned = false;

Expand Down Expand Up @@ -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<LoadResult>} load
* Calling the asynchronous `load` hook asynchronously.
Expand Down Expand Up @@ -163,8 +161,6 @@ class AsyncLoaderHooksOnLoaderHookWorker {
// Cache URLs we've already validated to avoid repeated validation
#validatedUrls = new SafeSet();

allowImportMetaResolve = false;

isForAsyncLoaderHookWorker = true;

/**
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -817,8 +801,6 @@ function getAsyncLoaderHookWorker() {
*/
class AsyncLoaderHooksProxiedToLoaderHookWorker {

allowImportMetaResolve = true;

isForAsyncLoaderHookWorker = false;

/**
Expand Down Expand Up @@ -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();
}
Expand Down
83 changes: 0 additions & 83 deletions lib/internal/modules/esm/initialize_import_meta.js

This file was deleted.

81 changes: 57 additions & 24 deletions lib/internal/modules/esm/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const {
kErrored,
kSourcePhase,
throwIfPromiseRejected,
setImportMetaResolveInitializer,
} = internalBinding('module_wrap');
const {
urlToFilename,
Expand All @@ -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');
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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
Expand All @@ -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;
}

Expand Down
19 changes: 3 additions & 16 deletions lib/internal/modules/esm/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,27 +183,14 @@ function registerModule(referrer, registry) {
}

/**
* Proxy the import meta handling to the default loader for source text modules.
* @param {Record<string, string | Function>} 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<string, string | Function>} 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;
Expand Down
Loading
Loading