微前端初体验之沙箱

861 阅读4分钟

微前端是一种前端架构模式,旨在将一个大型单体应用程序拆分为多个小型的独立应用,从而更好地支持复杂的业务需求和团队组织。

沙箱环境

沙箱是指一种隔离机制,它可以确保微前端应用程序之间的安全隔离,避免应用程序之间的代码干扰和数据泄露。

在微前端架构中,每个微前端应用程序都运行在自己的沙箱中,以确保它们之间的代码和数据互相隔离。

举例,在线编译器就是一种沙箱,它使用iframe进行代码隔离,并创建了一个新的document对象,使得内部无法访问到外部环境的私有变量。

image.png

模拟实现沙箱环境

实现一个sandbox函数实现内外环境的隔离。传入一段可执行的代码,该代码无法访问或修改全局变量

var global = {};
const code = `
  const a = 1;
  console.log(a, global);
`;

function sandbox(code) {};
sandbox(code); // Reference Error
evalnew Function 的使用

eval函数将字符串转换为代码执行

eval执行的代码可以访问闭包和全局范围,因此存在代码注入安全问题,代码内部可以沿着作用域链往上找

eval('let a = 1');
a // 1

函数是由在运行时传入的字符串创建的

// new Function([arg1[,arg2[...argN]], functionBody])
new Function('a', 'return a')(1); // 1

相当于

(function(a) {
    return a;
})(1)

相对于eval,new Function 创建的作用域只能在全局作用域运行对性能方面进行了优化

也就是它内部的函数体访问的变量只会从全局变量对象上进行查找

var a = 2;
function f() {
    var a = 1;
    return new Function('return a;');
}
var h = f();
console.log(h()); // 2
within 的使用

with允许半沙盒执行,也就是语句将某个对象添加到作用域链的顶部,如果在沙盒中有某个未使用命名空间的变量,跟作用域链中的某个属性同名,则这个变量将指向这个属性值。如果沒有同名的属性,则将拋出 ReferenceError。

with在内部使用in运算符,对于块内的变量访问,它都在沙盒条件下计算变量,如果条件是true,将在沙盒中检索变量,否则,在全局范围内查找

in:沿着原型链查询属性,如for in。查找属性时优先从自身属性查找,然后往原型链上查找

with可能造成数据泄露,在非严格模式下,会自动在全局作用域创建一个全局变量

function sandBox(o) {
    with(o) {
      data = '';
      name = {};
      h = 3
    }
}
var o = {
    data: {},
    name: ''
}
var h = 1;
sandBox(o);
console.log(h); // 3
Proxy 的使用

构造函数,对数据进行代理,内置api可以实现拦截操作

Proxy - JavaScript | MDN (mozilla.org)

这里主要介绍俩个api:hasget

  • get(), 属性读取操作的捕捉器
  • has(), in操作符的捕捉器
const person = new Proxy({}, {
    has() { // in
        return true;
    },
    get() {
        return 1;
    }
})
proxy.a, proxy.b// 1 1
'a' in proxy // true

实现沙箱

function sandbox() {
    const _sandbox = new Proxy({}, {
        has(target, key) { // in
          return true;
        },
        get(target, prop, receiver) {
          if(prop === 'console') return console; // 暴露全局方法console
          return Reflect.get(target, prop, receiver);
        }
    });
    return function(code) { // 闭包缓存代理对象
        // 在沙盒中执行代码
        return eval(`with (_sandbox) { ${code} }`);
    }
}

const code = `
    const a = 1;
    const b = 2;
    c = 4;
    console.log('a + b =', a + b, 'c =', c);
`;
const SB = sandbox();
SB(code);

image.png

iframe

HTML 内联框架元素 (<iframe>)  表示嵌套的browsing context。它能够将另一个 HTML 页面嵌入到当前页面中。

- Iframe | MDN (mozilla.org)

页面上的每个<iframe>都需要增加内存和其他计算资源,这是因为每个浏览上下文都拥有完整的文档环境。

iframe创造一个沙箱环境,由于同源策略的导致,无法访问父网页的dom或者变量(安全)

iframe 实现沙箱

<textarea id="code"></textarea>
<button onclick="runCode()">Run</button>
<script>
  function runCode() {
    var code = document.getElementById("code").value;
    var iframe = document.createElement("iframe");
    iframe.style.display = "none";
    document.body.appendChild(iframe);
    var sandboxWindow = iframe.contentWindow; // iframe的window环境
    sandboxWindow.console = console;
    sandboxWindow.eval(code);
    document.body.removeChild(iframe);
  }
</script>

image.png

iframe的跨域通信

如果要用iframe实现跨域(访问父网页内容),可以用postMessage API

window.postMessage - Web API 接口参考 | MDN (mozilla.org)

postMessage 是 HTML5 中的一个跨窗口通信的 API,它可以在两个窗口之间安全地传递信息。它的基本用法是,通过在一个窗口中调用 postMessage 方法,向另一个窗口发送消息

index.html

<!DOCTYPE html>
<html>
<head>
  <title>Sandbox</title>
</head>
<body>
  <h1>Sandbox</h1>
  <iframe id="sandbox" sandbox="allow-scripts"></iframe>
  <script>
    var iframe = document.getElementById('sandbox');
    iframe.src = './sandboxed.html';
    iframe.addEventListener('load', function() {
      iframe.contentWindow.postMessage({
        type: 'init',
        code: 'console.log("Hello from the sandbox!");'
      }, '*');
    });
    window.addEventListener('message', function(event) {
      if (event.origin !== 'null') return; // 防止跨域攻击
      if (event.data.type === 'log') {
        console.log('[Sandbox]', event.data.args);
      }
    });
  </script>
</body>
</html>

sandboxed.html

<!DOCTYPE html>
<html>
<head>
  <title>Sandboxed</title>
</head>
<body>
  <h1>Sandboxed</h1>
  <script>
    window.addEventListener('message', function(event) {
      if (event.origin !== 'null') return; // 防止跨域攻击
      if (event.data.type === 'init') {
        try {
          eval(event.data.code);
        } catch (err) {
          console.error(err);
        }
      } else if (event.data.type === 'log') {
        console.log('[Sandboxed]', event.data.args);
        event.source.postMessage({
          type: 'log',
          args: ['Got it!']
        }, '*');
      }
    });
  </script>
</body>
</html>

总结

微前端和沙箱之间存在着密切的关系。每个微前端应用程序都可以在自己的上下文中运行,与其他应用程序隔离开来,从而避免了应用程序之间的代码冲突和数据泄露。

此外,沙箱还可以确保微前端应用程序之间的安全通信。每个微前端应用程序都可以使用安全的通信协议与其他应用程序通信,从而确保数据的安全性和完整性。

总之,沙箱是微前端架构的重要组成部分,它可以确保微前端应用程序之间的隔离和安全,使得微前端架构更加健壮和可靠。