/** * 实现一个commonjs模块加载器 (同步的方式),所以不适合浏览器,会造成卡顿 * Commonjs的规范的准则 * 1. 模块加载器 解析方件地址, 有一个寻找的规则,目的找到文件 * src/xx/xxx/xxx/xx.js * 2. 模块解析器: 执行文件的内容,Node里面使用V8执行*/
// exapmle: ---// exports.a = 'a';// require('./a.js')
class Module { constructor(moduleName, source) { // 暴露数据 this.exports = {} // 保存模块信息 this.moduleName = moduleName; // 缓存 this.$cacheModule = new Map(); // 源代码 this.$source = source } /** * * @param {模块名称} moduleName * @param {文件的源代码} source */ require = (moduleName, source) => { // 访问的时候缓存, 都执行文件内容的话, 开销大,所以加缓存 if (this.$cacheModule.has(moduleName)) { return this.$cacheModule.get(moduleName).exports; } // 创建模块 const module = new Module(moduleName, source); // 执行文件 const exports = this.compile(module, source); // 缓存 this.$cacheModule.set(moduleName, module); // 返回 return exports } // 拼一个闭包出来, IIFE $wrap = (code) => { // 拼的一个闭包函数,然后拼接起来 const wrapper = [ 'return (function (module, exports, require) {', '\n});' ]; return wrapper[0] + code + wrapper[1]; }
/** * 实现一个浏览器运行的解析器 * 核心是创建是一个隔离的沙箱环境,来执行代码 * 隔离: 不能访问闭包的变量, 1. 不能访问全局变量, 只能访问传入的变量 * eval: 可以访问全局/闭包,但需要解析执行, es6后, 如果是间接使用eval, 不可以访问闭包 * -> (0, eval) ('var a = b + 1'); * new Funciton: 不可以访问闭包,可以访问全局, 只能编译一次 * with: with包裹的对象, 会被放到原型链的顶部, 而且通过in 操作符判断的 * 如果通过with 塞入传入的数据,只能访问传入的变量,任何属性,取不到返回undefined, 永远不会访问全局的域 * unscopable: 这个对象不能够被with处量 * @param {*} code */ $runInThisContext = (code, whiteList=['console']) => { // 使用with保证可以通过我们传入的sanbox对象数据 const wrapper = `with(sandbox) {${code}}`; const func = new Function('sandbox', wrapper) return function (sandbox) { // 塞到源代码中的变量 if (!sandbox || typeof sandbox !== 'object') { throw Error('sandbox parameter must be an object') } // 限制变量的查找,当怎么找,都是从sanbox里找 const proxedObject = new Proxy(sandbox, { // 传门处理in的操作符,因为这里的in是with里的特点, 这样就不会从原型链不停的往上找 has(target, key) { if (!whiteList.includes(key)) return true }, get(target, key, receiver) { if (key === Symbol.unscopables) { return void 0; } // Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。 return Reflect.get(target, key, receiver) } }); return func(proxedObject) } } /** * 执行文件内容,入参数是文件源代码字符串 * * IIFE: (function() {})(xxx, yyy); * * function (proxiedSandbox) { * with (proxiedSandbox) { * return (function (module, exports, require) { * // 文件内容字符串 * }) * } * } */ compile = (module, source) => { console.log(this) // return (function(module, exports, require) { //xxxx }); ⚠️ const iifeString = this.$wrap(source); const compiler = this.$runInThisContext(iifeString)({}) compiler.call(module, module, module.exports, this.require); return module.exports; }}
const m = new Module();
// a.jsconst sourceCodeFromAModule = ` const b = require('b.js', 'const a = require("a.js"); console.log("a module: ", a); exports.action = function() { console.log("execute action from B module successfully 🎉") }');
b.action();
exports.action = function() { console.log("execute action from A module!"); }`m.require('a.js', sourceCodeFromAModule);
评论