diff --git a/lib/fs.js b/lib/fs.js index a9e96bd736595e..17860ec05affcb 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1514,6 +1514,10 @@ function encodeRealpathResult(result, options, err) { } } +// This is removed from the fs exports in lib/module.js in order to make +// sure that this stays internal. +const realpathCacheKey = fs.realpathCacheKey = Symbol('realpathCacheKey'); + fs.realpathSync = function realpathSync(p, options) { if (!options) options = {}; @@ -1528,6 +1532,13 @@ fs.realpathSync = function realpathSync(p, options) { const seenLinks = {}; const knownHard = {}; + const cache = options[realpathCacheKey]; + const original = p; + + const maybeCachedResult = cache && cache.get(p); + if (maybeCachedResult) { + return maybeCachedResult; + } // current character position in p var pos; @@ -1568,39 +1579,47 @@ fs.realpathSync = function realpathSync(p, options) { pos = nextPartRe.lastIndex; // continue if not a symlink - if (knownHard[base]) { + if (knownHard[base] || (cache && cache.get(base) === base)) { continue; } var resolvedLink; - var stat = fs.lstatSync(base); - if (!stat.isSymbolicLink()) { - knownHard[base] = true; - continue; - } + const maybeCachedResolved = cache && cache.get(base); + if (maybeCachedResolved) { + resolvedLink = maybeCachedResolved; + } else { + var stat = fs.lstatSync(base); + if (!stat.isSymbolicLink()) { + knownHard[base] = true; + continue; + } - // read the link if it wasn't read before - // dev/ino always return 0 on windows, so skip the check. - var linkTarget = null; - if (!isWindows) { - var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); - if (seenLinks.hasOwnProperty(id)) { - linkTarget = seenLinks[id]; + // read the link if it wasn't read before + // dev/ino always return 0 on windows, so skip the check. + let linkTarget = null; + let id; + if (!isWindows) { + id = `${stat.dev.toString(32)}:${stat.ino.toString(32)}`; + if (seenLinks.hasOwnProperty(id)) { + linkTarget = seenLinks[id]; + } } - } - if (linkTarget === null) { - fs.statSync(base); - linkTarget = fs.readlinkSync(base); - } - resolvedLink = pathModule.resolve(previous, linkTarget); + if (linkTarget === null) { + fs.statSync(base); + linkTarget = fs.readlinkSync(base); + } + resolvedLink = pathModule.resolve(previous, linkTarget); - if (!isWindows) seenLinks[id] = linkTarget; + if (cache) cache.set(base, resolvedLink); + if (!isWindows) seenLinks[id] = linkTarget; + } // resolve the link, then start over p = pathModule.resolve(resolvedLink, p.slice(pos)); start(); } + if (cache) cache.set(original, p); return encodeRealpathResult(p, options); }; @@ -1696,8 +1715,9 @@ fs.realpath = function realpath(p, options, callback) { // stat & read the link if not read before // call gotTarget as soon as the link target is known // dev/ino always return 0 on windows, so skip the check. + let id; if (!isWindows) { - var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); + id = `${stat.dev.toString(32)}:${stat.ino.toString(32)}`; if (seenLinks.hasOwnProperty(id)) { return gotTarget(null, seenLinks[id], base); } diff --git a/lib/module.js b/lib/module.js index 96f1cfc42e47c6..c61a27a9e3cd1f 100644 --- a/lib/module.js +++ b/lib/module.js @@ -109,6 +109,14 @@ function tryPackage(requestPath, exts, isMain) { tryExtensions(path.resolve(filename, 'index'), exts, isMain); } +// In order to minimize unnecessary lstat() calls, +// this cache is a list of known-real paths. +// Set to an empty Map to reset. +const realpathCache = new Map(); + +const realpathCacheKey = fs.realpathCacheKey; +delete fs.realpathCacheKey; + // 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 @@ -118,7 +126,13 @@ function tryFile(requestPath, isMain) { if (preserveSymlinks && !isMain) { return rc === 0 && path.resolve(requestPath); } - return rc === 0 && fs.realpathSync(requestPath); + return rc === 0 && toRealPath(requestPath); +} + +function toRealPath(requestPath) { + return fs.realpathSync(requestPath, { + [realpathCacheKey]: realpathCache + }); } // given a path check a the file exists with any of the set extensions @@ -164,7 +178,7 @@ Module._findPath = function(request, paths, isMain) { if (preserveSymlinks && !isMain) { filename = path.resolve(basePath); } else { - filename = fs.realpathSync(basePath); + filename = toRealPath(basePath); } } else if (rc === 1) { // Directory. if (exts === undefined)