commonJS原理实现

140 阅读1分钟
const vm = require('vm')
const fs = require('fs')
const path = require('path')
function Module(id){
    this.id = id
    this.exports = {}
}
Module.prototype.load = function() {
    let ext = path.extname(this.id)
    Module._extensions[ext](this)
}
Module.wrap = function(script) {
    let wrapper = ['(function(exports,require,module,dirname,filename){', '})'];
    return wrapper[0] + script + wrapper[1];
}
Module._extensions = {
    '.js': function(module) {
        let script = fs.readFileSync(module.id, 'utf8')
        let wrapperStr = Module.wrap(script)
        let fn = vm.runInThisContext(wrapperStr)
        fn.call(module.exports, module.exports, req, module, path.dirname(module.id), module.id)
    },
    '.json': function(module) {
        let data = fs.readFileSync(module.id, 'utf8')
        module.exports = data
    }
}
Module._resolveFilename = function(filename) {
    let file = path.resolve(__dirname, filename)
    let exist = fs.existsSync(file)
    if(exist) return file
    let exts = Object.keys(Module._extensions)
    for (let ext of exts) {
        let fileStr = file + ext
        if(fs.existsSync(fileStr)){
            return fileStr
        }
    }

}
Module.cache = {}
function req(filename){
    let filepath = Module._resolveFilename(filename)
    if (Module.cash[filename]) { // 缓存机制
        return Module.cache[filename].exports
    }
    let module = new Module(filepath)
    Module.cash = {[module.id]: module}
    module.load()
    return module.exports
}
const a = req('./a')
console.log(a)
/**
 * commonJS原理:
 * require是同步加载,主要是通过函数隔离
 * 如果是json,通过fs.readFileSync获取内容后,赋值给module.exports;
 * 如果是js把每个文件转成函数字符串去执行,每次执行都传入一个module类,有两个重要的属性,id和exports, id是文件的绝对路径,用于通过fs获取内容,另一个作用是做缓存对象的key
 * exports初始值是{},文件的this是module.exports,即{}
 * 所以有三种写法(引用数据类型):
 * this.a = 1
 * module.exports.a = 1
 * exports.a = 1
 * 如果是非引用数据类型,则以module.exports为主
 */