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) { // 模块源码 }); ```
- 调用
