6

微前端中实现沙箱环境的方案调研 - 空山与新雨

 1 year ago
source link: https://www.cnblogs.com/walkermag/p/16997438.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在微前端实践过程中有一个必然会遇到的问题:全局作用域变量的污染问题,具体来说就是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方案是比较优雅和实用的方案


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK