/**
* 实现一个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.js
const 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);
评论