Nodejs 的 CommonJS 规范实现原理( 三 )


使用 tryModuleLoad 方法去加载模块 , tryModuleLoad 中使用 path.extname 获取到文件的扩展名 , 然后根据扩展名来执行对应的模块加载机制 。
最终将加载到的模块挂载 module.exports 中 。tryModuleLoad 执行完毕之后 module.exports 已经存在了,直接返回就可以了 。
接下来,我们给模块添加缓存 。就是文件加载的时候将文件放入缓存中 , 再去加载模块时先看缓存中是否存在 , 如果存在直接使用 , 如果不存在再去重新加载,加载之后再放入缓存 。
// 定义导入类,参数为模块路径
function Require(modulePath) {
// 获取当前要加载的绝对路径
let absPathname = path.resolve(__dirname, modulePath);
// 从缓存中读取,如果存在,直接返回结果
if (Module._cache[absPathname]) {
return Module._cache[absPathname].exports;
}
// 创建模块,新建Module实例
const module = new Module(absPathname);
// 添加缓存
Module._cache[absPathname] = module;
// 加载当前模块
tryModuleLoad(module);
// 返回exports对象
return module.exports;
}
增加功能:省略模块后缀名 。
自动给模块添加后缀名,实现省略后缀名加载模块,其实也就是如果文件没有后缀名的时候遍历一下所有的后缀名看一下文件是否存在 。
// 定义导入类 , 参数为模块路径
function Require(modulePath) {
// 获取当前要加载的绝对路径
let absPathname = path.resolve(__dirname, modulePath);
// 获取所有后缀名
const extNames = Object.keys(Module._extensions);
let index = 0;
// 存储原始文件路径
const oldPath = absPathname;
function findExt(absPathname) {
if (index === extNames.length) {
return throw new Error('文件不存在');
}
try {
fs.accessSync(absPathname);
return absPathname;
} catch(e) {
const ext = extNames[index++];
findExt(oldPath + ext);
}
}
// 递归追加后缀名,判断文件是否存在
absPathname = findExt(absPathname);
// 从缓存中读取 , 如果存在,直接返回结果
if (Module._cache[absPathname]) {
return Module._cache[absPathname].exports;
}
// 创建模块,新建Module实例
const module = new Module(absPathname);
// 添加缓存
Module._cache[absPathname] = module;
// 加载当前模块
tryModuleLoad(module);
// 返回exports对象
return module.exports;
}
源代码调试我们可以通过 VSCode 调试 Node.js
步骤
创建文件 a.js
module.exports = 'abc'
1. 文件 test.js
let r = require('./a')
console.log(r)
1. 配置 debug,本质是配置.vscode/launch.json 文件,而这个文件的本质是能提供多个启动命令入口选择 。
一些常见参数如下:

  • program 控制启动文件的路径(即入口文件)
  • name 下拉菜单中显示的名称(该命令对应的入口名称)
  • request 分为 launch(启动)和 attach(附加)(进程已经启动)
  • skipFiles 指定单步调试跳过的代码
  • runtimeExecutable 设置运行时可执行文件,默认是 node,可以设置成 nodemon , ts-node,npm 等?
修改 launch.json , skipFiles 指定单步调试跳过的代码
Nodejs 的 CommonJS 规范实现原理

文章插图
  1. 将 test.js 文件中的 require 方法所在行前面打断点
  2. 执行调试,进入源码相关入口方法
梳理代码步骤
1. 首先进入到进入到 require 方法:Module.prototype.require
Nodejs 的 CommonJS 规范实现原理

文章插图

Nodejs 的 CommonJS 规范实现原理

文章插图
2. 调试到 Module._load 方法中,该方法返回 module.exports,Module._resolveFilename 方法返回处理之后的文件地址,将文件改为绝对地址,同时如果文件没有后缀就加上文件后缀 。
Nodejs 的 CommonJS 规范实现原理

文章插图
3. 这里定义了 Module 类 。id 为文件名 。此类中定义了 exports 属性
Nodejs 的 CommonJS 规范实现原理

文章插图
4. 接着调试到 module.load 方法 , 该方法中使用了策略模式,Module._extensions [extension](this, filename) 根据传入的文件后缀名不同调用不同的方法
Nodejs 的 CommonJS 规范实现原理

文章插图
5. 进入到该方法中 , 看到了核心代码,读取传入的文件地址参数,拿到该文件中的字符串内容 , 执行 module._compile


推荐阅读