Node.js require(cjs)

And Cyclic dependency

// 617行
Module.prototype.require = function(id) {
    if (typeof id !== 'string') {
        throw new ERR_INVALID_ARG_TYPE('id', 'string', id);
    }
    if (id === '') {
        throw new ERR_INVALID_ARG_VALUE('id', id,
            'must be a non-empty string');
    }
    return Module._load(id, this, /* isMain */ false);
};
// 479行
// module load
Module._load = function(request, parent, isMain) {
    if (parent) {
        debug('Module._load REQUEST %s parent: %s', request, parent.id);
    }

    // 判断是否是 ECMAScript Modules 以及 入口文件
    // 调用esm的加载方法
    if (experimentalModules && isMain) {
        asyncESM.loaderPromise.then((loader) => {
                return loader.import(getURLFromFilePath(request).pathname);
            })
            .catch((e) => {
                decorateErrorStack(e);
                console.error(e);
                process.exit(1);
            });
        return;
    }

    // 获取要加载的文件名
    var filename = Module._resolveFilename(request, parent, isMain);

    // 判断缓存
    var cachedModule = Module._cache[filename];
    if (cachedModule) {
        // 当前模块挂载到父模块上 并且返回已经缓存好的模块exports
        updateChildren(parent, cachedModule, true);
        return cachedModule.exports;
    }

    // 判断是否是native模块
    if (NativeModule.nonInternalExists(filename)) {
        debug('load native module %s', request);
        return NativeModule.require(filename);
    }

    // Don't call updateChildren(), Module constructor already does.
    // 初始化一个Module
    var module = new Module(filename, parent);

    // 主模块挂在到process的mainModule上
    if (isMain) {
        process.mainModule = module;
        module.id = '.';
    }

    // 添加到缓存中
    Module._cache[filename] = module;

    // 加载模块
    tryModuleLoad(module, filename);

    // 返回模块的exports
    return module.exports;
};
// 536行
// 获取module的文件名
Module._resolveFilename = function(request, parent, isMain, options) {
    // native模块不做处理
    if (NativeModule.nonInternalExists(request)) {
        return request;
    }

    var paths;
    // 获取所有可能的路径
    paths = Module._resolveLookupPaths(request, parent, true);

    // look up the filename first, since that's the cache key.
    // 获取最终模块对应的文件名
    var filename = Module._findPath(request, paths, isMain);
    if (!filename) {
        // eslint-disable-next-line no-restricted-syntax
        var err = new Error(`Cannot find module '${request}'`);
        err.code = 'MODULE_NOT_FOUND';
        throw err;
    }
    return filename;
};
// _resolveLookupPaths 和 _findPath 不重点分析
function Module(id, parent) {
    this.id = id;
    this.exports = {};
    this.parent = parent;
    updateChildren(parent, this, false);
    this.filename = null;
    this.loaded = false;
    this.children = [];
}

// 将新的children挂载到parent上
function updateChildren(parent, child, scan) {
    var children = parent && parent.children;
    if (children && !(scan && children.includes(child)))
        children.push(child);
}
// 同一个模块会被 Module._cache 和 父模块引用
// 热更新如果只是简单的清除掉_cache中的模块,会导致每次新建一个module,
// 构造函数将新模块不断放入父模块的children中,导致内存泄漏
function tryModuleLoad(module, filename) {
  var threw = true;
  try {
    module.load(filename);
    threw = false;
  } finally {
    if (threw) {
      delete Module._cache[filename];
    }
  }
}
Module.prototype.load = function(filename) {
  debug('load %j for module %j', filename, this.id);

  assert(!this.loaded);
  this.filename = filename;
   // 获取这个module路径上所有可能的node_modules路径
  this.paths = Module._nodeModulePaths(path.dirname(filename));

  // 处理文件扩展名
  var extension = path.extname(filename) || '.js';
  if (!Module._extensions[extension]) extension = '.js';
  // 使用_extensions方法加载不同格式的文件
  Module._extensions[extension](this, filename);
  // 模块加载的标志位
  this.loaded = true;

  // esm模块的处理
  if (experimentalModules) {
  }
};
Module._extensions['.js'] = function(module, filename) {
  var content = fs.readFileSync(filename, 'utf8');
  module._compile(stripBOM(content), filename);
};

// Run the file contents in the correct scope or sandbox. Expose
// the correct helper variables (require, module, exports) to
// the file.
// Returns exception, if any.
Module.prototype._compile = function(content, filename) {

  content = stripShebang(content);

  // create wrapper function
  // 用  function (exports, require, module, __filename, __dirname) {} 来包装模块代码
  // 五个参数:exports 当前module的exports   require 在Module.prototype.require上改造的方法
  // module: 当前module  __filename 当前文件绝对路径  __dirname 当前文件所在文件夹的绝对路径
  var wrapper = Module.wrap(content);

  // 用V8暴露的方法来构造为一个新函数
  var compiledWrapper = vm.runInThisContext(wrapper, {
    filename: filename,
    lineOffset: 0,
    displayErrors: true
  });

  // 根据当前环境来执行构造出来的新函数
  var inspectorWrapper = null;
  if (process._breakFirstLine && process._eval == null) {
    if (!resolvedArgv) {
      // we enter the repl if we're not given a filename argument.
      if (process.argv[1]) {
        resolvedArgv = Module._resolveFilename(process.argv[1], null, false);
      } else {
        resolvedArgv = 'repl';
      }
    }

    // Set breakpoint on module start
    if (filename === resolvedArgv) {
      delete process._breakFirstLine;
      inspectorWrapper = process.binding('inspector').callAndPauseOnStart;
    }
  }
  var dirname = path.dirname(filename);
  var require = makeRequireFunction(this);
  var depth = requireDepth;
  if (depth === 0) stat.cache = new Map();
  var result;
  if (inspectorWrapper) {
    result = inspectorWrapper(compiledWrapper, this.exports, this.exports,
                              require, this, filename, dirname);
  } else {
    result = compiledWrapper.call(this.exports, this.exports, require, this,
                                  filename, dirname);
  }
  if (depth === 0) stat.cache = null;
  return result;
};
// 其他格式的处理
// Native extension for .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;
  }
};


//Native extension for .node
Module._extensions['.node'] = function(module, filename) {
  return process.dlopen(module, path.toNamespacedPath(filename));
};

if (experimentalModules) {
  Module._extensions['.mjs'] = function(module, filename) {
    throw new ERR_REQUIRE_ESM(filename);
  };
}

循环引用

a.js
console.log('a 开始');
exports.done = false;
const b = require('./b.js');
console.log('在 a 中,b.done = %j', b.done);
exports.done = true;
console.log('a 结束');

b.js
console.log('b 开始');
exports.done = false;
const a = require('./a.js');
console.log('在 b 中,a.done = %j', a.done);
exports.done = true;
console.log('b 结束');

main.js
console.log('main 开始');
const a = require('./a.js');
const b = require('./b.js');
console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);

Nodejs

By Joson Chen

Nodejs

  • 1,062