手写Node模块系统-官方流程分析

在本章节的内容开始之前,先修改一下 Node.js 的版本因为高版本做了许多的优化,不利于讲解与学习,切换的版本如下:

image-20210817223410708

nvm install 6.17.1
nvm use 6.17.1

首先我自定义了一个模块,之前说了一个文件就是一个模块,新建一个 a.js 内容如下:

exports.name = "BNTang";

然后紧接着新建了一个 b.js 内容如下:

let aModule = require("./a.js");

console.log(aModule);
console.log(aModule.name);

运行结果如下:

那么这个时候就可以开始我们的源码之旅了,打个断点如下,打了之后 DeBug 的方式进行启动工程,接下来就可以开始了:

按照我图中所选择的方式进行操作即可,如下红色的箭头代表着就是进入到断点这一行的当中当中:

内部实现了一个 require 方法:

function makeRequireFunction() {
  const Module = this.constructor;
  const self = this;

  function require(path) {
    try {
      exports.requireDepth += 1;
      return self.require(path);
    } finally {
      exports.requireDepth -= 1;
    }
  }

  function resolve(request) {
    return Module._resolveFilename(request, self);
  }

  require.resolve = resolve;

  require.main = process.mainModule;

  // Enable support to add extra extension types.
  require.extensions = Module._extensions;

  require.cache = Module._cache;

  return require;
}

点击蓝色箭头往下走一步就会走到下一行代码如下图:

image-20210819213232433

为了我们待会好实现我先把官方的实现代码贴出来如下,当然我是把多余的无用代码给删除了只留出了关键的部分,如下这只是第一步:

function require(path) {
	return self.require(path);
}

继续点击红色小箭头进入到方法的内部,如下只是一个断言我们不用看:

然后继续往下走按蓝色箭头,然后它找到了一个 Module 对象然后通过 Module 对象的静态 __load 方法进行加载模块文件:

Module.prototype.require = function(path) {
    return Module._load(path, this, /* isMain */ false);
};

继续点击红色小箭头进入到 __load 看一下它做了什么事情,发现在然后通过 Module 对象的静态 _resolveFilename 方法, 得到绝对路径并添加后缀名:

var filename = Module._resolveFilename(request, parent, isMain);

继续点击红色小箭头,根据路径判断是否有缓存,如果没有就创建一个新的 Module 模块对象并缓存起来:

var cachedModule = Module._cache[filename];
if (cachedModule) {
	return cachedModule.exports;
}

var module = new Module(filename, parent);
Module._cache[filename] = module;

function Module(id, parent) {
	this.id = id;
	this.exports = {};
}

继续点击红色小箭头,利用 tryModuleLoad 方法加载模块:

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;
  this.paths = Module._nodeModulePaths(path.dirname(filename));

  var extension = path.extname(filename) || '.js';
  if (!Module._extensions[extension]) extension = '.js';
  Module._extensions[extension](this, filename);
  this.loaded = true;
};

根据不同后缀查找不同方法并执行对应的方法, 进行加载模块:

如果是 JSON 就转换成对象:

module.exports = JSON.parse(internalModule.stripBOM(content));

如果是 JS 就包裹一个函数:

var wrapper = Module.wrap(content);

NativeModule.wrap = function(script) {
    return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
};

NativeModule.wrapper = [
    '(function (exports, require, module, __filename, __dirname) { ',
    '\n});'
];

执行包裹函数之后的代码, 拿到执行结果 (String → Function)

var compiledWrapper = vm.runInThisContext(wrapper);

利用 call 执行 fn 函数, 修改 module.exports 的值:

var args = [this.exports, require, module, filename, dirname];
var result = compiledWrapper.call(this.exports, args);

然后在返回 module.exports:

return module.exports;

最终 tryModuleLoad 方法的内容解释如下:

tryModuleLoad(module, filename){
    // 1.取出模块后缀
	var extension = path.extname(filename);

    // 2.根据不同后缀查找不同方法并执行对应的方法, 加载模块
    Module._extensions[extension](this, filename);

    // 3.如果是JSON就转换成对象
    module.exports = JSON.parse(internalModule.stripBOM(content));

    // 4.如果是JS就包裹一个函数
    var wrapper = Module.wrap(content);
    NativeModule.wrap = function(script) {
        return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
    };
    NativeModule.wrapper = [
        '(function (exports, require, module, __filename, __dirname) { ',
        '\n});'
    ];
    
    // 5.执行包裹函数之后的代码, 拿到执行结果(String -- Function)
    var compiledWrapper = vm.runInThisContext(wrapper);

    // 6.利用call执行fn函数, 修改module.exports的值
    var args = [this.exports, require, module, filename, dirname];
    var result = compiledWrapper.call(this.exports, args);

    // 7.返回module.exports
    return module.exports;
}
posted @ 2021-01-24 11:03  BNTang  阅读(91)  评论(0编辑  收藏  举报