模拟node中require加载机制实现

node中require加载机制

node中的模块加载规范为commonJS规范,规范中通过require来引入模块。

require源码探究
  • 暴漏出一个text模块
      let str = 'hello world'
      module.exports = str
    
  • node中的源码无法通过打断点进行调试,这里借助vscode,在debug模式中,新建launch.js文件,并且将"skipFiles"内容 清空,表示不跳过node核心代码,然后在require处打断点,即可进入require内部源码中
      {
          // 使用 IntelliSense 了解相关属性。 
          // 悬停以查看现有属性的描述。
          // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
          "version": "0.2.0",
          "configurations": [
              {
                  "type": "pwa-node",
                  "request": "launch",
                  "name": "Launch Program",
                  "skipFiles": [
                      // "<node_internals>/**"
                  ],
                  "program": "${workspaceFolder}\\node架构\\require加载\\require加载.js"
              }
          ]
      }
    
  • 分析源码执行流程:
    • require一个模块后,默认调用Module._load方法,传入require的文件名
    • 通过Module._resolveFilename方法,解析文件名,此时会默认添加文件的后缀,返回文件路径
    • 创建当前模块实例const module = new Module() => {id:文件名,exports:{}}
    • 调用module.load()方法,传入解析的文件名
    • 根据文件的后缀名来调用相关的读取方法 Module._extensions[extension](this, filename);
      • 此时会将模块内容读出来,内容为字符串形式
    • 调用module._compile方法,通过该方法将text的内容包装成一个函数字符串
      function(){
          let str = 'hello world'
          module.exports = str
      }
      
    • 通过vm.compileFunction()执行该字符串变成一个真的函数并执行,执行会把数据给module.exports
    • 最终返回module.exports
模块源码简单实现
  • 调用require,会执行Module类中的一些列方法,首先要创建Module类,并分别添加相对应方法
      function Module(id){
          this.id = id
          this.export = {}
      }
    
  • 对文件的全局路径查找
    • require中传入的参数本质上是一个文件名,这个文件名可以不加后缀,依次在查找的时候 需要考虑
      • 若有后缀,则直接拼接路径,存在该路径下的文件,则返回
      • 若无后缀,则需在第一步基础上不断拼接可能的后缀,同时判断该路径是否存在文件,若存在,则返回
      • 两种情况都不符合,则报错
      Module._resolveFilename = function(id){
          const filePath = path.resolve(__dirname,id)
          //如果有后缀
      if(fs.existsSync(filePath)){
          return filePath
      }
      //如果无后缀
      const exts =  Object.keys(Module._extension);
      for(let i = 0;i<exts.length;++i){
          let file = filePath+exts[i]
          if(fs.existsSync(file)){
              return file
          }
      }
      throw Error('Cannot find module:' + id)
      }
    
      Module._extension = {
          '.js'(){},
          '.json'(){}
      }
    
  • 之后创建一个新的module实例
  • 调用实例中的load方法,截取扩展名
      Module.prototype.load = function(filename){
          let ext = path.extname(filename)
          Module._extension[ext](this)
      }
    
  • 调用Module._extension方法对引入的文件进行内容读取,使用函数包裹模块内容,调用函数,将内容给module.exports
      Module._extension = {
          '.js'(module){
              //读取模块内容
              const content = fs.readFileSync(module.id,'utf8')
              //获取包裹函数
              let wrapperFn = vm.compileFunction(content,['exports','require','module','__filename','__dirname'])
              let exports = this.exports
              let thisValue = exports
              let require = myRequire
              let filename = module.id
              let dirname = path.dirname(filename)
              Reflect.apply(wrapperFn,thisValue,[exports, require, module, filename, dirname])
          },
          '.json'(module){
              //若内容是json,则直接赋值
              const content = fs.readFileSync(module.id,'utf8')
              module.exports = JSON.parse(content)
          }
      }
    
  • 最终myRequire函数为:
      function myRequire(id){
          //获取完整路径
      let absPath = Module._resolveFilename(id)
      const module = new Module(absPath)
      module.load(absPath)
      return module.exports
      }
    
  • 测试
        let str = myRequire('./text')
        console.log(str)  //hello world
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

问也去

创作不易,感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值