http://www.ruanyifeng.com/blog/2015/05/require.html
源码: https://github.com/nodejs/node/blob/v6.x/lib/module.js#L108
注入exports、require、module三个全局变量,然后执行模块的源码,然后将模块的 exports 变量的值输出。
require方法
require 并不是全局性命令,而是每个模块提供的一个内部方法。 调用_load方法
// lib/module.js
// ...
Module.prototype.require = function(path) {
assert(path, 'missing path');
assert(typeof path === 'string', 'path must be a string');
return Module._load(path, this, /* isMain */ false);
};
// ...
路径分析
_load方法
- 根据文件名,调用
Module._resolveFilename
解析文件的路径 - 查看缓存
Module._cache
中是否有该模块,如果有,直接返回 - 通过
NativeModule.nonInternalExists
判断该模块是否为核心模块,如果核心模块,调用核心模块的加载方法NativeModule.require
如果不是核心模块,新创建一个 Module 对象,调用
tryModuleLoad
函数加载模块// lib/module.js
// ...
Module._load = function(request, parent, isMain) {
if (parent) {
debug('Module._load REQUEST %s parent: %s', request, parent.id);
}
// 解析文件的路径
var filename = Module._resolveFilename(request, parent, isMain);
// 查看缓存
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
}
// 判断该模块是否为核心模块
if (NativeModule.nonInternalExists(filename)) {
debug('load native module %s', request);
return NativeModule.require(filename);
}
// 新创建一个 Module 对象
var module = new Module(filename, parent);
if (isMain) {
process.mainModule = module;
module.id = '.';
}
Module._cache[filename] = module;
// 加载模块
tryModuleLoad(module, filename);
return module.exports;
};
// ...
确定模块的绝对路径: _resolveFilename方法
// lib/module.js
// ...
Module._resolveFilename = function(request, parent, isMain) {
// ...
// 第一步:如果是内置模块,不含路径返回
if (NativeModule.exists(request)) {
return request;
}
// 第二步:确定所有可能的路径
var resolvedModule = Module._resolveLookupPaths(request, parent);
var id = resolvedModule[0];
var paths = resolvedModule[1];
// 第三步:确定哪一个路径为真
var filename = Module._findPath(request, paths, isMain);
if (!filename) {
var err = new Error("Cannot find module '" + request + "'");
err.code = 'MODULE_NOT_FOUND';
throw err;
}
return filename;
};
// ...
列出可能的路径: _resolveLookupPaths方法
确认哪一个路径为真: _findPath方法
文件路径解析的逻辑流程是这样的:
- 先生成 cacheKey,判断相应 cache 是否存在,若存在直接返回
- 如果 path 的最后一个字符不是
/
:- 如果路径是一个文件并且存在,那么直接返回文件的路径
- 如果路径是一个目录,调用
tryPackage
函数去解析目录下的package.json
,然后取出其中的main
字段所写入的文件路径- 判断路径如果存在,直接返回
- 尝试在路径后面加上 .js, .json, .node 三种后缀名,判断是否存在,存在则返回
- 尝试在路径后面依次加上 index.js, index.json, index.node,判断是否存在,存在则返回
- 如果还不成功,直接对当前路径加上 .js, .json, .node 后缀名进行尝试
如果 path 的最后一个字符是
/
:- 调用
tryPackage
,解析流程和上面的情况类似 - 如果不成功,尝试在路径后面依次加上 index.js, index.json, index.node,判断是否存在,存在则返回 ``` Module._findPath = function(request, paths) {
// 列出所有可能的后缀名:.js,.json, .node 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 (var i = 0, PL = paths.length; i < PL; i++) { var basePath = path.resolve(paths[i], request); var filename;
if (!trailingSlash) {
// 第三步:是否存在该模块文件
filename = tryFile(basePath);
if (!filename && !trailingSlash) {
// 第四步:该模块文件加上后缀名,是否存在
filename = tryExtensions(basePath, exts);
}
}
// 第五步:目录中是否存在 package.json if (!filename) {
filename = tryPackage(basePath, exts);
}
if (!filename) {
// 第六步:是否存在目录名 + index + 后缀名
filename = tryExtensions(path.resolve(basePath, 'index'), exts);
}
// 第七步:将找到的文件路径存入返回缓存,然后返回 if (filename) {
Module._pathCache[cacheKey] = filename;
return filename;
} }
// 第八步:没有找到文件,返回false return false; };
<a name="R6rvb"></a>
### 定位路径
<a name="6jNhr"></a>
#### 确定模块的后缀名:load
Module.prototype.load = function(filename) { var extension = path.extname(filename) || ‘.js’; if (!Module._extensions[extension]) extension = ‘.js’; Module._extensionsextension; this.loaded = true; };
.js 加载
Module._extensions[‘.js’] = function(module, filename) { var content = fs.readFileSync(filename, ‘utf8’); module._compile(stripBOM(content), filename); };
.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; } };
<a name="tfxag"></a>
### 文件解析
<a name="Bt3EH"></a>
#### _compile 方法
Module.prototype._compile = function(content, filename) { var self = this; var args = [self.exports, require, self, filename, dirname]; return compiledWrapper.apply(self.exports, args); };
等价
(function (exports, require, module, filename, dirname) { // 模块源码 }); ```
- 调用