微前端中实现沙箱环境的方案调研

前言

在微前端实践过程中有一个必然会遇到的问题:全局作用域变量的污染问题,具体来说就是window对象挂载数据会被主子应用获取和修改导致数据相互污染问题,这时候如果能在应用之间做个数据隔离,最好能实现一个沙箱环境,对解决问题很有帮助。

iframe方案

说到沙箱隔离,首先想到的是iframe,自带数据隔离能力,从iframe中获取到的window对象是一个全新和纯净的对象,然而在如果要作为沙箱执行业务代码的话是不行的,但是完全可以作为一个执行脚本环境,既安全,又简单:

const parent = window; const frame = document.createElement('iframe');  const data = [1, 2, 3, 4, 5, 6];  // 当前页面给 iframe 发送消息 frame.onload = function (e) {   frame.contentWindow.postMessage(data); };  document.body.appendChild(frame);  // iframe 接收到消息后处理 const code = `return dataInIframe.filter((item) => item % 2 === 0)`;  frame.contentWindow.addEventListener('message', function (e) {   const func = new frame.contentWindow.Function('dataInIframe', code);   parent.postMessage(func(e.data)); });  // 父页面接收 iframe 发送过来的消息 parent.addEventListener(   'message',   function (e) {     console.log('message from iframe:', e.data);   },   false, ); 

快照方案

在微前端框架qiankun中提供了快照方案,其原理就是在应用加载之时保存最初的window对象,卸载应用之时通过diff操作记录改过的属性即制作快照,当再次激活应用的时候恢复之前的快照。该方案的缺点是会污染window导致,多个应用无法同时处于激活状态,优点是兼容性好。

// 保存差异的方式 function createSandbox(){   let originWindow = {}   let diffMap = {};   return {     toActive(){       originWindow = {};       // 保存初始window对象       Object.keys(window).forEach(prop=>{         originWindow[prop] = window[prop];       })       // 将上次退出的时候保存的差异还原回去,也就是恢复快照       Object.keys(diffMap).forEach(prop=>{         window[prop] = diffMap[prop];       })     },     toInActive(){       Object.keys(window).forEach(prop=>{         if(window[prop] !== originWindow[prop]){           // 保存差异           diffMap[prop] = window[prop]           // 还原现场           window[prop] = originWindow[prop];         }       })     }   } }   window.originData = '最初的window上的数据';  console.log(window.originData, window.a1, window.b1); // 最初的window上的数据 undefined undefined const sandbox1 = createSandbox();  // 创建应用的时候,同时创建沙箱 sandbox1.toActive(); // 沙箱激活 window.a1 = 'aaaaa'; // 应用修改window上的属性 console.log(window.originData, window.a1, window.b1); // 最初的window上的数据 aaaaa undefined sandbox1.toInActive(); // 切换应用前沙箱1退出 const sandbox2 = createSandbox(); // 创建应用的时候,同时创建沙箱 sandbox2.toActive(); // 沙箱激活 console.log(window.originData, window.a1, window.b1); // 最初的window上的数据 undefined undefined window.b1 = 'bbbbb'; // 应用修改window上的属性 console.log(window.originData, window.a1, window.b1); // 最初的window上的数据 undefined bbbbb   和上面的数据做个对比 sandbox2.toInActive();  // 从应用2切换至1 sandbox1.toActive(); // 从应用2切换至1 console.log(window.originData, window.a1, window.b1); // 最初的window上的数据 aaaaa undefined 和上面的数据做个对比  sandbox1.toInActive();  // 从应用1切换至2 sandbox2.toActive(); // 从应用1切换至2 console.log(window.originData, window.a1, window.b1); // 最初的window上的数据 undefined bbbbb 和上面的数据做个对比  

代理方案

使用ES6中的proxy语法对自定义的全局对象代理,这样当在沙箱内部对window对象修改的时候,实际上修改的是自定义的全局对象,而不会影响到真正的window对象。其优点是不会污染window,支持多个应用同时激活。 缺点是部分浏览器不支持proxy,

function createProxySandBox(){   const rawWindow = window;   const fakeWindow = {};   const proxy = new Proxy(fakeWindow, {     get:(target, p)=>{       if(target.hasOwnProperty(p)){         return target[p];       }       return rawWindow[p];     },     set(target, p, value){       if(!target.hasOwnProperty(p) && rawWindow.hasOwnProperty(p)){         rawWindow[p] = value       } else {         target[p] = value;       }     }   })   return proxy; } const sandbox1 = createProxySandBox();  ((window) => {   window.a = 'a'; })(sandbox1);  const sandbox2 = createProxySandBox();  ((window) => {   console.log(window.a)   window.a = 'fff'; })(sandbox2); console.log(window.a)  

总结

proxy方案是比较优雅和实用的方案

发表评论

相关文章