CommonJS模块分类及加载流程,及模块加载模拟实现

模块分类

  1. 内置模块:Node源码编译时写入到二进制文件中
  2. 文件模块:代码运行时,动态加载
加载流程
  • 路径分析:依据标识符确定模块位置(路径标识符、非路径标识符)
  • 文件定位:确定目标模块中具体的文件及文件类型(存在'm1'模块,导入时使用require('m1')语法,使用m1.js->m1.json->m1.node的顺序,如果都没找到,会被当做一个目录,查找package.json文件,使用JOSN.parse()解析。接下来会找main.js->main.json->main.node。将index作为目标模块中的具体文件名称)
  • 编译执行:采用对应的方式完成文件的编译执行(将某个具体类型的文件按照相应的方式进行编译和执行,创建新对象,按路径载入,完成编译执行,返回可用exports对象)
缓存优先原则
  • 提高模块加载速度
  • 当前模块不存在,则经历一次完整加载流程
  • 模块加载完成后,使用路径作为索引进行缓存
模块加载模拟实现
核心逻辑
  • 路径分析
  • 缓存优化
  • 文件定位
  • 编译执行
【CommonJS模块分类及加载流程,及模块加载模拟实现】根据上面的加载流程特征模拟实现
const fs = require("fs"); const path = require("path"); const vm = require("vm"); function Module(id) { this.id = id; this.exports = {}; }// 静态方法 Module._resolveFilename = function (filename) { // 利用path将filename转为绝对路径 let absPath = path.resolve(__dirname, filename); console.log(absPath); // 判断当前路径对应的内容是否存在 if (fs.existsSync(absPath)) { // 条件成立说明absPath对应的内容是存在的 return absPath; } else { //根据文件定位的顺序依次去找 let suffix = Object.keys(Module.__extensions); for (var i = 0; i < suffix.length; i++) { // 拼接 let newPath = absPath + suffix[i]; if (fs.existsSync(newPath)) { return newPath; } } } throw new Error(`${filename} is not exists`); }; Module.__extensions = { ".js"(module) { // 读取 let content = fs.readFileSync(module.id, "utf-8"); // 包装 content = Module.wrapper[0] + content + Module.wrapper[1]; // 使用vm执行 let compileFn = vm.runInThisContext(content); // 转换成一个可执行匿名函数 // 准备参数的值 let exports = module.exports; let dirname = path.dirname(module.id); let filename = module.id; // 调用 compileFn.call(exports, exports, myRequire, module, filename, dirname); }, ".json"(module) { //读取然后格式化 let content = JSON.parse(fs.readFileSync(module.id, "utf-8")); module.exports = content; }, }; Module.wrapper = [ "(function(exports,require,module,__filename,__dirname){", "})", ]; Module._cache = {}; Module.prototype.load = function () { let extname = path.extname(this.id); console.log("extname", extname); Module.__extensions[extname](this); }; function myRequire(filename) { // 1,获取绝对路径 let modulePath = Module._resolveFilename(filename); //2,实现缓存优先 let cacheModule = Module._cache[modulePath]; if (cacheModule) { return cacheModule.exports; } // 3创建空对象加载目标模块 let module = new Module(modulePath); // 4,缓存已经加载的模块 Module._cache[modulePath] = module; // 5,执行加载(编译执行) module.load(); //6 ,返回数据 return module.exports; }let obj = myRequire("./m"); console.log(obj);

    推荐阅读