diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 6d1793c06a4270..5f352a14e90c1f 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -37,6 +37,7 @@ const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); const typeFlag = getOptionValue('--input-type'); const { URL, pathToFileURL, fileURLToPath, isURL } = require('internal/url'); +const { getCWDURL } = require('internal/util'); const { canParse: URLCanParse } = internalBinding('url'); const { legacyMainResolve: FSLegacyMainResolve } = internalBinding('fs'); const { @@ -1017,7 +1018,7 @@ function defaultResolve(specifier, context = {}) { const isMain = parentURL === undefined; if (isMain) { - parentURL = pathToFileURL(`${process.cwd()}/`).href; + parentURL = getCWDURL().href; // This is the initial entry point to the program, and --input-type has // been passed as an option; but --input-type can only be used with diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js index 5014c99b2a9eb3..64bc21a47c7845 100644 --- a/lib/internal/modules/esm/utils.js +++ b/lib/internal/modules/esm/utils.js @@ -16,7 +16,7 @@ const { loadPreloadModules, initializeFrozenIntrinsics, } = require('internal/process/pre_execution'); -const { pathToFileURL } = require('internal/url'); +const { getCWDURL } = require('internal/util'); const { setImportModuleDynamicallyCallback, setInitializeImportMetaObjectCallback, @@ -112,15 +112,6 @@ function isLoaderWorker() { async function initializeHooks() { const customLoaderURLs = getOptionValue('--experimental-loader'); - let cwd; - try { - // `process.cwd()` can fail if the parent directory is deleted while the process runs. - cwd = process.cwd() + '/'; - } catch { - cwd = '/'; - } - - const { Hooks } = require('internal/modules/esm/hooks'); const esmLoader = require('internal/process/esm_loader').esmLoader; @@ -137,7 +128,7 @@ async function initializeHooks() { loadPreloadModules(); initializeFrozenIntrinsics(); - const parentURL = pathToFileURL(cwd).href; + const parentURL = getCWDURL().href; for (let i = 0; i < customLoaderURLs.length; i++) { await hooks.register( customLoaderURLs[i], diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js index e735101ab18c47..a3451ddab307f2 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -9,8 +9,7 @@ const { getOptionValue } = require('internal/options'); const { hasUncaughtExceptionCaptureCallback, } = require('internal/process/execution'); -const { pathToFileURL } = require('internal/url'); -const { kEmptyObject } = require('internal/util'); +const { kEmptyObject, getCWDURL } = require('internal/util'); let esmLoader; @@ -23,14 +22,7 @@ module.exports = { try { const userImports = getOptionValue('--import'); if (userImports.length > 0) { - let cwd; - try { - // `process.cwd()` can fail if the parent directory is deleted while the process runs. - cwd = process.cwd() + '/'; - } catch { - cwd = '/'; - } - const parentURL = pathToFileURL(cwd).href; + const parentURL = getCWDURL().href; await SafePromiseAllReturnVoid(userImports, (specifier) => esmLoader.import( specifier, parentURL, diff --git a/lib/internal/util.js b/lib/internal/util.js index 3586084ba7b8bd..d35867635ca07b 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -358,6 +358,36 @@ function getConstructorOf(obj) { return null; } +let cachedURL; +let cachedCWD; + +/** + * Get the current working directory while accounting for the possibility that it has been deleted. + * `process.cwd()` can fail if the parent directory is deleted while the process runs. + * @returns {URL} The current working directory or the volume root if it cannot be determined. + */ +function getCWDURL() { + const { sep } = require('path'); + const { pathToFileURL } = require('internal/url'); + + let cwd; + + try { + // The implementation of `process.cwd()` already uses proper cache when it can. + // It's a relatively cheap call performance-wise for the most common use case. + cwd = process.cwd(); + } catch { + cachedURL ??= pathToFileURL(sep); + } + + if (cwd != null && cwd !== cachedCWD) { + cachedURL = pathToFileURL(cwd + sep); + cachedCWD = cwd; + } + + return cachedURL; +} + function getSystemErrorName(err) { const entry = uvErrmapGet(err); return entry ? entry[0] : `Unknown system error ${err}`; @@ -850,6 +880,7 @@ module.exports = { filterDuplicateStrings, filterOwnProperties, getConstructorOf, + getCWDURL, getInternalGlobal, getSystemErrorMap, getSystemErrorName,