mirror of
https://github.com/nodejs/node.git
synced 2025-04-30 15:41:06 +00:00

This adds support for a cache object to be passed to the fs.realpath and fs.realpathSync functions. The Module loader keeps an object around which caches the resulting realpaths that it looks up in the process of loading modules. This means that (at least as a result of loading modules) the same files and folders are never lstat()ed more than once. To reset the cache, set require("module")._realpathCache to an empty object. To disable the caching behavior, set it to null.
388 lines
9.9 KiB
JavaScript
388 lines
9.9 KiB
JavaScript
var NativeModule = require('native_module');
|
|
var Script = process.binding('evals').Script;
|
|
var runInThisContext = Script.runInThisContext;
|
|
var runInNewContext = Script.runInNewContext;
|
|
var assert = require('assert').ok;
|
|
|
|
function Module(id, parent) {
|
|
this.id = id;
|
|
this.exports = {};
|
|
this.parent = parent;
|
|
|
|
this.filename = null;
|
|
this.loaded = false;
|
|
this.exited = false;
|
|
this.children = [];
|
|
}
|
|
module.exports = Module;
|
|
|
|
// Set the environ variable NODE_MODULE_CONTEXTS=1 to make node load all
|
|
// modules in thier own context.
|
|
Module._contextLoad = (+process.env['NODE_MODULE_CONTEXTS'] > 0);
|
|
Module._cache = {};
|
|
Module._pathCache = {};
|
|
Module._extensions = {};
|
|
Module._paths = [];
|
|
|
|
Module.wrapper = NativeModule.wrapper;
|
|
Module.wrap = NativeModule.wrap;
|
|
|
|
var path = NativeModule.require('path');
|
|
|
|
Module._debug = function() {};
|
|
if (process.env.NODE_DEBUG && /module/.test(process.env.NODE_DEBUG)) {
|
|
Module._debug = function(x) {
|
|
console.error(x);
|
|
};
|
|
}
|
|
|
|
|
|
// We use this alias for the preprocessor that filters it out
|
|
var debug = Module._debug;
|
|
|
|
|
|
// given a module name, and a list of paths to test, returns the first
|
|
// matching file in the following precedence.
|
|
//
|
|
// require("a.<ext>")
|
|
// -> a.<ext>
|
|
//
|
|
// require("a")
|
|
// -> a
|
|
// -> a.<ext>
|
|
// -> a/index.<ext>
|
|
|
|
function statPath(path) {
|
|
var fs = NativeModule.require('fs');
|
|
try {
|
|
return fs.statSync(path);
|
|
} catch (ex) {}
|
|
return false;
|
|
}
|
|
|
|
// check if the directory is a package.json dir
|
|
var packageCache = {};
|
|
|
|
function readPackage(requestPath) {
|
|
if (packageCache.hasOwnProperty(requestPath)) {
|
|
return packageCache[requestPath];
|
|
}
|
|
|
|
var fs = NativeModule.require('fs');
|
|
try {
|
|
var jsonPath = path.resolve(requestPath, 'package.json');
|
|
var json = fs.readFileSync(jsonPath, 'utf8');
|
|
var pkg = packageCache[requestPath] = JSON.parse(json);
|
|
return pkg;
|
|
} catch (e) {}
|
|
|
|
return false;
|
|
}
|
|
|
|
function tryPackage(requestPath, exts) {
|
|
var pkg = readPackage(requestPath);
|
|
|
|
if (!pkg || !pkg.main) return false;
|
|
|
|
var filename = path.resolve(requestPath, pkg.main);
|
|
return tryFile(filename) || tryExtensions(filename, exts);
|
|
}
|
|
|
|
// In order to minimize unnecessary lstat() calls,
|
|
// this cache is a list of known-real paths.
|
|
// Set to an empty object to reset.
|
|
Module._realpathCache = {}
|
|
|
|
// check if the file exists and is not a directory
|
|
function tryFile(requestPath) {
|
|
var fs = NativeModule.require('fs');
|
|
var stats = statPath(requestPath);
|
|
if (stats && !stats.isDirectory()) {
|
|
return fs.realpathSync(requestPath, Module._realpathCache);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// given a path check a the file exists with any of the set extensions
|
|
function tryExtensions(p, exts) {
|
|
for (var i = 0, EL = exts.length; i < EL; i++) {
|
|
var filename = tryFile(p + exts[i]);
|
|
|
|
if (filename) {
|
|
return filename;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
Module._findPath = function(request, paths) {
|
|
var fs = NativeModule.require('fs');
|
|
var exts = Object.keys(Module._extensions);
|
|
|
|
if (request.charAt(0) === '/') {
|
|
paths = [''];
|
|
}
|
|
|
|
var trailingSlash = (request.slice(-1) === '/');
|
|
|
|
var cacheKey = JSON.stringify({request: request, paths: paths});
|
|
if (Module._pathCache[cacheKey]) {
|
|
return Module._pathCache[cacheKey];
|
|
}
|
|
|
|
// For each path
|
|
for (var i = 0, PL = paths.length; i < PL; i++) {
|
|
var basePath = path.resolve(paths[i], request);
|
|
var filename;
|
|
|
|
if (!trailingSlash) {
|
|
// try to join the request to the path
|
|
filename = tryFile(basePath);
|
|
|
|
if (!filename && !trailingSlash) {
|
|
// try it with each of the extensions
|
|
filename = tryExtensions(basePath, exts);
|
|
}
|
|
}
|
|
|
|
if (!filename) {
|
|
filename = tryPackage(basePath, exts);
|
|
}
|
|
|
|
if (!filename) {
|
|
// try it with each of the extensions at "index"
|
|
filename = tryExtensions(path.resolve(basePath, 'index'), exts);
|
|
}
|
|
|
|
if (filename) {
|
|
Module._pathCache[cacheKey] = filename;
|
|
return filename;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
|
|
Module._resolveLookupPaths = function(request, parent) {
|
|
if (NativeModule.exists(request)) {
|
|
return [request, []];
|
|
}
|
|
|
|
var start = request.substring(0, 2);
|
|
if (start !== './' && start !== '..') {
|
|
return [request, Module._paths];
|
|
}
|
|
|
|
// 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
|
|
return [request, ['.'].concat(Module._paths)];
|
|
}
|
|
|
|
// Is the parent an index module?
|
|
// We can assume the parent has a valid extension,
|
|
// as it already has been accepted as a module.
|
|
var isIndex = /^index\.\w+?$/.test(path.basename(parent.filename));
|
|
var parentIdPath = isIndex ? parent.id : 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) {
|
|
id = './' + id;
|
|
}
|
|
|
|
debug('RELATIVE: requested:' + request +
|
|
' set ID to: ' + id + ' from ' + parent.id);
|
|
|
|
return [id, [path.dirname(parent.filename)]];
|
|
};
|
|
|
|
|
|
Module._load = function(request, parent, isMain) {
|
|
if (parent) {
|
|
debug('Module._load REQUEST ' + (request) + ' parent: ' + parent.id);
|
|
}
|
|
|
|
var resolved = Module._resolveFilename(request, parent);
|
|
var id = resolved[0];
|
|
var filename = resolved[1];
|
|
|
|
var cachedModule = Module._cache[filename];
|
|
if (cachedModule) {
|
|
return cachedModule.exports;
|
|
}
|
|
|
|
if (NativeModule.exists(id)) {
|
|
// REPL is a special case, because it needs the real require.
|
|
if (id == 'repl') {
|
|
var replModule = new Module('repl');
|
|
replModule._compile(NativeModule.getSource('repl'), 'repl.js');
|
|
NativeModule._cache.repl = replModule;
|
|
return replModule.exports;
|
|
}
|
|
|
|
debug('load native module ' + request);
|
|
return NativeModule.require(id);
|
|
}
|
|
|
|
var module = new Module(id, parent);
|
|
|
|
if (isMain) {
|
|
process.mainModule = module;
|
|
module.id = '.';
|
|
}
|
|
|
|
Module._cache[filename] = module;
|
|
module.load(filename);
|
|
return module.exports;
|
|
};
|
|
|
|
Module._resolveFilename = function(request, parent) {
|
|
if (NativeModule.exists(request)) {
|
|
return [request, request];
|
|
}
|
|
|
|
var resolvedModule = Module._resolveLookupPaths(request, parent);
|
|
var id = resolvedModule[0];
|
|
var paths = resolvedModule[1];
|
|
|
|
// look up the filename first, since that's the cache key.
|
|
debug('looking for ' + JSON.stringify(id) +
|
|
' in ' + JSON.stringify(paths));
|
|
|
|
var filename = Module._findPath(request, paths);
|
|
if (!filename) {
|
|
throw new Error("Cannot find module '" + request + "'");
|
|
}
|
|
id = filename;
|
|
return [id, filename];
|
|
};
|
|
|
|
|
|
Module.prototype.load = function(filename) {
|
|
debug('load ' + JSON.stringify(filename) +
|
|
' for module ' + JSON.stringify(this.id));
|
|
|
|
assert(!this.loaded);
|
|
this.filename = filename;
|
|
|
|
var extension = path.extname(filename) || '.js';
|
|
if (!Module._extensions[extension]) extension = '.js';
|
|
Module._extensions[extension](this, filename);
|
|
this.loaded = true;
|
|
};
|
|
|
|
|
|
// Returns exception if any
|
|
Module.prototype._compile = function(content, filename) {
|
|
var self = this;
|
|
// remove shebang
|
|
content = content.replace(/^\#\!.*/, '');
|
|
|
|
function require(path) {
|
|
return Module._load(path, self);
|
|
}
|
|
|
|
require.resolve = function(request) {
|
|
return Module._resolveFilename(request, self)[1];
|
|
}
|
|
require.paths = Module._paths;
|
|
require.main = process.mainModule;
|
|
// Enable support to add extra extension types
|
|
require.extensions = Module._extensions;
|
|
require.registerExtension = function() {
|
|
throw new Error('require.registerExtension() removed. Use ' +
|
|
'require.extensions instead.');
|
|
}
|
|
require.cache = Module._cache;
|
|
|
|
var dirname = path.dirname(filename);
|
|
|
|
if (Module._contextLoad) {
|
|
if (self.id !== '.') {
|
|
debug('load submodule');
|
|
// not root module
|
|
var sandbox = {};
|
|
for (var k in global) {
|
|
sandbox[k] = global[k];
|
|
}
|
|
sandbox.require = require;
|
|
sandbox.exports = self.exports;
|
|
sandbox.__filename = filename;
|
|
sandbox.__dirname = dirname;
|
|
sandbox.module = self;
|
|
sandbox.global = sandbox;
|
|
sandbox.root = root;
|
|
|
|
return runInNewContext(content, sandbox, filename, true);
|
|
}
|
|
|
|
debug('load root module');
|
|
// root module
|
|
global.require = require;
|
|
global.exports = self.exports;
|
|
global.__filename = filename;
|
|
global.__dirname = dirname;
|
|
global.module = self;
|
|
|
|
return runInThisContext(content, filename, true);
|
|
}
|
|
|
|
// create wrapper function
|
|
var wrapper = Module.wrap(content);
|
|
|
|
var compiledWrapper = runInThisContext(wrapper, filename, true);
|
|
if (filename === process.argv[1] && global.v8debug) {
|
|
global.v8debug.Debug.setBreakPoint(compiledWrapper, 0, 0);
|
|
}
|
|
var args = [self.exports, require, self, filename, dirname];
|
|
return compiledWrapper.apply(self.exports, args);
|
|
};
|
|
|
|
// Native extension for .js
|
|
Module._extensions['.js'] = function(module, filename) {
|
|
var content = NativeModule.require('fs').readFileSync(filename, 'utf8');
|
|
module._compile(content, filename);
|
|
};
|
|
|
|
|
|
// Native extension for .node
|
|
Module._extensions['.node'] = function(module, filename) {
|
|
process.dlopen(filename, module.exports);
|
|
};
|
|
|
|
|
|
// bootstrap main module.
|
|
Module.runMain = function() {
|
|
// Load the main module--the command line argument.
|
|
Module._load(process.argv[1], null, true);
|
|
};
|
|
|
|
Module._initPaths = function() {
|
|
var paths = [path.resolve(process.execPath, '..', '..', 'lib', 'node')];
|
|
|
|
if (process.env['HOME']) {
|
|
paths.unshift(path.resolve(process.env['HOME'], '.node_libraries'));
|
|
paths.unshift(path.resolve(process.env['HOME'], '.node_modules'));
|
|
}
|
|
|
|
if (process.env['NODE_PATH']) {
|
|
paths = process.env['NODE_PATH'].split(':').concat(paths);
|
|
}
|
|
|
|
Module._paths = paths;
|
|
};
|
|
|
|
// bootstrap repl
|
|
Module.requireRepl = function() {
|
|
return Module._load('repl', '.');
|
|
};
|
|
|
|
Module._initPaths();
|
|
|
|
// backwards compatibility
|
|
Module.Module = Module;
|