手写Node模块系统-官方流程分析
在本章节的内容开始之前,先修改一下 Node.js 的版本因为高版本做了许多的优化,不利于讲解与学习,切换的版本如下:
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;
}
点击蓝色箭头往下走一步就会走到下一行代码如下图:
为了我们待会好实现我先把官方的实现代码贴出来如下,当然我是把多余的无用代码给删除了只留出了关键的部分,如下这只是第一步:
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;
}