From 9c54e43ef7e40a48555cb08c223e3ecd72d97fb0 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater <ruben@bridgewater.de> Date: Sun, 30 Dec 2018 18:24:28 +0100 Subject: [PATCH] modules: significantly improve repeated requires 1) It adds more benchmark options to properly verify the gains. This makes sure the benchmark also tests requiring the same module again instead of only loading each module only once. 2) Remove dead code: The array check is obsolete as this function will only be called internally with preprepared data which is always an array. 3) Simpler code It was possible to use a more direct logic to prevent some branches. 4) Inline try catch The function is not required anymore, since V8 is able to produce performant code with it. 5) var -> let / const & less lines 6) Update require.extensions description The comment was outdated. 7) Improve extension handling This is a performance optimization to prevent loading the extensions on each uncached require call. It uses proxies to intercept changes and receives the necessary informations by doing that. --- benchmark/module/module-loader.js | 53 +- doc/api/deprecations.md | 10 +- doc/api/modules.md | 57 +- lib/internal/bootstrap/loaders.js | 54 +- lib/internal/modules/cjs/helpers.js | 81 +- lib/internal/modules/cjs/loader.js | 749 +++++++++--------- lib/repl.js | 2 +- test/es-module/test-esm-namespace.mjs | 2 +- test/fixtures/module-require-depth/one.js | 11 - test/fixtures/module-require-depth/two.js | 11 - test/fixtures/require-resolve.js | 24 +- test/message/core_line_numbers.out | 6 +- test/message/error_exit.out | 7 +- .../events_unhandled_error_common_trace.out | 8 +- .../events_unhandled_error_nexttick.out | 12 +- .../events_unhandled_error_sameline.out | 9 +- test/message/if-error-has-good-stack.out | 6 +- test/message/nexttick_throw.out | 2 +- .../undefined_reference_in_new_context.out | 2 +- test/message/vm_display_runtime_error.out | 12 +- test/message/vm_display_syntax_error.out | 12 +- .../message/vm_dont_display_runtime_error.out | 6 +- test/message/vm_dont_display_syntax_error.out | 6 +- test/parallel/test-cwd-enoent-preload.js | 1 - test/parallel/test-module-relative-lookup.js | 5 +- test/parallel/test-module-require-depth.js | 16 - test/parallel/test-repl.js | 1 + test/parallel/test-require-dot.js | 11 +- 28 files changed, 555 insertions(+), 621 deletions(-) delete mode 100644 test/fixtures/module-require-depth/one.js delete mode 100644 test/fixtures/module-require-depth/two.js delete mode 100644 test/parallel/test-module-require-depth.js diff --git a/benchmark/module/module-loader.js b/benchmark/module/module-loader.js index e780d6376b5e8d..84aa62505c5b8d 100644 --- a/benchmark/module/module-loader.js +++ b/benchmark/module/module-loader.js @@ -4,18 +4,20 @@ const path = require('path'); const common = require('../common.js'); const tmpdir = require('../../test/common/tmpdir'); -const benchmarkDirectory = path.join(tmpdir.path, 'nodejs-benchmark-module'); +let benchmarkDirectory = path.join(tmpdir.path, 'nodejs-benchmark-module'); const bench = common.createBenchmark(main, { - n: [5e4], - fullPath: ['true', 'false'], - useCache: ['true', 'false'] + name: ['', '/', '/index.js'], + dir: ['rel', 'abs'], + files: [5e2], + n: [1, 1e3], + cache: ['true', 'false'] }); -function main({ n, fullPath, useCache }) { +function main({ n, name, cache, files, dir }) { tmpdir.refresh(); - try { fs.mkdirSync(benchmarkDirectory); } catch {} - for (var i = 0; i <= n; i++) { + fs.mkdirSync(benchmarkDirectory); + for (var i = 0; i <= files; i++) { fs.mkdirSync(`${benchmarkDirectory}${i}`); fs.writeFileSync( `${benchmarkDirectory}${i}/package.json`, @@ -27,38 +29,25 @@ function main({ n, fullPath, useCache }) { ); } - if (fullPath === 'true') - measureFull(n, useCache === 'true'); - else - measureDir(n, useCache === 'true'); + if (dir === 'rel') + benchmarkDirectory = path.relative(__dirname, benchmarkDirectory); - tmpdir.refresh(); -} + measureDir(n, cache === 'true', files, name); -function measureFull(n, useCache) { - var i; - if (useCache) { - for (i = 0; i <= n; i++) { - require(`${benchmarkDirectory}${i}/index.js`); - } - } - bench.start(); - for (i = 0; i <= n; i++) { - require(`${benchmarkDirectory}${i}/index.js`); - } - bench.end(n); + tmpdir.refresh(); } -function measureDir(n, useCache) { +function measureDir(n, cache, files, name) { var i; - if (useCache) { - for (i = 0; i <= n; i++) { - require(`${benchmarkDirectory}${i}`); + if (cache) { + for (i = 0; i <= files; i++) { + require(`${benchmarkDirectory}${i}${name}`); } } bench.start(); - for (i = 0; i <= n; i++) { - require(`${benchmarkDirectory}${i}`); + for (i = 0; i <= files; i++) { + for (var j = 0; j < n; j++) + require(`${benchmarkDirectory}${i}${name}`); } - bench.end(n); + bench.end(n * files); } diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md index c4fbacc0e0ae46..61a0c11699aa19 100644 --- a/doc/api/deprecations.md +++ b/doc/api/deprecations.md @@ -442,6 +442,9 @@ code. ### DEP0019: require('.') resolved outside directory <!-- YAML changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/REPLACEME + description: Removed functionality. - version: - v4.8.6 - v6.12.0 @@ -452,11 +455,10 @@ changes: description: Runtime deprecation. --> -Type: Runtime +Type: End-of-Life -In certain cases, `require('.')` may resolve outside the package directory. -This behavior is deprecated and will be removed in a future major Node.js -release. +In certain cases, `require('.')` could resolve outside the package directory. +This behavior has been removed. <a id="DEP0020"></a> ### DEP0020: Server.connections diff --git a/doc/api/modules.md b/doc/api/modules.md index 5292d2389760bd..ea9b3bed09a994 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -196,28 +196,26 @@ NODE_MODULES_PATHS(START) <!--type=misc--> -Modules are cached after the first time they are loaded. This means -(among other things) that every call to `require('foo')` will get -exactly the same object returned, if it would resolve to the same file. +Modules are cached after the first time they are loaded. This means (among other +things) that every call to `require('foo')` will get exactly the same object +returned, if it would resolve to the same file. -Provided `require.cache` is not modified, multiple calls to -`require('foo')` will not cause the module code to be executed multiple times. -This is an important feature. With it, "partially done" objects can be returned, -thus allowing transitive dependencies to be loaded even when they would cause -cycles. +Provided `require.cache` is not modified, multiple calls to `require('foo')` +will not cause the module code to be executed multiple times. This is an +important feature. With it, "partially done" objects can be returned, thus +allowing transitive dependencies to be loaded even when they would cause cycles. -To have a module execute code multiple times, export a function, and call -that function. +To have a module execute code multiple times, export a function, and call that +function. ### Module Caching Caveats <!--type=misc--> -Modules are cached based on their resolved filename. Since modules may -resolve to a different filename based on the location of the calling -module (loading from `node_modules` folders), it is not a *guarantee* -that `require('foo')` will always return the exact same object, if it -would resolve to different files. +Modules are cached based on their resolved filename. Since modules may resolve +to a different filename based on the location of the calling module (loading +from `node_modules` folders), it is not a *guarantee* that `require('foo')` will +always return the exact same object, if it would resolve to different files. Additionally, on case-insensitive file systems or operating systems, different resolved filenames can point to the same file, but the cache will still treat @@ -412,7 +410,7 @@ are not found elsewhere. On Windows, `NODE_PATH` is delimited by semicolons (`;`) instead of colons. `NODE_PATH` was originally created to support loading modules from -varying paths before the current [module resolution][] algorithm was frozen. +varying paths before the current [module resolution][] algorithm was defined. `NODE_PATH` is still supported, but is less necessary now that the Node.js ecosystem has settled on a convention for locating dependent modules. @@ -582,6 +580,10 @@ value from this object, the next `require` will reload the module. Note that this does not apply to [native addons][], for which reloading will result in an error. +Adding ore replacing entries is also possible. This cache is checked before +native modules and if such a name is added to the cache, no require call is +going to receive the native module anymore. Use with care! + #### require.extensions <!-- YAML added: v0.3.0 @@ -600,22 +602,15 @@ Process files with the extension `.sjs` as `.js`: require.extensions['.sjs'] = require.extensions['.js']; ``` -**Deprecated** In the past, this list has been used to load -non-JavaScript modules into Node.js by compiling them on-demand. -However, in practice, there are much better ways to do this, such as -loading modules via some other Node.js program, or compiling them to -JavaScript ahead of time. - -Since the module system is locked, this feature will probably never go -away. However, it may have subtle bugs and complexities that are best -left untouched. - -Note that the number of file system operations that the module system -has to perform in order to resolve a `require(...)` statement to a -filename scales linearly with the number of registered extensions. +**Deprecated** In the past, this list has been used to load non-JavaScript +modules into Node.js by compiling them on-demand. However, in practice, there +are much better ways to do this, such as loading modules via some other Node.js +program, or compiling them to JavaScript ahead of time. -In other words, adding extensions slows down the module loader and -should be discouraged. +Using this could cause subtle bugs and adding extensions slows down the module +loader and is therefore discouraged (the number of file system operations that +the module system has to perform in order to resolve a `require(...)` statement +to a filename scales linearly with the number of registered extensions). #### require.main <!-- YAML diff --git a/lib/internal/bootstrap/loaders.js b/lib/internal/bootstrap/loaders.js index 93fb186574165c..caa7c765e34b1b 100644 --- a/lib/internal/bootstrap/loaders.js +++ b/lib/internal/bootstrap/loaders.js @@ -179,21 +179,33 @@ NativeModule._cache = {}; const config = internalBinding('config'); +// Do not expose this to user land even with --expose-internals. +const loaderId = 'internal/bootstrap/loaders'; + +// Create a native module map to tell if a module is internal or not. +const moduleState = {}; +const moduleNames = Object.keys(source); +for (const name of moduleNames) { + moduleState[name] = name === loaderId || + !config.exposeInternals && + (name.startsWith('internal/') || + (name === 'worker_threads' && !config.experimentalWorker)); +} + // Think of this as module.exports in this file even though it is not // written in CommonJS style. const loaderExports = { internalBinding, NativeModule }; -const loaderId = 'internal/bootstrap/loaders'; NativeModule.require = function(id) { - if (id === loaderId) { - return loaderExports; - } - const cached = NativeModule.getCached(id); - if (cached && (cached.loaded || cached.loading)) { + if (cached !== undefined && (cached.loaded || cached.loading)) { return cached.exports; } + if (id === loaderId) { + return loaderExports; + } + if (!NativeModule.exists(id)) { // Model the error off the internal/errors.js model, but // do not use that module given that it could actually be @@ -231,32 +243,16 @@ NativeModule.getCached = function(id) { }; NativeModule.exists = function(id) { - return NativeModule._source.hasOwnProperty(id); + return NativeModule._source[id] !== undefined; }; -if (config.exposeInternals) { - NativeModule.nonInternalExists = function(id) { - // Do not expose this to user land even with --expose-internals. - if (id === loaderId) { - return false; - } - return NativeModule.exists(id); - }; - - NativeModule.isInternal = function(id) { - // Do not expose this to user land even with --expose-internals. - return id === loaderId; - }; -} else { - NativeModule.nonInternalExists = function(id) { - return NativeModule.exists(id) && !NativeModule.isInternal(id); - }; +NativeModule.nonInternalExists = function(id) { + return NativeModule.exists(id) && !NativeModule.isInternal(id); +}; - NativeModule.isInternal = function(id) { - return id.startsWith('internal/') || - (id === 'worker_threads' && !config.experimentalWorker); - }; -} +NativeModule.isInternal = function(id) { + return moduleState[id]; +}; NativeModule.getSource = function(id) { return NativeModule._source[id]; diff --git a/lib/internal/modules/cjs/helpers.js b/lib/internal/modules/cjs/helpers.js index fcb536c3df3738..7613c45d10478f 100644 --- a/lib/internal/modules/cjs/helpers.js +++ b/lib/internal/modules/cjs/helpers.js @@ -1,15 +1,12 @@ 'use strict'; const { validateString } = require('internal/validators'); - -const { - CHAR_LINE_FEED, - CHAR_CARRIAGE_RETURN, - CHAR_EXCLAMATION_MARK, - CHAR_HASH, -} = require('internal/constants'); - const { getOptionValue } = require('internal/options'); +const { + ERR_INVALID_ARG_TYPE, + ERR_OUT_OF_RANGE +} = require('internal/errors').codes; +const { isAbsolute } = require('path'); // Invoke with makeRequireFunction(module) where |module| is the Module object // to use as the context for the require() function. @@ -17,24 +14,40 @@ function makeRequireFunction(mod) { const Module = mod.constructor; function require(path) { - try { - exports.requireDepth += 1; - return mod.require(path); - } finally { - exports.requireDepth -= 1; - } + return mod.require(path); } function resolve(request, options) { validateString(request, 'request'); - return Module._resolveFilename(request, mod, false, options); + if (options !== undefined && options.paths !== undefined) { + if (!Array.isArray(options.paths)) { + throw new ERR_INVALID_ARG_TYPE('options.paths', 'Array', options.paths); + } + if (options.paths.length === 0) { + throw new ERR_OUT_OF_RANGE('options.paths.length', '> 0', + options.paths.length); + } + if (!isAbsolute(request) && + (request.charAt(0) !== '.' || + (request.length > 1 && + request.charAt(1) !== '.' && + request.charAt(1) !== '/' && + (process.platform !== 'win32' || request.charAt(1) !== '\\')))) { + const paths = new Set(); + options.paths.forEach((path) => + Module._nodeModulePaths(path).forEach((modPath) => + paths.add(modPath))); + return Module._resolveFilename(request, mod, false, [...paths]); + } + } + return Module._resolveFilename(request, mod, false); } require.resolve = resolve; function paths(request) { validateString(request, 'request'); - return Module._resolveLookupPaths(request, mod, true); + return Module._resolveLookupPaths(request, mod); } resolve.paths = paths; @@ -66,31 +79,17 @@ function stripBOM(content) { */ function stripShebang(content) { // Remove shebang - var contLen = content.length; - if (contLen >= 2) { - if (content.charCodeAt(0) === CHAR_HASH && - content.charCodeAt(1) === CHAR_EXCLAMATION_MARK) { - if (contLen === 2) { - // Exact match - content = ''; - } else { - // Find end of shebang line and slice it off - var i = 2; - for (; i < contLen; ++i) { - var code = content.charCodeAt(i); - if (code === CHAR_LINE_FEED || code === CHAR_CARRIAGE_RETURN) - break; - } - if (i === contLen) - content = ''; - else { - // Note that this actually includes the newline character(s) in the - // new output. This duplicates the behavior of the regular expression - // that was previously used to replace the shebang line - content = content.slice(i); - } - } - } + if (content.charAt(0) === '#' && content.charAt(1) === '!') { + // Find end of shebang line and slice it off + let index = content.indexOf('\n', 2); + if (index === -1) + return ''; + if (content.charAt(index - 1) === '\r') + index--; + // Note that this actually includes the newline character(s) in the + // new output. This duplicates the behavior of the regular expression + // that was previously used to replace the shebang line + content = content.slice(index); } return content; } diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index bf6a9d4029c2a5..edd8ea7f7a3edf 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -37,10 +37,12 @@ const { const { safeGetenv } = internalBinding('credentials'); const { makeRequireFunction, - requireDepth, stripBOM, stripShebang } = require('internal/modules/cjs/helpers'); +const { + decorateErrorStack +} = require('internal/util'); const { getOptionValue } = require('internal/options'); const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); @@ -52,64 +54,51 @@ const { } = require('internal/errors').codes; const { validateString } = require('internal/validators'); -module.exports = Module; - let asyncESM; let ModuleJob; let createDynamicModule; -let decorateErrorStack; - -function lazyLoadESM() { - asyncESM = require('internal/process/esm_loader'); - ModuleJob = require('internal/modules/esm/module_job'); - createDynamicModule = require( - 'internal/modules/esm/create_dynamic_module'); - decorateErrorStack = require('internal/util').decorateErrorStack; -} +let extensionNames; +let extensionCopy; +let getNodeModulePaths; const { - CHAR_UPPERCASE_A, - CHAR_LOWERCASE_A, - CHAR_UPPERCASE_Z, - CHAR_LOWERCASE_Z, CHAR_FORWARD_SLASH, CHAR_BACKWARD_SLASH, CHAR_COLON, CHAR_DOT, - CHAR_UNDERSCORE, - CHAR_0, - CHAR_9, } = require('internal/constants'); const isWindows = process.platform === 'win32'; +const statCache = new Map(); function stat(filename) { filename = path.toNamespacedPath(filename); - const cache = stat.cache; - if (cache !== null) { - const result = cache.get(filename); - if (result !== undefined) return result; - } - const result = internalModuleStat(filename); - if (cache !== null) cache.set(filename, result); + let result = statCache.get(filename); + if (result !== undefined) + return result; + result = internalModuleStat(filename); + if (statCache.size < 1e4) + statCache.set(filename, result); return result; } -stat.cache = null; -function updateChildren(parent, child, scan) { - var children = parent && parent.children; - if (children && !(scan && children.includes(child))) +function updateChildren(parent, child) { + const children = parent.children; + if (children.indexOf(child) === -1) children.push(child); } function Module(id, parent) { this.id = id; + this.path = path.dirname(id); this.exports = {}; this.parent = parent; - updateChildren(parent, this, false); this.filename = null; this.loaded = false; this.children = []; + this.paths = undefined; + if (parent !== undefined) + parent.children.push(this); } const builtinModules = Object.keys(NativeModule._source) @@ -120,8 +109,7 @@ Module.builtinModules = builtinModules; Module._cache = Object.create(null); Module._pathCache = Object.create(null); -Module._extensions = Object.create(null); -var modulePaths = []; +let modulePaths = []; Module.globalPaths = []; Module.wrap = function(script) { @@ -149,39 +137,33 @@ Module._debug = util.deprecate(debug, 'Module._debug is deprecated.', // -> a.<ext> // -> a/index.<ext> -// check if the directory is a package.json dir -const packageMainCache = Object.create(null); - +// Check if the directory is a package.json dir function readPackage(requestPath) { - const entry = packageMainCache[requestPath]; - if (entry) - return entry; - - const jsonPath = path.resolve(requestPath, 'package.json'); + const jsonPath = `${requestPath}${path.sep}package.json`; const json = internalModuleReadJSON(path.toNamespacedPath(jsonPath)); if (json === undefined) { - return false; + return ''; } try { - return packageMainCache[requestPath] = JSON.parse(json).main; + return JSON.parse(json).main; } catch (e) { e.path = jsonPath; - e.message = 'Error parsing ' + jsonPath + ': ' + e.message; + e.message = `Error parsing ${jsonPath}: ${e.message}`; throw e; } } -function tryPackage(requestPath, exts, isMain) { - var pkg = readPackage(requestPath); +function tryPackage(requestPath, isMain) { + const pkg = readPackage(requestPath); - if (!pkg) return false; + if (!pkg) return ''; - var filename = path.resolve(requestPath, pkg); + const filename = path.resolve(requestPath, pkg); return tryFile(filename, isMain) || - tryExtensions(filename, exts, isMain) || - tryExtensions(path.resolve(filename, 'index'), exts, isMain); + tryExtensions(filename, isMain) || + tryExtensions(`${filename}${path.sep}index`, isMain); } // In order to minimize unnecessary lstat() calls, @@ -189,16 +171,19 @@ function tryPackage(requestPath, exts, isMain) { // Set to an empty Map to reset. const realpathCache = new Map(); -// check if the file exists and is not a directory +// Check if the file exists and is not a directory // if using --preserve-symlinks and isMain is false, // keep symlinks intact, otherwise resolve to the // absolute realpath. function tryFile(requestPath, isMain) { const rc = stat(requestPath); + if (rc !== 0) { + return ''; + } if (preserveSymlinks && !isMain) { - return rc === 0 && path.resolve(requestPath); + return requestPath; } - return rc === 0 && toRealPath(requestPath); + return toRealPath(requestPath); } function toRealPath(requestPath) { @@ -207,16 +192,16 @@ function toRealPath(requestPath) { }); } -// Given a path, check if the file exists with any of the set extensions -function tryExtensions(p, exts, isMain) { - for (var i = 0; i < exts.length; i++) { - const filename = tryFile(p + exts[i], isMain); +// Given a path, check if the file exists with any of the set extensions. +function tryExtensions(p, isMain) { + for (var i = 0; i < extensionNames.length; i++) { + const filename = tryFile(`${p}${extensionNames[i]}`, isMain); - if (filename) { + if (filename !== '') { return filename; } } - return false; + return ''; } // Find the longest (possibly multi-dot) extension registered in @@ -224,56 +209,65 @@ function tryExtensions(p, exts, isMain) { function findLongestRegisteredExtension(filename) { const name = path.basename(filename); let currentExtension; - let index; - let startIndex = 0; - while ((index = name.indexOf('.', startIndex)) !== -1) { - startIndex = index + 1; - if (index === 0) continue; // Skip dotfiles like .gitignore + let index = 0; + do { + index = name.indexOf('.', index); currentExtension = name.slice(index); - if (Module._extensions[currentExtension]) return currentExtension; - } + if (extensionCopy[currentExtension]) { + if (index === 0) + continue; // Skip dotfiles like .gitignore + return currentExtension; + } + } while (index++ !== -1); return '.js'; } -var warned = false; -Module._findPath = function(request, paths, isMain) { - if (path.isAbsolute(request)) { - paths = ['']; - } else if (!paths || paths.length === 0) { +function hasTrailingSlash(request) { + let char = request.charCodeAt(request.length - 1); + if (char === CHAR_FORWARD_SLASH) { + return true; + } + if (char !== CHAR_DOT) { return false; } - - var cacheKey = request + '\x00' + - (paths.length === 1 ? paths[0] : paths.join('\x00')); - var entry = Module._pathCache[cacheKey]; - if (entry) - return entry; - - var exts; - var trailingSlash = request.length > 0 && - request.charCodeAt(request.length - 1) === CHAR_FORWARD_SLASH; - if (!trailingSlash) { - trailingSlash = /(?:^|\/)\.?\.$/.test(request); + if (request.length === 1) { + return true; + } + char = request.charCodeAt(request.length - 2); + if (char === CHAR_DOT) { + return request.length === 2 || + request.charCodeAt(request.length - 3) === CHAR_FORWARD_SLASH; } + return char === CHAR_FORWARD_SLASH; +} + +Module._findPath = function(request, paths, isMain) { + let trailingSlash = false; // For each path for (var i = 0; i < paths.length; i++) { // Don't search further if path doesn't exist const curPath = paths[i]; - if (curPath && stat(curPath) < 1) continue; - var basePath = path.resolve(curPath, request); - var filename; + const cacheKey = `${request}\x00${curPath}`; + const cache = Module._pathCache[cacheKey]; + if (cache !== undefined) { + if (cache === '') // Path does not contain anything. + continue; + return cache; + } + if (i === 0) + trailingSlash = hasTrailingSlash(request); - var rc = stat(basePath); - if (!trailingSlash) { + const basePath = curPath !== '' ? + path.resolve(curPath, request) : + path.resolve(request); + + let filename = ''; + + const rc = stat(basePath); + if (rc !== -1 && !trailingSlash) { if (rc === 0) { // File. - if (!isMain) { - if (preserveSymlinks) { - filename = path.resolve(basePath); - } else { - filename = toRealPath(basePath); - } - } else if (preserveSymlinksMain) { + if (!isMain && preserveSymlinks || preserveSymlinksMain) { // For the main module, we use the preserveSymlinksMain flag instead // mainly for backward compatibility, as the preserveSymlinks flag // historically has not applied to the main module. Most likely this @@ -282,72 +276,64 @@ Module._findPath = function(request, paths, isMain) { // files to resolve; that said, in some use cases following symlinks // causes bigger problems which is why the preserveSymlinksMain option // is needed. - filename = path.resolve(basePath); + filename = basePath; } else { filename = toRealPath(basePath); } - } - - if (!filename) { - // try it with each of the extensions - if (exts === undefined) - exts = Object.keys(Module._extensions); - filename = tryExtensions(basePath, exts, isMain); + } else { + // Try it with each of the extensions + filename = tryExtensions(basePath, isMain); } } - if (!filename && rc === 1) { // Directory. - // try it with each of the extensions at "index" - if (exts === undefined) - exts = Object.keys(Module._extensions); - filename = tryPackage(basePath, exts, isMain); - if (!filename) { - filename = tryExtensions(path.resolve(basePath, 'index'), exts, isMain); + if (rc === 1 && filename === '') { // Directory. + // Try it with each of the extensions at "index" + filename = tryPackage(basePath, isMain); + if (filename === '') { + filename = tryExtensions(`${basePath}${path.sep}index`, isMain); } } - if (filename) { - // Warn once if '.' resolved outside the module dir - if (request === '.' && i > 0) { - if (!warned) { - warned = true; - process.emitWarning( - 'warning: require(\'.\') resolved outside the package ' + - 'directory. This functionality is deprecated and will be removed ' + - 'soon.', - 'DeprecationWarning', 'DEP0019'); - } - } - + if (filename !== '') { Module._pathCache[cacheKey] = filename; return filename; } - } - return false; + // Signal that we already checked this path and that it did not contain + // a file. + Module._pathCache[cacheKey] = ''; + } + // eslint-disable-next-line no-restricted-syntax + const err = new Error(`Cannot find module '${request}'`); + err.code = 'MODULE_NOT_FOUND'; + throw err; +}; + +// This wrapper exists for backwards compatibility. It just guarantees that +// `from` is absolute. +Module._nodeModulePaths = function(from) { + return getNodeModulePaths(path.resolve(from)); }; // 'node_modules' character codes reversed -var nmChars = [ 115, 101, 108, 117, 100, 111, 109, 95, 101, 100, 111, 110 ]; -var nmLen = nmChars.length; +const nmChars = [ 115, 101, 108, 117, 100, 111, 109, 95, 101, 100, 111, 110 ]; +const nmLen = nmChars.length; if (isWindows) { // 'from' is the __dirname of the module. - Module._nodeModulePaths = function(from) { - // guarantee that 'from' is absolute. - from = path.resolve(from); - - // note: this approach *only* works when the path is guaranteed + getNodeModulePaths = (from) => { + // Note: this approach *only* works when the path is guaranteed // to be absolute. Doing a fully-edge-case-correct path.split // that works on both Windows and Posix is non-trivial. - // return root node_modules when path is 'D:\\'. + // Return root node_modules when path is 'D:\\'. // path.resolve will make sure from.length >=3 in Windows. if (from.charCodeAt(from.length - 1) === CHAR_BACKWARD_SLASH && - from.charCodeAt(from.length - 2) === CHAR_COLON) - return [from + 'node_modules']; + from.charCodeAt(from.length - 2) === CHAR_COLON) { + return [`${from}node_modules`]; + } const paths = []; - var p = 0; - var last = from.length; + let p = 0; + let last = from.length; for (var i = from.length - 1; i >= 0; --i) { const code = from.charCodeAt(i); // The path segment separator check ('\' and '/') was used to get @@ -359,7 +345,7 @@ if (isWindows) { code === CHAR_FORWARD_SLASH || code === CHAR_COLON) { if (p !== nmLen) - paths.push(from.slice(0, last) + '\\node_modules'); + paths.push(`${from.slice(0, last)}\\node_modules`); last = i; p = 0; } else if (p !== -1) { @@ -370,30 +356,31 @@ if (isWindows) { } } } - return paths; }; -} else { // posix - // 'from' is the __dirname of the module. - Module._nodeModulePaths = function(from) { - // guarantee that 'from' is absolute. - from = path.resolve(from); +} else { // Posix + getNodeModulePaths = (from) => { + // TODO(BridgeAR): Consider checking for the directory existence and to only + // return existing ones + adding a local cache to prevent calculating the + // path again for the same directory. This would slow down this function + // while decreasing the amount of `_pathCache` entries. + // Return early not only to avoid unnecessary work, but to *avoid* returning // an array of two items for a root: [ '//node_modules', '/node_modules' ] - if (from === '/') + if (from.length === 1) return ['/node_modules']; - // note: this approach *only* works when the path is guaranteed + // Note: this approach *only* works when the path is guaranteed // to be absolute. Doing a fully-edge-case-correct path.split // that works on both Windows and Posix is non-trivial. const paths = []; - var p = 0; - var last = from.length; + let p = 0; + let last = from.length; for (var i = from.length - 1; i >= 0; --i) { const code = from.charCodeAt(i); if (code === CHAR_FORWARD_SLASH) { if (p !== nmLen) - paths.push(from.slice(0, last) + '/node_modules'); + paths.push(`${from.slice(0, last)}/node_modules`); last = i; p = 0; } else if (p !== -1) { @@ -407,113 +394,49 @@ if (isWindows) { // Append /node_modules to handle root paths. paths.push('/node_modules'); - return paths; }; } - -// 'index.' character codes -var indexChars = [ 105, 110, 100, 101, 120, 46 ]; -var indexLen = indexChars.length; -Module._resolveLookupPaths = function(request, parent, newReturn) { - if (NativeModule.nonInternalExists(request)) { - debug('looking for %j in []', request); - return (newReturn ? null : [request, []]); - } - - // Check for non-relative path - if (request.length < 2 || - request.charCodeAt(0) !== CHAR_DOT || - (request.charCodeAt(1) !== CHAR_DOT && - request.charCodeAt(1) !== CHAR_FORWARD_SLASH && - (!isWindows || request.charCodeAt(1) !== CHAR_BACKWARD_SLASH))) { - var paths = modulePaths; - if (parent) { - if (!parent.paths) - paths = parent.paths = []; - else - paths = parent.paths.concat(paths); - } - - // Maintain backwards compat with certain broken uses of require('.') - // by putting the module's directory in front of the lookup paths. - if (request === '.') { - if (parent && parent.filename) { - paths.unshift(path.dirname(parent.filename)); - } else { - paths.unshift(path.resolve(request)); - } +Module._resolveLookupPaths = function(request, parent) { + if (path.isAbsolute(request)) { + debug('looking for absolute path "%s"', request); + return ['']; + } + + // Check for node modules paths. + if (request.charAt(0) !== '.' || + (request.length > 1 && + request.charAt(1) !== '.' && + request.charAt(1) !== '/' && + (!isWindows || request.charAt(1) !== '\\'))) { + let paths = modulePaths; + if (parent != null && parent.paths.length) { + paths = parent.paths.concat(paths); } - debug('looking for %j in %j', request, paths); - return (newReturn ? (paths.length > 0 ? paths : null) : [request, paths]); + debug('looking for "%s" in %j', request, paths); + return paths; } - // with --eval, parent.id is not set and parent.filename is null + // With --eval, parent.id is not set and parent.filename is null. if (!parent || !parent.id || !parent.filename) { // Make require('./path/to/foo') work - normally the path is taken // from realpath(__filename) but with eval there is no filename - var mainPaths = ['.'].concat(Module._nodeModulePaths('.'), modulePaths); + const mainPaths = ['.'].concat( + getNodeModulePaths(process.cwd()), + modulePaths + ); - debug('looking for %j in %j', request, mainPaths); - return (newReturn ? mainPaths : [request, mainPaths]); + debug('looking for "%s" in %j', request, mainPaths); + return mainPaths; } - // Is the parent an index module? - // We can assume the parent has a valid extension, - // as it already has been accepted as a module. - const base = path.basename(parent.filename); - var parentIdPath; - if (base.length > indexLen) { - var i = 0; - for (; i < indexLen; ++i) { - if (indexChars[i] !== base.charCodeAt(i)) - break; - } - if (i === indexLen) { - // We matched 'index.', let's validate the rest - for (; i < base.length; ++i) { - const code = base.charCodeAt(i); - if (code !== CHAR_UNDERSCORE && - (code < CHAR_0 || code > CHAR_9) && - (code < CHAR_UPPERCASE_A || code > CHAR_UPPERCASE_Z) && - (code < CHAR_LOWERCASE_A || code > CHAR_LOWERCASE_Z)) - break; - } - if (i === base.length) { - // Is an index module - parentIdPath = parent.id; - } else { - // Not an index module - parentIdPath = path.dirname(parent.id); - } - } else { - // Not an index module - parentIdPath = path.dirname(parent.id); - } - } else { - // Not an index module - parentIdPath = path.dirname(parent.id); - } - var id = path.resolve(parentIdPath, request); - - // Make sure require('./path') and require('path') get distinct ids, even - // when called from the toplevel js file - if (parentIdPath === '.' && - id.indexOf('/') === -1 && - (!isWindows || id.indexOf('\\') === -1)) { - id = './' + id; - } - - debug('RELATIVE: requested: %s set ID to: %s from %s', request, id, - parent.id); - - var parentDir = [path.dirname(parent.filename)]; - debug('looking for %j in %j', id, parentDir); - return (newReturn ? parentDir : [id, parentDir]); + return [parent.path]; }; +const filenameCache = Object.create(null); + // Check the cache for the requested file. // 1. If a module already exists in the cache: return its exports object. // 2. If the module is native: call `NativeModule.require()` with the @@ -522,25 +445,46 @@ Module._resolveLookupPaths = function(request, parent, newReturn) { // Then have it load the file contents before returning its exports // object. Module._load = function(request, parent, isMain) { - if (parent) { + let identifier; + if (parent !== undefined) { debug('Module._load REQUEST %s parent: %s', request, parent.id); - } - var filename = Module._resolveFilename(request, parent, isMain); - - var cachedModule = Module._cache[filename]; - if (cachedModule) { - updateChildren(parent, cachedModule, true); - return cachedModule.exports; + // Fast path for (lazy loaded) modules in the same directory. The indirect + // caching is required to allow cache invalidation without changing the old + // cache key names. + identifier = `${parent.path}\x00${request}`; + const filename = filenameCache[identifier]; + if (filename !== undefined) { + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + updateChildren(parent, cachedModule, true); + return cachedModule.exports; + } + delete filenameCache[identifier]; + } } - if (NativeModule.nonInternalExists(filename)) { + // Check for native modules first. If we find such name in the user module + // cache, bail out. This keeps the behavior backwards compatible. It was + // originally meant to allow mocking of whole modules by placing a native + // module name in there. + if (NativeModule.nonInternalExists(request) && + Module._cache[request] === undefined) { debug('load native module %s', request); - return NativeModule.require(filename); + return NativeModule.require(request); + } + + const filename = Module._resolveFilename(request, parent, isMain); + + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + if (parent !== undefined) + updateChildren(parent, cachedModule, true); + return cachedModule.exports; } // Don't call updateChildren(), Module constructor already does. - var module = new Module(filename, parent); + const module = new Module(filename, parent); if (isMain) { process.mainModule = module; @@ -548,87 +492,58 @@ Module._load = function(request, parent, isMain) { } Module._cache[filename] = module; + if (parent !== undefined) + filenameCache[identifier] = filename; - tryModuleLoad(module, filename); - - return module.exports; -}; - -function tryModuleLoad(module, filename) { - var threw = true; + let threw = true; try { module.load(filename); threw = false; } finally { if (threw) { delete Module._cache[filename]; + if (parent !== undefined) + delete filenameCache[identifier]; } } -} -Module._resolveFilename = function(request, parent, isMain, options) { + return module.exports; +}; + +Module._resolveFilename = function(request, parent, isMain, paths) { if (NativeModule.nonInternalExists(request)) { return request; } - var paths; - - if (typeof options === 'object' && options !== null && - Array.isArray(options.paths)) { - const fakeParent = new Module('', null); + if (paths === undefined) + paths = Module._resolveLookupPaths(request, parent); - paths = []; - - for (var i = 0; i < options.paths.length; i++) { - const path = options.paths[i]; - fakeParent.paths = Module._nodeModulePaths(path); - const lookupPaths = Module._resolveLookupPaths(request, fakeParent, true); - - for (var j = 0; j < lookupPaths.length; j++) { - if (!paths.includes(lookupPaths[j])) - paths.push(lookupPaths[j]); - } - } - } else { - paths = Module._resolveLookupPaths(request, parent, true); - } - - // Look up the filename first, since that's the cache key. - var filename = Module._findPath(request, paths, isMain); - if (!filename) { - // eslint-disable-next-line no-restricted-syntax - var err = new Error(`Cannot find module '${request}'`); - err.code = 'MODULE_NOT_FOUND'; - throw err; - } - return filename; + return Module._findPath(request, paths, isMain); }; - // Given a file name, pass it to the proper extension handler. Module.prototype.load = function(filename) { - debug('load %j for module %j', filename, this.id); + debug('load "%s" for module "%s"', filename, this.id); assert(!this.loaded); this.filename = filename; - this.paths = Module._nodeModulePaths(path.dirname(filename)); + this.paths = getNodeModulePaths(this.path); - var extension = findLongestRegisteredExtension(filename); + const extension = findLongestRegisteredExtension(filename); Module._extensions[extension](this, filename); this.loaded = true; if (experimentalModules) { - if (asyncESM === undefined) lazyLoadESM(); const ESMLoader = asyncESM.ESMLoader; const url = `${pathToFileURL(filename)}`; const module = ESMLoader.moduleMap.get(url); // Create module entry at load time to snapshot exports correctly const exports = this.exports; - if (module !== undefined) { // called from cjs translator + if (module !== undefined) { // Called from cjs translator module.reflect.onReady((reflect) => { reflect.exports.default.set(exports); }); - } else { // preemptively cache + } else { // Preemptively cache ESMLoader.moduleMap.set( url, new ModuleJob(ESMLoader, url, async () => { @@ -642,7 +557,6 @@ Module.prototype.load = function(filename) { } }; - // Loads a module at the given file path. Returns that module's // `exports` property. Module.prototype.require = function(id) { @@ -654,10 +568,9 @@ Module.prototype.require = function(id) { return Module._load(id, this, /* isMain */ false); }; - // Resolved path to process.argv[1] will be lazily placed here // (needed for setting breakpoint when called with --inspect-brk) -var resolvedArgv; +let resolvedArgv; function normalizeReferrerURL(referrer) { if (typeof referrer === 'string' && path.isAbsolute(referrer)) { @@ -666,99 +579,52 @@ function normalizeReferrerURL(referrer) { return new URL(referrer).href; } - // Run the file contents in the correct scope or sandbox. Expose // the correct helper variables (require, module, exports) to // the file. // Returns exception, if any. Module.prototype._compile = function(content, filename) { - content = stripShebang(content); - // create wrapper function - var wrapper = Module.wrap(content); + // Create wrapper function + const wrapper = Module.wrap(content); - var compiledWrapper = vm.runInThisContext(wrapper, { + const compiledWrapper = vm.runInThisContext(wrapper, { filename: filename, lineOffset: 0, displayErrors: true, importModuleDynamically: experimentalModules ? async (specifier) => { - if (asyncESM === undefined) lazyLoadESM(); const loader = await asyncESM.loaderPromise; return loader.import(specifier, normalizeReferrerURL(filename)); } : undefined, }); - var inspectorWrapper = null; - if (process._breakFirstLine && process._eval == null) { - if (!resolvedArgv) { - // We enter the repl if we're not given a filename argument. - if (process.argv[1]) { - resolvedArgv = Module._resolveFilename(process.argv[1], null, false); - } else { - resolvedArgv = 'repl'; - } - } + const require = makeRequireFunction(this); + if (process._breakFirstLine && process._eval == null && !resolvedArgv) { + // We enter the repl if we're not given a filename argument. + if (process.argv[1]) { + resolvedArgv = Module._resolveFilename(process.argv[1], null, false); + } else { + resolvedArgv = 'repl'; + } // Set breakpoint on module start if (filename === resolvedArgv) { delete process._breakFirstLine; - inspectorWrapper = internalBinding('inspector').callAndPauseOnStart; + const inspectorWrapper = internalBinding('inspector').callAndPauseOnStart; + return inspectorWrapper(compiledWrapper, this.exports, this.exports, + require, this, filename, this.path); } } - var dirname = path.dirname(filename); - var require = makeRequireFunction(this); - var depth = requireDepth; - if (depth === 0) stat.cache = new Map(); - var result; - if (inspectorWrapper) { - result = inspectorWrapper(compiledWrapper, this.exports, this.exports, - require, this, filename, dirname); - } else { - result = compiledWrapper.call(this.exports, this.exports, require, this, - filename, dirname); - } - if (depth === 0) stat.cache = null; - return result; -}; - - -// Native extension for .js -Module._extensions['.js'] = function(module, filename) { - var content = fs.readFileSync(filename, 'utf8'); - module._compile(stripBOM(content), filename); -}; - - -// Native extension for .json -Module._extensions['.json'] = function(module, filename) { - var content = fs.readFileSync(filename, 'utf8'); - try { - module.exports = JSON.parse(stripBOM(content)); - } catch (err) { - err.message = filename + ': ' + err.message; - throw err; - } -}; - -// Native extension for .node -Module._extensions['.node'] = function(module, filename) { - return process.dlopen(module, path.toNamespacedPath(filename)); + return compiledWrapper.call(this.exports, this.exports, require, this, + filename, this.path); }; -if (experimentalModules) { - if (asyncESM === undefined) lazyLoadESM(); - Module._extensions['.mjs'] = function(module, filename) { - throw new ERR_REQUIRE_ESM(filename); - }; -} - -// bootstrap main module. +// Bootstrap main module. Module.runMain = function() { // Load the main module--the command line argument. if (experimentalModules) { - if (asyncESM === undefined) lazyLoadESM(); asyncESM.loaderPromise.then((loader) => { return loader.import(pathToFileURL(process.argv[1]).pathname); }) @@ -768,7 +634,7 @@ Module.runMain = function() { process.exit(1); }); } else { - Module._load(process.argv[1], null, true); + Module._load(process.argv[1], undefined, true); } // Handle any nextTicks added in the first tick of the program process._tickCallback(); @@ -777,31 +643,28 @@ Module.runMain = function() { Module.createRequireFromPath = (filename) => { const m = new Module(filename); m.filename = filename; - m.paths = Module._nodeModulePaths(path.dirname(filename)); + m.paths = getNodeModulePaths(m.path); return makeRequireFunction(m); }; Module._initPaths = function() { - var homeDir; - var nodePath; + let homeDir; + let nodePath; + // $PREFIX/lib/node, where $PREFIX is the root of the Node.js installation. + // `process.execPath` is $PREFIX/bin/node except on Windows where it is + // $PREFIX\node.exe. + let prefixDir; if (isWindows) { homeDir = process.env.USERPROFILE; nodePath = process.env.NODE_PATH; + prefixDir = path.resolve(process.execPath, '..'); } else { homeDir = safeGetenv('HOME'); nodePath = safeGetenv('NODE_PATH'); - } - - // $PREFIX/lib/node, where $PREFIX is the root of the Node.js installation. - var prefixDir; - // process.execPath is $PREFIX/bin/node except on Windows where it is - // $PREFIX\node.exe. - if (isWindows) { - prefixDir = path.resolve(process.execPath, '..'); - } else { prefixDir = path.resolve(process.execPath, '..', '..'); } - var paths = [path.resolve(prefixDir, 'lib', 'node')]; + + let paths = [path.resolve(prefixDir, 'lib', 'node')]; if (homeDir) { paths.unshift(path.resolve(homeDir, '.node_libraries')); @@ -809,37 +672,141 @@ Module._initPaths = function() { } if (nodePath) { - paths = nodePath.split(path.delimiter).filter(function pathsFilterCB(path) { - return !!path; - }).concat(paths); + paths = nodePath.split(path.delimiter) + .filter((path) => !!path) + .concat(paths); } - modulePaths = paths; + // Filter all paths that do not exist or that resolve to a file instead of a + // directory. + modulePaths = paths.filter((path) => stat(path) === 1); - // clone as a shallow copy, for introspection. - Module.globalPaths = modulePaths.slice(0); + // Use the original input for introspection. + Module.globalPaths = paths; }; Module._preloadModules = function(requests) { - if (!Array.isArray(requests)) - return; - // Preloaded modules have a dummy parent module which is deemed to exist // in the current working directory. This seeds the search path for // preloaded modules. - var parent = new Module('internal/preload', null); + const parent = new Module('internal/preload'); try { - parent.paths = Module._nodeModulePaths(process.cwd()); + parent.paths = getNodeModulePaths(process.cwd()); } catch (e) { if (e.code !== 'ENOENT') { throw e; } + parent.paths = []; + } + for (const request of requests) { + parent.require(request); } - for (var n = 0; n < requests.length; n++) - parent.require(requests[n]); }; Module._initPaths(); // Backwards compatibility Module.Module = Module; + +const extensions = Object.create(null); + +// Native extension for .js +extensions['.js'] = function(module, filename) { + const content = fs.readFileSync(filename, 'utf8'); + module._compile(stripBOM(content), filename); +}; + +// Native extension for .json +extensions['.json'] = function(module, filename) { + const content = fs.readFileSync(filename, 'utf8'); + try { + module.exports = JSON.parse(stripBOM(content)); + } catch (err) { + err.message = `${filename}: ${err.message}`; + throw err; + } +}; + +// Native extension for .node +extensions['.node'] = function(module, filename) { + return process.dlopen(module, path.toNamespacedPath(filename)); +}; + +if (experimentalModules) { + extensions['.mjs'] = function(module, filename) { + throw new ERR_REQUIRE_ESM(filename); + }; +} + +// Use proxies to optimize resetting the extensions. This would otherwise be a +// significant overhead as we have to check for all object keys each time a +// uncached module is loaded (everything else would be a breaking change). +function setExtensionsProxy(extensions) { + // Intersect manipulating the extensions. + extensionNames = Object.keys(extensions); + extensionCopy = { ...extensions }; + return new Proxy(extensions, { + set(obj, prop, value) { + if (extensions[prop] === undefined) { + extensionCopy[prop] = value; + extensionNames.push(prop); + } + return Reflect.set(obj, prop, value); + }, + defineProperty(target, prop, descriptor) { + if (extensions[prop] === undefined) { + Object.defineProperty(extensionCopy, prop, descriptor); + extensionNames.push(prop); + } + return Reflect.defineProperty(target, prop, descriptor); + }, + deleteProperty(target, prop) { + const pos = extensionNames.indexOf(prop); + if (pos !== -1) { + delete extensionCopy[prop]; + extensionNames.splice(pos, 1); + } + return Reflect.deleteProperty(target, prop); + } + }); +} + +// Intersect manipulating the extensions. +const ProxyModule = new Proxy(Module, { + set(obj, prop, value) { + if (prop === '_extensions') { + value = setExtensionsProxy(value); + } + return Reflect.set(obj, prop, value); + }, + defineProperty(target, prop, descriptor) { + if (prop === '_extensions') { + if (descriptor.value) { + descriptor = { + ...descriptor, + value: setExtensionsProxy(descriptor.value) + }; + } else { + descriptor = { + ...descriptor, + get() { + return setExtensionsProxy(descriptor.get()); + } + }; + } + } + return Reflect.defineProperty(target, prop, descriptor); + } +}); + +Module._extensions = setExtensionsProxy(extensions); + +module.exports = ProxyModule; + +// We have to load the esm things after module.exports! +if (experimentalModules) { + asyncESM = require('internal/process/esm_loader'); + ModuleJob = require('internal/modules/esm/module_job'); + createDynamicModule = require( + 'internal/modules/esm/create_dynamic_module'); +} diff --git a/lib/repl.js b/lib/repl.js index 30812c232b7639..c1852ee395e970 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -829,7 +829,7 @@ REPLServer.prototype.createContext = function() { var module = new CJSModule('<repl>'); module.paths = - CJSModule._resolveLookupPaths('<repl>', parentModule, true) || []; + CJSModule._resolveLookupPaths('<repl>', parentModule) || []; Object.defineProperty(context, 'module', { configurable: true, diff --git a/test/es-module/test-esm-namespace.mjs b/test/es-module/test-esm-namespace.mjs index da1286d0f40e67..70e7975a64c085 100644 --- a/test/es-module/test-esm-namespace.mjs +++ b/test/es-module/test-esm-namespace.mjs @@ -5,7 +5,7 @@ import assert from 'assert'; import Module from 'module'; const keys = Object.entries( - Object.getOwnPropertyDescriptors(new Module().require('fs'))) + Object.getOwnPropertyDescriptors(new Module('').require('fs'))) .filter(([ , d]) => d.enumerable) .map(([name]) => name) .concat('default') diff --git a/test/fixtures/module-require-depth/one.js b/test/fixtures/module-require-depth/one.js deleted file mode 100644 index 02b451465bb76c..00000000000000 --- a/test/fixtures/module-require-depth/one.js +++ /dev/null @@ -1,11 +0,0 @@ -// Flags: --expose_internals -'use strict'; -const assert = require('assert'); -const { - requireDepth -} = require('internal/modules/cjs/helpers'); - -exports.requireDepth = requireDepth; -assert.strictEqual(requireDepth, 1); -assert.deepStrictEqual(require('./two'), { requireDepth: 2 }); -assert.strictEqual(requireDepth, 1); diff --git a/test/fixtures/module-require-depth/two.js b/test/fixtures/module-require-depth/two.js deleted file mode 100644 index 5c94c4c89aa9ef..00000000000000 --- a/test/fixtures/module-require-depth/two.js +++ /dev/null @@ -1,11 +0,0 @@ -// Flags: --expose_internals -'use strict'; -const assert = require('assert'); -const { - requireDepth -} = require('internal/modules/cjs/helpers'); - -exports.requireDepth = requireDepth; -assert.strictEqual(requireDepth, 2); -assert.deepStrictEqual(require('./one'), { requireDepth: 1 }); -assert.strictEqual(requireDepth, 2); diff --git a/test/fixtures/require-resolve.js b/test/fixtures/require-resolve.js index 2a0dc8846ce3cb..a6a85f354d3b50 100644 --- a/test/fixtures/require-resolve.js +++ b/test/fixtures/require-resolve.js @@ -14,8 +14,28 @@ assert.strictEqual( // Verify that existing paths are removed. assert.throws(() => { - require.resolve('bar', { paths: [] }) -}, /^Error: Cannot find module 'bar'$/); + require.resolve('bar', { paths: ['/zzzzzzz'] }) +}, { + name: 'Error', + code: 'MODULE_NOT_FOUND', + message: "Cannot find module 'bar'" +}); + +// Verify invalid paths throw. +{ + assert.throws(() => { + require.resolve('bar', { paths: [] }) + }, { + name: 'RangeError [ERR_OUT_OF_RANGE]', + code: 'ERR_OUT_OF_RANGE' + }); + assert.throws(() => { + require.resolve('bar', { paths: '/path' }) + }, { + name: 'TypeError [ERR_INVALID_ARG_TYPE]', + code: 'ERR_INVALID_ARG_TYPE' + }); +} // Verify that resolution path can be overwritten. { diff --git a/test/message/core_line_numbers.out b/test/message/core_line_numbers.out index 59953132fa0542..0670b24b36382d 100644 --- a/test/message/core_line_numbers.out +++ b/test/message/core_line_numbers.out @@ -7,9 +7,9 @@ RangeError: Invalid input at Object.decode (punycode.js:*:*) at Object.<anonymous> (*test*message*core_line_numbers.js:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:*:*) + at Proxy.extensions..js (internal/modules/cjs/loader.js:*:*) at Module.load (internal/modules/cjs/loader.js:*:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*:*) at executeUserCode (internal/bootstrap/node.js:*:*) + at startExecution (internal/bootstrap/node.js:*:*) diff --git a/test/message/error_exit.out b/test/message/error_exit.out index 6e9b42167dde18..f3f4d05b9d5510 100644 --- a/test/message/error_exit.out +++ b/test/message/error_exit.out @@ -9,10 +9,11 @@ AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: at Object.<anonymous> (*test*message*error_exit.js:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:*:*) + at Proxy.extensions..js (internal/modules/cjs/loader.js:*:*) at Module.load (internal/modules/cjs/loader.js:*:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*:*) at executeUserCode (internal/bootstrap/node.js:*:*) at startExecution (internal/bootstrap/node.js:*:*) + at startup (internal/bootstrap/node.js:*:*) + at internal/bootstrap/node.js:*:* diff --git a/test/message/events_unhandled_error_common_trace.out b/test/message/events_unhandled_error_common_trace.out index 67be22554fb41e..b66f2e07e10def 100644 --- a/test/message/events_unhandled_error_common_trace.out +++ b/test/message/events_unhandled_error_common_trace.out @@ -7,16 +7,16 @@ Error: foo:bar at foo (*events_unhandled_error_common_trace.js:*:*) at Object.<anonymous> (*events_unhandled_error_common_trace.js:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:*:*) + at Proxy.extensions..js (internal/modules/cjs/loader.js:*:*) at Module.load (internal/modules/cjs/loader.js:*:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*:*) at executeUserCode (internal/bootstrap/node.js:*:*) + at startExecution (internal/bootstrap/node.js:*:*) Emitted 'error' event at: at quux (*events_unhandled_error_common_trace.js:*:*) at Object.<anonymous> (*events_unhandled_error_common_trace.js:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) [... lines matching original stack trace ...] - at executeUserCode (internal/bootstrap/node.js:*:*) at startExecution (internal/bootstrap/node.js:*:*) + at startup (internal/bootstrap/node.js:*:*) diff --git a/test/message/events_unhandled_error_nexttick.out b/test/message/events_unhandled_error_nexttick.out index cd098b64bfb5f1..a2fd13843c288a 100644 --- a/test/message/events_unhandled_error_nexttick.out +++ b/test/message/events_unhandled_error_nexttick.out @@ -5,17 +5,19 @@ events.js:* Error at Object.<anonymous> (*events_unhandled_error_nexttick.js:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:*:*) + at Proxy.extensions..js (internal/modules/cjs/loader.js:*:*) at Module.load (internal/modules/cjs/loader.js:*:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*:*) at executeUserCode (internal/bootstrap/node.js:*:*) at startExecution (internal/bootstrap/node.js:*:*) + at startup (internal/bootstrap/node.js:*:*) + at internal/bootstrap/node.js:*:* Emitted 'error' event at: at process.nextTick (*events_unhandled_error_nexttick.js:*:*) at internalTickCallback (internal/process/next_tick.js:*:*) at process.runNextTicks [as _tickCallback] (internal/process/next_tick.js:*:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*:*) at executeUserCode (internal/bootstrap/node.js:*:*) - at startExecution (internal/bootstrap/node.js:*:*) + [... lines matching original stack trace ...] + at internal/bootstrap/node.js:*:* diff --git a/test/message/events_unhandled_error_sameline.out b/test/message/events_unhandled_error_sameline.out index 678a9ae4b11d7d..5dced13625150f 100644 --- a/test/message/events_unhandled_error_sameline.out +++ b/test/message/events_unhandled_error_sameline.out @@ -5,15 +5,16 @@ events.js:* Error at Object.<anonymous> (*events_unhandled_error_sameline.js:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:*:*) + at Proxy.extensions..js (internal/modules/cjs/loader.js:*:*) at Module.load (internal/modules/cjs/loader.js:*:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*:*) at executeUserCode (internal/bootstrap/node.js:*:*) at startExecution (internal/bootstrap/node.js:*:*) + at startup (internal/bootstrap/node.js:*:*) + at internal/bootstrap/node.js:*:* Emitted 'error' event at: at Object.<anonymous> (*events_unhandled_error_sameline.js:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) [... lines matching original stack trace ...] - at startExecution (internal/bootstrap/node.js:*:*) + at internal/bootstrap/node.js:*:* diff --git a/test/message/if-error-has-good-stack.out b/test/message/if-error-has-good-stack.out index 2a4dfe8fb26630..a11e07981e4c6a 100644 --- a/test/message/if-error-has-good-stack.out +++ b/test/message/if-error-has-good-stack.out @@ -12,8 +12,8 @@ AssertionError [ERR_ASSERTION]: ifError got unwanted exception: test error at a (*if-error-has-good-stack.js:*:*) at Object.<anonymous> (*if-error-has-good-stack.js:*:*) at Module._compile (internal/modules/cjs/loader.js:*:*) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:*:*) + at Proxy.extensions..js (internal/modules/cjs/loader.js:*:*) at Module.load (internal/modules/cjs/loader.js:*:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*:*) + at executeUserCode (internal/bootstrap/node.js:*:*) diff --git a/test/message/nexttick_throw.out b/test/message/nexttick_throw.out index 6a4d7ed2dabcb0..7228f15bae5a02 100644 --- a/test/message/nexttick_throw.out +++ b/test/message/nexttick_throw.out @@ -6,6 +6,6 @@ ReferenceError: undefined_reference_error_maker is not defined at *test*message*nexttick_throw.js:*:* at internalTickCallback (internal/process/next_tick.js:*:*) at process.runNextTicks [as _tickCallback] (internal/process/next_tick.js:*:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*:*) at executeUserCode (internal/bootstrap/node.js:*:*) at startExecution (internal/bootstrap/node.js:*:*) diff --git a/test/message/undefined_reference_in_new_context.out b/test/message/undefined_reference_in_new_context.out index 6b5fedfa993701..85ce0de574b673 100644 --- a/test/message/undefined_reference_in_new_context.out +++ b/test/message/undefined_reference_in_new_context.out @@ -12,5 +12,5 @@ ReferenceError: foo is not defined at Module._compile (internal/modules/cjs/loader.js:*) at *..js (internal/modules/cjs/loader.js:*) at Module.load (internal/modules/cjs/loader.js:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*:*) diff --git a/test/message/vm_display_runtime_error.out b/test/message/vm_display_runtime_error.out index bc95b2a7b092f5..6079d01af5f4c9 100644 --- a/test/message/vm_display_runtime_error.out +++ b/test/message/vm_display_runtime_error.out @@ -9,11 +9,11 @@ Error: boo! at Object.runInThisContext (vm.js:*) at Object.<anonymous> (*test*message*vm_display_runtime_error.js:*) at Module._compile (internal/modules/cjs/loader.js:*) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:*) + at Proxy.extensions..js (internal/modules/cjs/loader.js:*:*) at Module.load (internal/modules/cjs/loader.js:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*) + at executeUserCode (internal/bootstrap/node.js:*:*) test.vm:1 throw new Error("spooky!") ^ @@ -24,8 +24,8 @@ Error: spooky! at Object.runInThisContext (vm.js:*) at Object.<anonymous> (*test*message*vm_display_runtime_error.js:*) at Module._compile (internal/modules/cjs/loader.js:*) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:*) + at Proxy.extensions..js (internal/modules/cjs/loader.js:*:*) at Module.load (internal/modules/cjs/loader.js:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*) + at executeUserCode (internal/bootstrap/node.js:*:*) diff --git a/test/message/vm_display_syntax_error.out b/test/message/vm_display_syntax_error.out index f0692723e81257..50a00d37c9dd33 100644 --- a/test/message/vm_display_syntax_error.out +++ b/test/message/vm_display_syntax_error.out @@ -8,11 +8,11 @@ SyntaxError: Unexpected number at Object.runInThisContext (vm.js:*) at Object.<anonymous> (*test*message*vm_display_syntax_error.js:*) at Module._compile (internal/modules/cjs/loader.js:*) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:*) + at Proxy.extensions..js (internal/modules/cjs/loader.js:*:*) at Module.load (internal/modules/cjs/loader.js:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*) + at executeUserCode (internal/bootstrap/node.js:*:*) test.vm:1 var 5; ^ @@ -22,8 +22,8 @@ SyntaxError: Unexpected number at Object.runInThisContext (vm.js:*) at Object.<anonymous> (*test*message*vm_display_syntax_error.js:*) at Module._compile (internal/modules/cjs/loader.js:*) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:*) + at Proxy.extensions..js (internal/modules/cjs/loader.js:*:*) at Module.load (internal/modules/cjs/loader.js:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*) + at executeUserCode (internal/bootstrap/node.js:*:*) diff --git a/test/message/vm_dont_display_runtime_error.out b/test/message/vm_dont_display_runtime_error.out index 532cfbf4dd8125..daeb367376d3b6 100644 --- a/test/message/vm_dont_display_runtime_error.out +++ b/test/message/vm_dont_display_runtime_error.out @@ -10,8 +10,8 @@ Error: boo! at Object.runInThisContext (vm.js:*) at Object.<anonymous> (*test*message*vm_dont_display_runtime_error.js:*) at Module._compile (internal/modules/cjs/loader.js:*) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:*) + at Proxy.extensions..js (internal/modules/cjs/loader.js:*:*) at Module.load (internal/modules/cjs/loader.js:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*) + at executeUserCode (internal/bootstrap/node.js:*:*) diff --git a/test/message/vm_dont_display_syntax_error.out b/test/message/vm_dont_display_syntax_error.out index f27cb1a0859d8c..31328218f9b02e 100644 --- a/test/message/vm_dont_display_syntax_error.out +++ b/test/message/vm_dont_display_syntax_error.out @@ -10,8 +10,8 @@ SyntaxError: Unexpected number at Object.runInThisContext (vm.js:*) at Object.<anonymous> (*test*message*vm_dont_display_syntax_error.js:*) at Module._compile (internal/modules/cjs/loader.js:*) - at Object.Module._extensions..js (internal/modules/cjs/loader.js:*) + at Proxy.extensions..js (internal/modules/cjs/loader.js:*:*) at Module.load (internal/modules/cjs/loader.js:*) - at tryModuleLoad (internal/modules/cjs/loader.js:*:*) at Function.Module._load (internal/modules/cjs/loader.js:*) - at Function.Module.runMain (internal/modules/cjs/loader.js:*) + at Proxy.Module.runMain (internal/modules/cjs/loader.js:*) + at executeUserCode (internal/bootstrap/node.js:*:*) diff --git a/test/parallel/test-cwd-enoent-preload.js b/test/parallel/test-cwd-enoent-preload.js index 2077d9c1478335..7997675880eb6c 100644 --- a/test/parallel/test-cwd-enoent-preload.js +++ b/test/parallel/test-cwd-enoent-preload.js @@ -19,7 +19,6 @@ fs.mkdirSync(dirname); process.chdir(dirname); fs.rmdirSync(dirname); - const proc = spawn(process.execPath, ['-r', abspathFile, '-e', '0']); proc.stdout.pipe(process.stdout); proc.stderr.pipe(process.stderr); diff --git a/test/parallel/test-module-relative-lookup.js b/test/parallel/test-module-relative-lookup.js index c5350e11facda4..045fdc49b51863 100644 --- a/test/parallel/test-module-relative-lookup.js +++ b/test/parallel/test-module-relative-lookup.js @@ -10,12 +10,11 @@ function testFirstInPath(moduleName, isLocalModule) { assert.strictEqual : assert.notStrictEqual; - const lookupResults = _module._resolveLookupPaths(moduleName); + let paths = _module._resolveLookupPaths(moduleName); - let paths = lookupResults[1]; assertFunction(paths[0], '.'); - paths = _module._resolveLookupPaths(moduleName, null, true); + paths = _module._resolveLookupPaths(moduleName, null); assertFunction(paths && paths[0], '.'); } diff --git a/test/parallel/test-module-require-depth.js b/test/parallel/test-module-require-depth.js deleted file mode 100644 index 0a3fc2826c71f4..00000000000000 --- a/test/parallel/test-module-require-depth.js +++ /dev/null @@ -1,16 +0,0 @@ -// Flags: --expose_internals -'use strict'; -require('../common'); -const fixtures = require('../common/fixtures'); -const assert = require('assert'); -const { - requireDepth -} = require('internal/modules/cjs/helpers'); - -// Module one loads two too so the expected depth for two is, well, two. -assert.strictEqual(requireDepth, 0); -const one = require(fixtures.path('module-require-depth', 'one')); -const two = require(fixtures.path('module-require-depth', 'two')); -assert.deepStrictEqual(one, { requireDepth: 1 }); -assert.deepStrictEqual(two, { requireDepth: 2 }); -assert.strictEqual(requireDepth, 0); diff --git a/test/parallel/test-repl.js b/test/parallel/test-repl.js index 89370b0f7ad464..415d3325b7814f 100644 --- a/test/parallel/test-repl.js +++ b/test/parallel/test-repl.js @@ -536,6 +536,7 @@ const errorTests = [ /^ at .*/, /^ at .*/, /^ at .*/, + /^ at .*/, /^ at .*/ ] }, diff --git a/test/parallel/test-require-dot.js b/test/parallel/test-require-dot.js index 423441cfcde435..42a59487e90295 100644 --- a/test/parallel/test-require-dot.js +++ b/test/parallel/test-require-dot.js @@ -14,9 +14,10 @@ assert.strictEqual(a, b); process.env.NODE_PATH = fixtures.path('module-require', 'relative'); m._initPaths(); -const c = require('.'); -assert.strictEqual( - c.value, - 42, - `require(".") should honor NODE_PATH; expected 42, found ${c.value}` +assert.throws( + () => require('.'), + { + message: "Cannot find module '.'", + code: 'MODULE_NOT_FOUND' + } );