了解微前端

微前端:将微服务的思想拓展到了前端开发,是一种实现了多个团队独立开发,共同构建现代化web应用(Modern Web App)的策略和方法。

什么是微前端

当前流行的趋势是构建基于微服务架构的功能强大的浏览器应用程序(又叫单页应用程序),这样的单体应用在一个相对长的时间跨度下,前端层面会不断增长,逐渐变得难以维护,成为巨石应用(Frontend Monolith)。
微前端背后的思想是将一个网站或者web应用看成是被多个独立团队维护的功能的集合(composition of features)。每个团队都有他们自己关心的或者专注的业务或者任务领域。每个团队是跨职能的,端对端的实现它的功能(feature),从数据库到用户界面。

Monolithic Frontends
image

Organisation in Verticals
image

微前端背后的核心思想

  • 技术无关
    每个团队都应该在不需要和其他团队协商的情况下选择和升级他们的技术栈。自定义元素(Custom Element)是一种隐藏实现细节并且提供接口的好的方法。
  • 互相隔离的代码
    不共享一个运行环境,即使是所有的团队都使用相同的框架。构建独立的应用程序,不依赖共享状态或者全局变量。
  • 建立团队前缀(命名)
    在未隔离的时候就约定命名,约定CSS,事件,localstorage和Cookies的命名,以避免冲突并阐明所有权。
  • 优先浏览器功能而不是自定义API
    使用浏览器事件而不是建立一个全局的发布订阅系统,如果确实需要建立跨团队的API,尽量使它简单。
  • 建立弹性站点
    即使JavaScript失败或尚未执行,功能也应该是可用的。使用通用渲染和响应式增强来提高可感知的性能。

需考虑的问题和解决方案

隔离js的机制,避免css冲突,按需加载资源,在团队之间共享公共资源,处理数据获取并考虑为用户提供良好的加载状态…

1. 应用之间的通信方式

  • 基于URL来传参数,传递消息能力弱
  • CustomEvent
  • 基于prop主子应用间通信
  • 全局变量

2. 公共依赖

  • CDN-externals
  • webpack联邦模块

3. CSS隔离方案

  • 子应用之间样式隔离
    • 动态样式表
  • 父子之间样式隔离

    • BEM(Block Element Modifier)约定项目前缀
    • CSS-Modules 打包时生成不冲突的选择器名
    • Shadow DOM 真正意义的隔离(qiankun)

      • 影子dom:设置外界拿不到

        1
        2
        3
        4
        5
        6
        7
        let shadowDOM = shadow.attachShadow({mode: 'closed'});
        let p = window.createElement('p');
        p.innerHTML = 'hello';
        let style = document.createElement('style');
        style.textContent = 'p{color:red}';
        shadowDOM.appendChild(style);
        shadowDOM.appendChild(p);
      • body上挂的话是边界之外

    • css-in-js(不推荐)

js隔离方案

应用之间切换的时候,丢弃旧的属性,恢复新的属性,给应用创造一个干净的环境。应用切换后不会影响全局。

  • 快照(qiankun)一段时间前快照,一段时间后快照,保存修改;不停拍照、替换、还原

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    class SnapshotSandbox {
    constructor() {
    this.proxy = window; // window属性
    this.modifyPropsMap = {}; // 记录在window上的修改
    this.active();
    }
    active() {
    this.windowSnapshot = {}; // 拍照
    for (const prop in window) {
    if (window.hasOwnProperty(prop)) {
    this.windowSnapshot[prop] = window[prop];
    }
    }
    Object.keys(this.modifyPropsMap).forEach(p => {
    window[p] = this.modifyPropsMap[p]; // 把上次的修改应用到快照上
    })
    }
    inactive() {
    for (const prop in window) {
    if (window.hasOwnProperty(prop)) {
    if (window[prop] !== this.windowSnapshot[prop]) {
    this.modifyPropsMap[prop] = window[prop];
    window[prop] = this.windowSnapshot[prop];
    }
    }
    }
    }
    }
    let sandbox = new SnapshotSandbox();
    ((window) => {
    window.a = 1;
    window.b = 2;
    sandbox.inactive(); // 失活
    sandbox.active(); // 激活
    })(sandbox.proxy);
    • 如果是多个子应用,不能使用这种方式,使用es6的proxy
  • 代理:可以实现多应用沙箱,把不同的应用用不同的代理来处理

实现方案

1. Web Components

自定义元素(Custom Element),Web Components Spec中的互操作性方面,是浏览器中集成的一个很好的原语。每个团队都使用自己选择的网络技术来构建其组件,并将其包装到自定义元素中(例如<order-minicart> </ order-minicart>)。该特定元素(标记名,属性和事件)的DOM规范充当其他团队的合同或公共API。优点是他们可以使用组件及其功能,而无需了解实现。他们只需要能够与DOM交互即可。

2. iframe

  • 子应用切换路由,会刷新页面,很多状态会无
  • 通信问题

3. single-spa

  • 实现了路由劫持和应用加载,也就是可以根据路由加载对应的应用
  • 子应用的接入协议,导出bootstrap,mount,unmount
  • 子应用内路由切换要限制好绝对路径
  • 没有处理样式隔离和js隔离,没有js沙箱的机制
    • js隔离:比如共用windows上的一些方法
    • 沙箱: 一种安全机制,为运行中的程序提供的隔离环境
  • 不能动态加载js文件

4. qiankun

  • 基于single-spa
  • 沙箱,import,html-entry
  • 子应用在new的时候挂载到自己的html中,基座拿到这个挂载后的html,将其插入到对应的container中
  • 子应用的协议,和single-spa一样,导出bootstrap(启动的时候做的事情),mount(render),unmount(销毁/卸载)
  • 子应用要自己实现跨域
  • 子应用之间通信:通过共同的父级,或者全局window

链接