commonjs规范
- 每个js文件都是一个模块
- 模块的导出 module.exports
- 模块的导入require
require的执行步骤
-
调用require时会内部调用Module._load方法;
-
Module.resolveFilename 解析文件名,这一步会尝试给文件加上后缀;
const filename = Module._resolveFilename(request, parent, isMain); -
创建当前模块实例,new Module() => {id,exports};
const module = cachedModule || new Module(filename, parent); function Module(id = '', parent) { this.id = id; this.path = path.dirname(id); this.exports = {}; moduleParentCache.set(this, parent); updateChildren(parent, this, false); this.filename = null; this.loaded = false; this.children = []; } -
调用module.load加载文件
-
根据文件后缀调用相应的方法进行加载,这里使用了策略模式;
Module._extensions[extension](this, filename); -
读取文件内容
content = fs.readFileSync(filename, 'utf8'); -
调用wrapSafe方法给读取到的内容包装一个函数,老版本直接使用字符串拼接,新版本有专门的方法
vm.compileFunction运行工之后直接包装成一个函数,包装完之后就长这样:ƒ (exports, require, module, __filename, __dirname) {\nconst str = 'abc';\nmodule.exports = str;\n\n} // 参数如下,也就是说每个文件都可以直接用这几个变量 'exports', 'require', 'module', '__filename', '__dirname', -
执行这个函数,并将module传递给用户,用户将手动给module.exports进行赋值
result = ReflectApply(compiledWrapper, thisValue, [exports, require, module, filename, dirname]); return result;
-
-
返回module.exports;;
一个mini版的commonjs实现
const path = require('path');
const fs = require('fs');
const vm = require('vm');
function Module(id) {
this.id = id;
this.exports = {};
}
Module._extensions = {
'.js'(id) {
const content = fs.readFileSync(id, 'utf-8');
const wrapperFn = vm.compileFunction(content, ['exports', 'require', 'module', '__filename', '__dirname']);
return wrapperFn
},
'.json'() {},
};
Module._resolveFilename = function (id) {
let filepath = path.resolve(__dirname, id);
if (fs.existsSync(filepath)) return filepath;
// 取出的key的顺序与Object.keys是否一致?此处要求有顺序
const exts = Reflect.ownKeys(Module._extensions);
for (let i = 0; i < exts.length; i++) {
const file = filepath + exts[i];
if (fs.existsSync(file)) return file;
}
throw new Error('Module not found: ' + id);
};
Module.prototype.load = function (id) {
const ext = path.extname(id);
const safeWrap = Module._extensions[ext](id);
const exports = this.exports;
const thisValue = exports;
const require = req;
const module = this;
const filename = id;
const dirname = path.dirname(filename);
Reflect.apply(safeWrap, thisValue, [exports, require, module, filename, dirname])
};
const req = function (id) {
const filename = Module._resolveFilename(id);
const module = new Module(filename);
module.load(filename);
return module.exports;
};
const str = req('./1');
console.log('req的输出:' + str.toString());
todo
加载过的模块进行缓存,第二次取值时直接返回