前言:可以用VS code的断点调试一步一步走进去看,其实真整个过程并不复杂。看完之后先理一下思路,然后自己写一遍。写的时候肯定会遇到具体的问题,再到源码中看它是怎么解决的。就可以啦~

思路:每次在文件内调用require('path')的时候,会走到静态方法Module_load上,然后生成新的module实例。接着加载文件,执行文件。

核心是Module类,,父类上有一个核心方法

Module._load // 用于new新的Module实例,缓存控制,最后调用私有方法tryModuleLoad

私有核心方法

tryModuleLoad // 会去调用实例.load加载文件

原型链核心方法

Module.prototype.load // 加载文件,调用_compile
Module.prototype.require // 调用父类Module._load
Module.prototype._compile // 执行加载到的文件

自己实现的整体思路。包括缓存和自动添加拓展名。

const fs = require('fs')
const vm = require('vm')
const path = require('path')

function getFilePath (filePath) {
  // 前面还需要有一个是否绝对路径的判断
  let absolutePath = path.resolve(__dirname, filePath)
  let extname = path.extname(absolutePath)
  let realPath = ''
  if (extname) {
    try {
      fs.accessSync(absolutePath)
      realPath = absolutePath
    } catch (e) {}
  } else {
    for (let i = 0, len = Module._extnames.length; i < len; ++i) {
      try {
        let joinedPath = absolutePath + Module._extnames[i]
        console.log(joinedPath)
        fs.accessSync(joinedPath)
        realPath = joinedPath
        break
      } catch (e) {}
    }
  }
  if (!realPath) {
    var err = new Error(`Cannot find module '${filePath}'`)
    err.code = 'MODULE_NOT_FOUND'
    throw err
  }
  return realPath
}

function makeRequireFunction (mod) {
  function require (path) {
    return mod.require(path)
  }

  return require
}

function join (script) {
  return Module.makeUpFunctions[0] + script + Module.makeUpFunctions[1]
}

class Module {
  constructor (id) {
    this.id = id
    this.exports = {}
  }
  require (id) {
    return Module._load(id)
  }
  load (filePath) {
    const script = fs.readFileSync(filePath, 'utf-8')
    const code = vm.runInThisContext(join(script))
    this._compile(code)
  }
  _compile (code) {
    const require = makeRequireFunction(this)
    const result = code.call(this.exports, this.exports, require, this, __dirname, __filename)
    // 这个返回值在源码中没有用到。
  }
}

Module.makeUpFunctions = [
  '(function (exports, require, module, __dirname, __filename) {\n',
  '\n})'
]
Module._cache = []
Module._extnames = ['.js', '.json', '.node']
Module._load = function (id) {
  const module = new Module(id)
  const filePath = getFilePath(id)
  if (Module._cache[filePath]) {
    return Module._cache[filePath].exports
  }
  Module._cache[filePath] = module
  module.load(filePath)
  return module.exports
}

function require2 (id) {
  return Module._load(id)
}

const test = require2('./test')
const test2 = require2('./test')

console.log(test)
console.log(test2)

注意:require第二遍的时候,不会再去执行一遍该模块里面的代码了,直接把该模块导出。