'use strict';
const {
ArrayPrototypeForEach,
ArrayPrototypePush,
MapPrototypeForEach,
MapPrototypeGet,
ObjectDefineProperty,
ObjectFreeze,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
ReflectApply,
SafeMap,
SafeSet,
StringPrototypeCharCodeAt,
StringPrototypeIncludes,
StringPrototypeSlice,
StringPrototypeStartsWith,
Symbol,
} = primordials;
const {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_RETURN_PROPERTY_VALUE,
ERR_UNKNOWN_BUILTIN_MODULE,
} = require('internal/errors').codes;
const { BuiltinModule } = require('internal/bootstrap/realm');
const { validateString } = require('internal/validators');
const fs = require('fs'); // Import all of `fs` so that it can be monkey-patched.
const internalFS = require('internal/fs/utils');
const path = require('path');
const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
const assert = require('internal/assert');
const { getOptionValue } = require('internal/options');
const { kEmptyObject, setOwnProperty, getLazy } = require('internal/util');
const { inspect } = require('internal/util/inspect');
const { emitWarningSync } = require('internal/process/warning');
const {
privateSymbols: {
module_first_parent_private_symbol: kFirstModuleParent,
},
} = internalBinding('util');
const lazyTmpdir = getLazy(() => require('os').tmpdir());
const { join } = path;
const internalFsBinding = internalBinding('fs');
const { canParse: URLCanParse } = internalBinding('url');
const modulesBinding = internalBinding('modules');
const {
enableCompileCache: _enableCompileCache,
getCompileCacheDir: _getCompileCacheDir,
compileCacheStatus: _compileCacheStatus,
flushCompileCache,
} = modulesBinding;
const lazyCJSLoader = getLazy(() => require('internal/modules/cjs/loader'));
let debug = require('internal/util/debuglog').debuglog('module', (fn) => {
debug = fn;
});
/** @typedef {import('internal/modules/cjs/loader.js').Module} Module */
/**
* Cache for storing resolved real paths of modules.
* In order to minimize unnecessary lstat() calls, this cache is a list of known-real paths.
* Set to an empty Map to reset.
* @type {Map<string, string>}
*/
const realpathCache = new SafeMap();
// Toggleable loader hooks for VFS support. Overrides that return
// `undefined` fall through to the pinned native implementation.
const nativeLoaderMethods = {
internalModuleStat: (filename) => internalFsBinding.internalModuleStat(filename),
readFileSync: (filename, options) => fs.readFileSync(filename, options),
realpathSync: (requestPath) => fs.realpathSync(requestPath, {
[internalFS.realpathCacheKey]: realpathCache,
}),
legacyMainResolve: (pkgPath, main, base) =>
internalFsBinding.legacyMainResolve(pkgPath, main, base),
getFormatOfExtensionlessFile: (filePath) =>
internalFsBinding.getFormatOfExtensionlessFile(filePath),
readPackageJSON: (jsonPath, isESM, base, specifier) =>
modulesBinding.readPackageJSON(jsonPath, isESM, base, specifier),
getNearestParentPackageJSON: (checkPath) =>
modulesBinding.getNearestParentPackageJSON(checkPath),
getRootPackageJSON: (checkPath) =>
modulesBinding.getRootPackageJSON(checkPath),
getNearestParentPackageJSONType: (checkPath) =>
modulesBinding.getNearestParentPackageJSONType(checkPath),
getPackageScopeConfig: (resolved) =>
modulesBinding.getPackageScopeConfig(resolved),
getPackageType: (url) => modulesBinding.getPackageType(url),
// Synthetic hook with no native counterpart: returns the directory at
// which upward walks (node_modules lookups) must stop for `path`, or
// undefined to walk to the file system root.
getResolutionRoot: () => undefined,
};
const loaderMethodKeys = ObjectKeys(nativeLoaderMethods);
// Overrides use this sentinel when they handled a request but their method's
// meaningful result is `undefined`. A plain `undefined` means fall through to
// the native implementation.
const kLoaderOverrideNoResult = Symbol('kLoaderOverrideNoResult');
const loaderOverrides = new SafeMap();
let hasLoaderOverrides = false;
/**
* @param {string} key Name of the wrapped method in `nativeLoaderMethods`.
* @param {Function} originalFn Native fallback pinned at load time.
* @returns {Function} Wrapper that consults `loaderOverrides` and falls
* through to `originalFn` when no override is set or when the
* override returns `undefined`.
*/
function wrapLoaderMethod(key, originalFn) {
return function loaderMethodWrapper(...args) {
if (hasLoaderOverrides) {
const override = MapPrototypeGet(loaderOverrides, key);
if (override !== undefined) {
const result = ReflectApply(override, undefined, args);
if (result === kLoaderOverrideNoResult) { return undefined; }
if (result !== undefined) { return result; }
}
}
return ReflectApply(originalFn, undefined, args);
};
}
/**
* Toggleable versions of the loader's fs and package.json methods, keyed
* like `nativeLoaderMethods`. The module loader calls these instead of the
* native implementations so that VFS overrides can be consulted first.
*/
const loaderMethods = { __proto__: null };
for (let i = 0; i < loaderMethodKeys.length; i++) {
const key = loaderMethodKeys[i];
loaderMethods[key] = wrapLoaderMethod(key, nativeLoaderMethods[key]);
}
/**
* Set override functions for the module loader's fs and package.json
* operations. Fields are keyed like `nativeLoaderMethods`; missing or
* nullish fields disable that hook (fast-path restored).
* @param {Record<string, Function|undefined>} overrides
*/
function setLoaderOverrides(overrides = kEmptyObject) {
for (let i = 0; i < loaderMethodKeys.length; i++) {
const key = loaderMethodKeys[i];
const override = overrides[key];
if (override) {
loaderOverrides.set(key, override);
} else {
loaderOverrides.delete(key);
}
}
hasLoaderOverrides = loaderOverrides.size > 0;
}
/**
* Delete every entry in `cache` whose owned string is under
* `mountPoint`. `cache` is either a plain object (Module._cache,
* Module._pathCache) or a SafeMap (statCache, realpathCache,
* the package.json caches). By default the entry KEY is tested;
* pass `byValue: true` to test the entry value instead (used for
* Module._pathCache whose keys are `request\0parent` tokens and
* whose values are the resolved filenames).
*
* Mount points live in the reserved VFS namespace so a prefix scan
* on strings is exact - real-fs entries never match.
* @param {object|Map<string, *>} cache
* @param {string} mountPoint Absolute mount-point path
* @param {boolean} [byValue] Test the entry value instead of the key
*/
function cleanForVfsPrefix(cache, mountPoint, byValue = false) {
const prefix = mountPoint + path.sep;
const owns = (s) =>
typeof s === 'string' &&
(s === mountPoint || StringPrototypeStartsWith(s, prefix));
if (typeof cache.forEach === 'function') {
MapPrototypeForEach(cache, (v, k) => {
if (owns(byValue ? v : k)) { cache.delete(k); }
});
} else {
ArrayPrototypeForEach(ObjectKeys(cache), (k) => {
if (owns(byValue ? cache[k] : k)) { delete cache[k]; }
});
}
}
/**
* Drop realpath-cache entries under an unmounted VFS mount point.
* Called by internal/vfs/setup.js.
* @param {string} mountPoint Absolute mount-point path
*/
function purgeRealpathCacheForPrefix(mountPoint) {
cleanForVfsPrefix(realpathCache, mountPoint);
}
/** Whether the `worker` condition applies, i.e. this is a Web Worker thread. */
let workerCondition = false;
function enableWorkerCondition() {
workerCondition = true;
}
function hasWorkerCondition() {
return workerCondition;
}
/** @type {Set<string>} */
let cjsConditions;
/** @type {string[]} */
let cjsConditionsArray;
/**
* Define the conditions that apply to the CommonJS loader.
* @returns {void}
*/
function initializeCjsConditions() {
const userConditions = getOptionValue('--conditions');
const noAddons = getOptionValue('--no-addons');
const addonConditions = noAddons ? [] : ['node-addons'];
// TODO: Use this set when resolving pkg#exports conditions in loader.js.
cjsConditionsArray = [
'require',
'node',
...addonConditions,
...(workerCondition ? ['worker'] : []),
...userConditions,
];
if (getOptionValue('--require-module')) {
cjsConditionsArray.push('module-sync');
}
ObjectFreeze(cjsConditionsArray);
cjsConditions = new SafeSet(cjsConditionsArray);
}
/**
* Get the conditions that apply to the CommonJS loader.
* @returns {Set<string>}
*/
function getCjsConditions() {
if (cjsConditions === undefined) {
initializeCjsConditions();
}
return cjsConditions;
}
function getCjsConditionsArray() {
if (cjsConditionsArray === undefined) {
initializeCjsConditions();
}
return cjsConditionsArray;
}
/**
* Provide one of Node.js' public modules to user code.
* @param {string} id - The identifier/specifier of the builtin module to load
* @returns {object|undefined}
*/
function loadBuiltinModule(id) {
if (!BuiltinModule.canBeRequiredByUsers(id)) {
return;
}
/** @type {import('internal/bootstrap/realm.js').BuiltinModule} */
const mod = BuiltinModule.map.get(id);
debug('load built-in module %s', id);
// compileForPublicLoader() throws if canBeRequiredByUsers is false:
mod.compileForPublicLoader();
return mod;
}
let isSEABuiltinWarningNeeded_;
function isSEABuiltinWarningNeeded() {
if (isSEABuiltinWarningNeeded_ === undefined) {
const { isExperimentalSeaWarningNeeded, isSea } = internalBinding('sea');
isSEABuiltinWarningNeeded_ = isSea() && isExperimentalSeaWarningNeeded();
}
return isSEABuiltinWarningNeeded_;
}
let warnedAboutBuiltins = false;
/**
* Helpers to load built-in modules for embedder modules.
* @param {string} id
* @returns {import('internal/bootstrap/realm.js').BuiltinModule}
*/
function loadBuiltinModuleForEmbedder(id) {
const normalized = BuiltinModule.normalizeRequirableId(id);
if (normalized) {
const mod = loadBuiltinModule(normalized);
if (mod) {
return mod;
}
}
if (isSEABuiltinWarningNeeded() && !warnedAboutBuiltins) {
emitWarningSync(
'Currently the require() provided to the main script embedded into ' +
'single-executable applications only supports loading built-in modules.\n' +
'To load a module from disk after the single executable application is ' +
'launched, use require("module").createRequire().\n' +
'Support for bundled module loading or virtual file systems are under ' +
'discussions in https://github.com/nodejs/single-executable');
warnedAboutBuiltins = true;
}
throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
}
/** @type {Module} */
let $Module = null;
/**
* Import the Module class on first use.
* @returns {object}
*/
function lazyModule() {
return $Module ??= require('internal/modules/cjs/loader').Module;
}
/**
* Create the module-scoped `require` function to pass into CommonJS modules.
* @param {Module} mod - The module to create the `require` function for.
* @returns {function(string): unknown}
*/
function makeRequireFunction(mod) {
// lazy due to cycle
const Module = lazyModule();
if (mod instanceof Module !== true) {
throw new ERR_INVALID_ARG_TYPE('mod', 'Module', mod);
}
function require(path) {
return mod.require(path);
}
/**
* The `resolve` method that gets attached to module-scope `require`.
* @param {string} request
* @param {Parameters<Module['_resolveFilename']>[3]} options
* @returns {string}
*/
function resolve(request, options) {
validateString(request, 'request');
const { resolveForCJSWithHooks } = lazyCJSLoader();
// require.resolve() has different behaviors from the internal resolution used by
// Module._load:
// 1. When the request resolves to a non-existent built-in, it throws MODULE_NOT_FOUND
// instead of UNKNOWN_BUILTIN_MODULE. This is handled by resolveForCJSWithHooks.
// 2. If the request is a prefixed built-in, the returned value is also prefixed. This
// is handled below.
const { filename, url } = resolveForCJSWithHooks(
request, mod, /* isMain */ false, {
__proto__: null,
shouldSkipModuleHooks: false,
requireResolveOptions: options ?? {},
});
if (url === request && StringPrototypeStartsWith(request, 'node:')) {
return url;
}
return filename;
}
require.resolve = resolve;
/**
* The `paths` method that gets attached to module-scope `require`.
* @param {string} request
* @returns {object}
*/
function paths(request) {
validateString(request, 'request');
return Module._resolveLookupPaths(request, mod);
}
resolve.paths = paths;
setOwnProperty(require, 'main', process.mainModule);
// Enable support to add extra extension types.
require.extensions = Module._extensions;
require.cache = Module._cache;
return require;
}
/**
* Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
* because the buffer-to-string conversion in `fs.readFileSync()`
* translates it to FEFF, the UTF-16 BOM.
* @param {string} content
* @returns {string}
*/
function stripBOM(content) {
if (StringPrototypeCharCodeAt(content) === 0xFEFF) {
content = StringPrototypeSlice(content, 1);
}
return content;
}
/**
* Add built-in modules to a global or REPL scope object.
* @param {Record<string, unknown>} object - The object such as `globalThis` to add the built-in modules to.
* @param {string} dummyModuleName - The label representing the set of built-in modules to add.
*/
function addBuiltinLibsToObject(object, dummyModuleName) {
// Make built-in modules available directly (loaded lazily).
const Module = require('internal/modules/cjs/loader').Module;
const { builtinModules } = Module;
// To require built-in modules in user-land and ignore modules whose
// `canBeRequiredByUsers` is false. So we create a dummy module object and not
// use `require()` directly.
const dummyModule = new Module(dummyModuleName);
ArrayPrototypeForEach(builtinModules, (name) => {
// Neither add underscored modules, nor ones that contain slashes (e.g.,
// 'fs/promises') or ones that are already defined.
if (name[0] === '_' ||
StringPrototypeIncludes(name, '/') ||
ObjectPrototypeHasOwnProperty(object, name)) {
return;
}
// Goals of this mechanism are:
// - Lazy loading of built-in modules
// - Having all built-in modules available as non-enumerable properties
// - Allowing the user to re-assign these variables as if there were no
// pre-existing globals with the same name.
const setReal = (val) => {
// Deleting the property before re-assigning it disables the
// getter/setter mechanism.
delete object[name];
object[name] = val;
};
ObjectDefineProperty(object, name, {
__proto__: null,
get: () => {
const lib = dummyModule.require(name);
try {
// Override the current getter/setter and set up a new
// non-enumerable property.
ObjectDefineProperty(object, name, {
__proto__: null,
get: () => lib,
set: setReal,
configurable: true,
enumerable: false,
});
} catch {
// If the property is no longer configurable, ignore the error.
}
return lib;
},
set: setReal,
configurable: true,
enumerable: false,
});
});
}
/**
* Normalize the referrer name as a URL.
* If it's a string containing an absolute path or a URL it's normalized as
* a URL string.
* Otherwise it's returned as undefined.
* @param {string | null | undefined} referrerName
* @returns {string | undefined}
*/
function normalizeReferrerURL(referrerName) {
if (referrerName === null || referrerName === undefined) {
return undefined;
}
if (typeof referrerName === 'string') {
if (path.isAbsolute(referrerName)) {
return pathToFileURL(referrerName).href;
}
if (StringPrototypeStartsWith(referrerName, 'file://') ||
URLCanParse(referrerName)) {
return referrerName;
}
return undefined;
}
assert.fail('Unreachable code reached by ' + inspect(referrerName));
}
/**
* Coerce a URL string to a filename. This is used by the ESM loader
* to map ESM URLs to entries in the CJS module cache on a best-effort basis.
* TODO(joyeecheung): this can be rather expensive, cache the result on the
* ModuleWrap wherever we can.
* @param {string|undefined} url URL to convert to filename
* @returns {string|undefined}
*/
function urlToFilename(url) {
if (url && StringPrototypeStartsWith(url, 'file://')) {
let urlObj;
try {
urlObj = new URL(url);
} catch {
// Not a proper URL, return as-is as the cache key.
return url;
}
try {
return fileURLToPath(urlObj);
} catch {
// This is generally only possible when the URL is provided by a custom loader.
// Just use the path and ignore whether it's absolute or not as there's no such
// requirement for CJS cache.
return urlObj.pathname;
}
}
// Not a file URL, return as-is.
return url;
}
// Whether we have started executing any user-provided CJS code.
// This is set right before we call the wrapped CJS code (not after,
// in case we are half-way in the execution when internals check this).
// Used for internal assertions.
let _hasStartedUserCJSExecution = false;
// Similar to _hasStartedUserCJSExecution but for ESM. This is set
// right before ESM evaluation in the default ESM loader. We do not
// update this during vm SourceTextModule execution because at that point
// some user code must already have been run to execute code via vm
// there is little value checking whether any user JS code is run anyway.
let _hasStartedUserESMExecution = false;
/**
* Load a public built-in module. ID may or may not be prefixed by `node:` and
* will be normalized.
* @param {string} id ID of the built-in to be loaded.
* @returns {object|undefined} exports of the built-in. Undefined if the built-in
* does not exist.
*/
function getBuiltinModule(id) {
validateString(id, 'id');
const normalizedId = BuiltinModule.normalizeRequirableId(id);
return normalizedId ? require(normalizedId) : undefined;
}
/** @type {import('internal/util/types')} */
let _TYPES = null;
/**
* Lazily loads and returns the internal/util/types module.
* @returns {object}
*/
function lazyTypes() {
if (_TYPES !== null) { return _TYPES; }
return _TYPES = require('internal/util/types');
}
/**
* Asserts that the given body is a buffer source (either a string, array buffer, or typed array).
* Throws an error if the body is not a buffer source.
* @param {string | ArrayBufferView | ArrayBuffer} body - The body to check.
* @param {boolean} allowString - Whether or not to allow a string as a valid buffer source.
* @param {string} hookName - The name of the hook being called.
* @throws {ERR_INVALID_RETURN_PROPERTY_VALUE} If the body is not a buffer source.
* @returns {void}
*/
function assertBufferSource(body, allowString, hookName) {
if (allowString && typeof body === 'string') {
return;
}
const { isArrayBufferView, isAnyArrayBuffer } = lazyTypes();
if (isArrayBufferView(body) || isAnyArrayBuffer(body)) {
return;
}
throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
`${allowString ? 'string, ' : ''}array buffer, or typed array`,
hookName,
'source',
body,
);
}
/**
* Converts a buffer or buffer-like object to a string.
* @param {string | ArrayBuffer | ArrayBufferView} body - The buffer or buffer-like object to convert to a string.
* @returns {string} The resulting string.
*/
function stringify(body) {
if (typeof body === 'string') { return body; }
assertBufferSource(body, false, 'load');
return require('internal/encoding').getUtf8Decoder().decode(body);
}
/**
* Enable on-disk compiled cache for all user modules being compiled in the current Node.js instance
* after this method is called.
* This method accepts either:
* - A string: path to the cache directory.
* - An options object `{directory?: string, portable?: boolean, readOnly?: boolean}`:
* - `directory`: A string path to the cache directory.
* - `portable`: If `portable` is true, the cache directory will be considered relative.
* Defaults to `NODE_COMPILE_CACHE_PORTABLE === '1'`.
* - `readOnly`: If `readOnly` is true, existing cache entries are used but nothing is
* written, and the cache directory is not created. Defaults to
* `NODE_COMPILE_CACHE_READONLY === '1'`.
* If cache directory is undefined, it defaults to the `NODE_COMPILE_CACHE` environment variable.
* If `NODE_COMPILE_CACHE` isn't set, it defaults to `path.join(os.tmpdir(), 'node-compile-cache')`.
* @param {string | { directory?: string, portable?: boolean, readOnly?: boolean } | undefined} options
* @returns {{status: number, message?: string, directory?: string}}
*/
function enableCompileCache(options) {
let portable;
let readOnly;
let directory;
if (typeof options === 'object' && options !== null) {
({ directory, portable, readOnly } = options);
} else {
directory = options;
}
if (directory === undefined) {
directory = process.env.NODE_COMPILE_CACHE || join(lazyTmpdir(), 'node-compile-cache');
}
if (portable === undefined) {
portable = process.env.NODE_COMPILE_CACHE_PORTABLE === '1';
}
if (readOnly === undefined) {
readOnly = process.env.NODE_COMPILE_CACHE_READONLY === '1';
}
const nativeResult = _enableCompileCache(directory, portable, readOnly);
const result = { status: nativeResult[0] };
if (nativeResult[1]) {
result.message = nativeResult[1];
}
if (nativeResult[2]) {
result.directory = nativeResult[2];
}
return result;
}
const compileCacheStatus = { __proto__: null };
for (let i = 0; i < _compileCacheStatus.length; ++i) {
compileCacheStatus[_compileCacheStatus[i]] = i;
}
ObjectFreeze(compileCacheStatus);
const constants = { __proto__: null, compileCacheStatus };
ObjectFreeze(constants);
/**
* Get the compile cache directory if on-disk compile cache is enabled.
* @returns {string|undefined} Path to the module compile cache directory if it is enabled,
* or undefined otherwise.
*/
function getCompileCacheDir() {
return _getCompileCacheDir() || undefined;
}
function getRequireStack(parent) {
const requireStack = [];
for (let cursor = parent;
cursor;
// TODO(joyeecheung): it makes more sense to use kLastModuleParent here.
cursor = cursor[kFirstModuleParent]) {
ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
}
return requireStack;
}
// Suffixes tried by legacy CommonJS `main` resolution; indexes match
// the values returned by the `legacyMainResolve` C++ binding.
const legacyMainResolveExtensions = [
'',
'.js',
'.json',
'.node',
'/index.js',
'/index.json',
'/index.node',
'./index.js',
'./index.json',
'./index.node',
];
const legacyMainResolveExtensionsIndexes = {
// 0-6: when packageConfig.main is defined
kResolvedByMain: 0,
kResolvedByMainJs: 1,
kResolvedByMainJson: 2,
kResolvedByMainNode: 3,
kResolvedByMainIndexJs: 4,
kResolvedByMainIndexJson: 5,
kResolvedByMainIndexNode: 6,
// 7-9: when packageConfig.main is not defined
kResolvedByPackageAndJs: 7,
kResolvedByPackageAndJson: 8,
kResolvedByPackageAndNode: 9,
};
module.exports = {
addBuiltinLibsToObject,
assertBufferSource,
cleanForVfsPrefix,
purgeRealpathCacheForPrefix,
constants,
enableCompileCache,
enableWorkerCondition,
flushCompileCache,
getBuiltinModule,
getCjsConditions,
getCjsConditionsArray,
getCompileCacheDir,
hasWorkerCondition,
initializeCjsConditions,
legacyMainResolveExtensions,
legacyMainResolveExtensionsIndexes,
kLoaderOverrideNoResult,
loaderMethods,
loadBuiltinModuleForEmbedder,
loadBuiltinModule,
makeRequireFunction,
normalizeReferrerURL,
setLoaderOverrides,
stringify,
stripBOM,
hasStartedUserCJSExecution() {
return _hasStartedUserCJSExecution;
},
setHasStartedUserCJSExecution() {
_hasStartedUserCJSExecution = true;
},
hasStartedUserESMExecution() {
return _hasStartedUserESMExecution;
},
setHasStartedUserESMExecution() {
_hasStartedUserESMExecution = true;
},
urlToFilename,
getRequireStack,
};