微前端架构下应用隔离的实现细节与踩坑经验分享

UE丶利娜 前端 阅读 2,727
赞 22 收藏
二维码
手机扫码查看
反馈

先看效果,再看代码

最近在一个多团队协作的项目中,我们遇到了一个头疼的问题:多个应用共存时,样式和脚本冲突得一塌糊涂。尤其是某个老系统用的是jQuery,新模块是Vue,结果就是各种奇怪的bug层出不穷。

微前端架构下应用隔离的实现细节与踩坑经验分享

后来我用了应用隔离的技术,亲测有效!直接上核心代码:

const createAppContainer = (appName, appHtml) => {
  const iframe = document.createElement('iframe');
  iframe.style.border = 'none';
  iframe.style.width = '100%';
  iframe.style.height = '100%';
  document.getElementById(appName).appendChild(iframe);

  const appContainer = iframe.contentDocument.body;
  appContainer.innerHTML = appHtml;

  return {
    mount: (app) => app.mount(appContainer),
    unmount: () => (appContainer.innerHTML = ''),
  };
};

// 使用示例
const vueApp = Vue.createApp({
  template: '<div>我是Vue应用</div>',
});
createAppContainer('app1', '').mount(vueApp);

这段代码的核心思想是通过iframe创建独立的运行环境,把不同框架的应用隔离开来。虽然看起来简单,但实际效果相当不错。

这个场景最好用

说到应用隔离,最常见的场景就是微前端架构了。比如我们之前做的一个项目,主应用是AngularJS,子应用分别是React和Vue。这种情况下,使用iframe隔离是最稳妥的选择。

这里有个关键点要注意:不要直接在主文档里混用不同框架。我就踩过这个坑,结果就是内存泄漏、事件冲突等问题接踵而至。

我的建议是直接用这种方式:

<div id="legacy-app"></div>
<div id="react-app"></div>
<div id="vue-app"></div>

<script>
  // 假设这是三个不同框架的应用
  const legacyApp = new LegacyApp();
  legacyApp.init(document.getElementById('legacy-app'));

  const reactApp = ReactDOM.createRoot(document.getElementById('react-app'));
  reactApp.render(React.createElement(App));

  const vueApp = Vue.createApp({
    template: '<div>Vue App</div>',
  });
  vueApp.mount('#vue-app');
</script>

这样分开挂载,互不干扰。虽然不是完全隔离,但对于一些简单的场景已经够用了。

踩坑提醒:这三点一定注意

在实践过程中,我发现有几个常见的坑点需要特别提醒:

  • 通信问题:隔离之后,应用间的通信变得麻烦。最初我尝试用window.postMessage,但发现性能开销太大。后来改用shared worker,效率提升明显。
  • 路由冲突:特别是hash模式的路由,多个应用同时存在时很容易打架。建议统一使用history模式,并做好命名空间的区分。
  • 样式污染:即使做了隔离,全局样式还是可能互相影响。我现在的做法是在每个应用的入口文件里加上scoped样式处理。

举个具体的例子,下面是我在Vue应用中处理样式的代码:

import Vue from 'vue';
import App from './App.vue';

new Vue({
  el: '#vue-app',
  render: h => h(App),
});

// 样式隔离处理
const style = document.createElement('style');
style.setAttribute('scoped', '');
style.innerHTML = 
  .vue-app-class {
    all: initial;
  }
;
document.head.appendChild(style);

高级技巧:动态加载的妙用

有时候我们需要按需加载不同的应用,这时候可以结合webpack的动态import来做:

const loadApp = async (appName) => {
  const appModule = await import(./apps/${appName}/index.js);
  const container = document.getElementById(appName);
  
  if (appModule.bootstrap) {
    appModule.bootstrap(container);
  }
};

// 动态加载示例
loadApp('vueApp');
loadApp('reactApp');

这样做不仅实现了按需加载,还能保证各个应用的独立性。不过要注意:服务端一定要做好CORS配置,否则动态加载会报跨域错误。

还有这些拓展玩法

除了上面提到的方法,其实应用隔离还有很多其他的实现方式:

  • Web Components:原生支持的组件化方案,适合轻量级隔离
  • Sandbox:通过with语句创建沙箱环境,适合纯JS隔离
  • Proxy:拦截对象操作,实现更细粒度的隔离控制

比如下面是一个简单的Proxy实现:

const createSandbox = (code) => {
  const sandbox = {
    console,
    setTimeout,
    window: {},
    document: {},
  };

  const proxy = new Proxy(sandbox, {
    has(target, key) {
      return key in target;
    },
  });

  return function() {
    with(proxy) {
      eval(code);
    }
  };
};

// 使用示例
const safeCode = 
  console.log(&#039;hello&#039;);
  document.title = &#039;sandbox test&#039;;
;
createSandbox(safeCode)();

这个技术的拓展用法还有很多,后续我会继续分享这类博客。以上是我个人对应用隔离的完整讲解,有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论