假如你是『无界』微前端框架的开发者

6,232 阅读6分钟

本文正在参加「金石计划」

传送门

之前写过一些微前端的文章,建议结合在一起阅读,本文将对比乾坤的实现方式进行讲解:

微前端技术选型

面试官:你了解微前端吗?

源码解析系列-qiankun源码详解

前言

在我们分析「无界」这个具体的微前端方案之前,我们先假设我们自己是框架设计者,思考一下一个微前端框架需要解决哪些问题;

给读者思考一分钟......

子应用如何加载,生命周期管理

js隔离措施

css隔离措施

通信机制

......

我们就以这些问题为切入点去看一看「无界」是怎么解决这些具体问题的。先回顾一下「无界」微前端怎么使用;

「无界」的使用方式

无界对于不同的前端框架都有对应的组件化封装。假如我们主项目是React技术栈,那么直接用WujieReact这个组件就可以了:

ReactDOM.render(
  <>
    <App />
    <WujieReact
      width="100%"
      height="100%"
      name="browser"
      url={'http://localhost:3001/'}
    />
  </>,
  document.getElementById('root'),
);

无界微前端没有什么改造成本,那么他是怎么做到的呢?接下来我们通过半源码、半手写代码的方式去分析「无界」这个微前端框架;

子应用如何加载,生命周期管理

子应用的加载与乾坤的方式相同,都通过一个importHTML函数进行加载;它的具体过程就是:

  1. fetch我们传入的这个url,得到一个html字符串,然后通过正则表达式匹配到内部样式表、外部样式表、脚本;源码通过/<(link)\s+.*?>/gis匹配外部样式,通过/(<script[\s\S]*?>)[\s\S]*?<\/script>/gi匹配脚本;通过/<style[^>]*>[\s\S]*?<\/style>/gi匹配内部样式;我们尝试一下自己写一个importHTML,解析一下我们当前这一篇文章:

    const STYLE_REG = /<style>(.*)<\/style>/gi;
    const SCRIPT_REG = /<script>(.*)<\/script>/gi;
    const LINK_REG = /<(link)\s+.*?>/gi
    
    async function imoprtHTML() {
        let html = await fetch("https://juejin.cn/post/7209162467928096825");
        html = await html.text();
        const ans = html.replace(STYLE_REG, match=>{
            // ... 很多逻辑
            return match;
        }
        ).replace(SCRIPT_REG, match=>{
            // ... 很多逻辑
            return match;
        }
        ).replace(LINK_REG, match=>{
            // ... 很多逻辑
            debugger
            return match;
        })
    }
    
    
  2. 第二步对于外部样式表、外部脚本我们也需要通过fetch获取到内容然后将代码存储起来

  3. 将合并的样式表添加到页面上截屏2023-03-20 17.24.52.png

  4. 执行js,这个详细过程我们后文分析截屏2023-03-20 17.29.48.png

  5. 子应用加载完毕

这个过程中涉及到哪些生命周期呢?

  1. beforeLoad:子应用开始加载静态资源前触发,也就是importHTML之前触发 image.png
  2. beforeMount:子应用渲染前触发 (生命周期改造专用) image.png
  3. afterMount:子应用渲染后触发(生命周期改造专用)
  4. beforeUnmount:子应用卸载前触发(生命周期改造专用)
  5. afterUnmount:子应用卸载后触发(生命周期改造专用)
  6. activated:子应用进入后触发(保活模式专用)
  7. deactivated:子应用离开后触发(保活模式专用)

从上面我们就了解到了「无界」框架整体的一个加载流程,整体看起来它的生命周期比较多,比乾坤要多很多,但是比较好记,因为与Vue的生命周期在命名上有些类似;接下来我们具体分析一下js的隔离措施;

js沙箱

先回顾一下乾坤是怎么创建js沙箱的:乾坤设计了三种沙箱,在不支持Proxy的浏览器基于window进行diff,把修改了的变量存储到一个对象中,在子应用卸载时还原;支持Proxy的使用Proxywindow进行代理,然后存储修改了的变量;多个子应用共存的情况下会拷贝一份window对象;对拷贝的对象进行代理,子应用操作的也是这个拷贝的window对象;最后使用一个函数包裹子应用的js代码,绑定thiswindow,然后eval执行;这里使用到了eval执行代码,并且还使用到了with,这两个都是性能杀手,我们对比着看看「无界」怎么处理的?

「无界」是通过iframe进行环境隔离,也就是说我们的子应用上的全局对象其实是动态创建的一个iframe的全局对象,我们简单实现一下这个过程:

function iframePool() {
    const pool = [];
    return function() {
        const iframe = document.createElement("iframe");
        pool.push(iframe);
        return {
            getIframe() {
                const ele = pool.pop();
                return ele;
            },
            recover(ele) {
                pool.push(ele);
            }
        }
    }
}
const container = iframePool()();
const iframe = container.getIframe();
document.body.appendChild(iframe);
container.recover(iframe);
window.a = 1234;

const proxyWindow = iframe.contentWindow;

const externalScripts = `
    window.abc = 1;
    console.log(window.abc)
`
function excute() {
    const doc = proxyWindow.document;
    const sc = doc.createElement("script");
    sc.innerHTML = externalScripts;
    doc.body.appendChild(sc);
    console.log(proxyWindow.abc, "********")
}

excute();

简单解释一下这个过程:我们获取到了子应用代码之后将代码添加到iframe中,这样很简单地就实现了一个沙箱,然后子应用的全局环境绑定到iframe的window上就可以了,看起来很简单是不是?而且它也没有兼容性问题啊,但是iframe本身的一些问题可能会影响到主应用,这个需要我们具体去测评一下,iframe会阻塞页面的onload事件,通常用来加载广告这种无关紧要的内容,如果加载子应用会不会有性能问题,这个就需要不断的实践验证了

css隔离措施:在web-components容器下css可以自然隔离

通信机制

「无界」的通信机制是通过发布订阅模式实现的,就是创建一个全局的发布订阅者,然后挂载到了「无界」对象上:

image.png

这一块源码比较好实现,读者可以自行实现一个发布订阅模式

「无界」插件

「无界」中还使用了插件架构,主要分为html插件、css插件、js插件,html插件是在html解析完成之后执行,也就是上文提到的importHTML

image.png

cssLoader是在插入到DOM之前执行:

image.png

jsLoader插件在插入到iframe之前执行

image.png

当然还有一些其他的插件,无非都是在这些方法之前或者之后执行,这里列举一下:js-excludes、css-excludes、js-before-loaders、js-after-loaders、css-before-loaders、css-after-loaders

优雅降级

「无界」是存在优雅降级的,对于不支持webcomponents的浏览器,直接降级为iframe,虽然直接使用iframe体验上会差很多,但是目前已经有95%的浏览器已经支持webcomponents,所以影响的只是一些还在使用IE浏览器的用户,况且我们的主流框架也都抛弃了IE

image.png

后记

我们先站在高处思考了一下一个微前端框架应该解决什么问题;然后又从无界的使用、生命周期、js沙箱的创建、通信机制的实现、插件、优雅降级等几个方面详细地分析了一下「无界」内部的原理;「无界」「乾坤」相比,它们是两种不同类型的微前端方案,一种是基于webcomponents,一种是基于路由,它们孰优孰劣需要结合项目自身的要求去分析;

从jQuery到三大框架经历了很长时间,那么一个完美的微前端方案需要等多长时间呢?