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方法

  1. // lib/module.js
  2. // ...
  3. Module.prototype.require = function(path) {
  4. assert(path, 'missing path');
  5. assert(typeof path === 'string', 'path must be a string');
  6. return Module._load(path, this, /* isMain */ false);
  7. };
  8. // ...

路径分析

_load方法

  1. 根据文件名,调用 Module._resolveFilename 解析文件的路径
  2. 查看缓存 Module._cache 中是否有该模块,如果有,直接返回
  3. 通过 NativeModule.nonInternalExists 判断该模块是否为核心模块,如果核心模块,调用核心模块的加载方法 NativeModule.require
  4. 如果不是核心模块,新创建一个 Module 对象,调用 tryModuleLoad 函数加载模块

    1. // lib/module.js
    2. // ...
    3. Module._load = function(request, parent, isMain) {
    4. if (parent) {
    5. debug('Module._load REQUEST %s parent: %s', request, parent.id);
    6. }
    7. // 解析文件的路径
    8. var filename = Module._resolveFilename(request, parent, isMain);
    9. // 查看缓存
    10. var cachedModule = Module._cache[filename];
    11. if (cachedModule) {
    12. return cachedModule.exports;
    13. }
    14. // 判断该模块是否为核心模块
    15. if (NativeModule.nonInternalExists(filename)) {
    16. debug('load native module %s', request);
    17. return NativeModule.require(filename);
    18. }
    19. // 新创建一个 Module 对象
    20. var module = new Module(filename, parent);
    21. if (isMain) {
    22. process.mainModule = module;
    23. module.id = '.';
    24. }
    25. Module._cache[filename] = module;
    26. // 加载模块
    27. tryModuleLoad(module, filename);
    28. return module.exports;
    29. };
    30. // ...

    确定模块的绝对路径: _resolveFilename方法

    1. // lib/module.js
    2. // ...
    3. Module._resolveFilename = function(request, parent, isMain) {
    4. // ...
    5. // 第一步:如果是内置模块,不含路径返回
    6. if (NativeModule.exists(request)) {
    7. return request;
    8. }
    9. // 第二步:确定所有可能的路径
    10. var resolvedModule = Module._resolveLookupPaths(request, parent);
    11. var id = resolvedModule[0];
    12. var paths = resolvedModule[1];
    13. // 第三步:确定哪一个路径为真
    14. var filename = Module._findPath(request, paths, isMain);
    15. if (!filename) {
    16. var err = new Error("Cannot find module '" + request + "'");
    17. err.code = 'MODULE_NOT_FOUND';
    18. throw err;
    19. }
    20. return filename;
    21. };
    22. // ...

    列出可能的路径: _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) {

    1. // 第三步:是否存在该模块文件
    2. filename = tryFile(basePath);
    3. if (!filename && !trailingSlash) {
    4. // 第四步:该模块文件加上后缀名,是否存在
    5. filename = tryExtensions(basePath, exts);
    6. }

    }

    // 第五步:目录中是否存在 package.json if (!filename) {

    1. filename = tryPackage(basePath, exts);

    }

    if (!filename) {

    1. // 第六步:是否存在目录名 + index + 后缀名
    2. filename = tryExtensions(path.resolve(basePath, 'index'), exts);

    }

    // 第七步:将找到的文件路径存入返回缓存,然后返回 if (filename) {

    1. Module._pathCache[cacheKey] = filename;
    2. return filename;

    } }

    // 第八步:没有找到文件,返回false return false; };

    1. <a name="R6rvb"></a>
    2. ### 定位路径
    3. <a name="6jNhr"></a>
    4. #### 确定模块的后缀名:load

    Module.prototype.load = function(filename) { var extension = path.extname(filename) || ‘.js’; if (!Module._extensions[extension]) extension = ‘.js’; Module._extensionsextension; this.loaded = true; };

    1. .js 加载

    Module._extensions[‘.js’] = function(module, filename) { var content = fs.readFileSync(filename, ‘utf8’); module._compile(stripBOM(content), filename); };

    1. .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; } };

    1. <a name="tfxag"></a>
    2. ### 文件解析
    3. <a name="Bt3EH"></a>
    4. #### _compile 方法

    Module.prototype._compile = function(content, filename) { var self = this; var args = [self.exports, require, self, filename, dirname]; return compiledWrapper.apply(self.exports, args); };

    1. 等价

    (function (exports, require, module, filename, dirname) { // 模块源码 }); ```