MicroApp微前端实践

593 阅读4分钟

一 问题

如何将一个庞大的前端应用拆分成多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用融合为一个完整的应用。

二 微前端概念

微前端的概念是由ThoughtWorks在2016年提出的,它借鉴了微服务的架构理念,核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用融合为一个完整的应用,或者将原本运行已久、没有关联的几个应用融合为一个应用。微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。

三 MicroApp优势

1 使用简单

可以在基座应用中嵌入一行代码即可渲染一个微前端应用,还提供了js沙箱、样式隔离、元素隔离、预加载、数据通信、静态资源补全等一系列完善的功能。

2 零依赖

micro-app没有任何依赖,这赋予它小巧的体积和更高的扩展性。

3 兼容所有框架

在任何技术框架中都可以正常运行

四 效果演示

实际运行项目进行演示

五 基座应用使用方法

基座应用不限制技术栈,此基座以vue为例

1 初始化项目

此步骤使用脚手架工具快速搭建即可,省略

2 安装依赖

npm i @micro-zoe/micro-app --save

3 入口处引入

import microApp from '@micro-zoe/micro-app'

microApp.start({
    // microApp生命周期
    lifeCycles: {
        created(e) {
            console.log('created')
        },
        beforemount(e) {
            console.log('beforemount')
        },
        mounted(e) {
            console.log('mounted')
        },
        unmount(e) {
            console.log('unmount')
        },
        error(e) {
            console.log('error', e)
        }
    }
})

4 给子应用分配路由

const routes = [
    {
        path: '/',
        name: 'base',
        component: () => import("../views/base.vue"),
    },
    {
        path: '/micro-app1/:page*', // vue-router@4.x path的写法为:'/my-page/:page*'
        name: 'child1',
        component: () => import("../views/app1.vue"),
    },
    {
        path: '/micro-app2/:page*', // vue-router@4.x path的写法为:'/my-page/:page*'
        name: 'child2',
        component: () => import("../views/app2.vue"),
    }
]

5 嵌入子应用

<!-- 
  name(必传):应用名称,必须与第4步配置路由的name保持一致
  url(必传):应用地址,会被自动补全为http://localhost:3000/index.html
  baseroute(可选):基座应用分配给子应用的基础路由,就是上面的 `/micro-app1` 或者 `/micro-app2`
--> 
<micro-app name='child1' url='http://localhost:8081/micro-app1' baseroute="/micro-app1"></micro-app>

六 子应用使用方法

子应用不限制技术栈,此基座以vue为例

1 设置基础路由

如果基座应用是history路由,子应用是hash路由,这一步可以省略

const router = createRouter({
    history: createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL),
    routes: routes
})

2 创建公共路径引入文件,并在入口文件引入

// __MICRO_APP_ENVIRONMENT__和__MICRO_APP_PUBLIC_PATH__是由micro-app注入的全局变量
if (window.__MICRO_APP_ENVIRONMENT__) {
    // eslint-disable-next-line
    __webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__
}
import './public-path'

3 配置项目跨域访问

 devServer: {
     headers: {
       'Access-Control-Allow-Origin': '*',
     }
}

七 提供功能

1 数据通信

micro-app提供了一套灵活的数据通信机制,方便基座应用和子应用之间的数据传输。

(1)子应用获取来自基座应用的数据

setup() {
  const dataListener = (data) => {
    console.log("来自基座应用的数据", data);
  };

  onMounted(() => {
    // 绑定监听函数
    window.microApp?.addDataListener(dataListener, true);
    // 解绑监听函数
    // window.microApp.removeDataListener(dataListener)
  });

  return {
    dataListener
  };
}

(2)子应用向基座应用发送数据

 window.microApp?.dispatch({
   type: "app1向基座发送的数据",
   value: 123,
 });

(3)基座应用向子应用发送数据

import microApp from "@micro-zoe/micro-app";

// child1为基座应用的name
microApp.setData("child1", {
  type: "发送给app1的数据",
  value: 123,
});

(4)基座应用获取来自子应用的数据

<template>
  <micro-app
    name='my-app'
    url='xx'
    // 数据在事件对象的detail.data字段中子应用每次发送数据都会触发datachange
    @datachange='handleDataChange'
  />
</template>

<script>
export default {
  methods: {
    handleDataChange (e) {
      console.log('来自子应用的数据:', e.detail.data)
    }
  }
}
</script>

2 生命周期

(1)应用内生命周期

<template>
  <micro-app
    name='xx'
    url='xx'
    @created='created'
    @beforemount='beforemount'
    @mounted='mounted'
    @unmount='unmount'
    @error='error'
  />
</template>

<script>
export default {
  methods: {
    created () {
      console.log('micro-app元素被创建'),
    },
    beforemount () {
      console.log('即将被渲染'),
    },
    mounted () {
      console.log('已经渲染完成'),
    },
    unmount () {
      console.log('已经卸载'),
    },
    error () {
      console.log('渲染出错'),
    }
  }
}
</script>

(2)全局生命周期

import microApp from '@micro-zoe/micro-app'

microApp.start({
  lifeCycles: {
    created (e) {
      console.log('created')
    },
    beforemount (e) {
      console.log('beforemount')
    },
    mounted (e) {
      console.log('mounted')
    },
    unmount (e) {
      console.log('unmount')
    },
    error (e) {
      console.log('error')
    }
  }
})

3 环境变量

(1)判断是否处于微前端环境

window.__MICRO_APP_ENVIRONMENT__

(2)获取当前应用名称

window.__MICRO_APP_NAME__

(3)判断是否是基座应用

window.__MICRO_APP_BASE_APPLICATION__

八 其他

1 什么情况下需要用到微前端

多个团队采用不同技术栈并行开发;项目单独部署,单独维护。

2 iframe与微前端对比

(1)iframe的优缺点

iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题。

优点:

  1. 完全隔离了css和js,避免了各个系统之间的样式和js污染。
  2. 可以在子系统完全不修改的情况下嵌入进来。

缺点:

  1. 页面加载问题: 影响主页面加载,阻塞onload事件,本身加载也很慢,页面缓存过多会导致电脑卡顿。
  2. 布局问题:iframe必须给一个指定的高度,否则会塌陷。解决办法:子系统实时计算高度并通过postMessage发送给主页面,主页面动态设置高度,修改子系统或者代理插入脚本。有些情况会出现多个滚动条,用户体验不佳。

(2)微前端的优缺点

优点:

  1. 技术无关性
  2. 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染

缺点:

  1. 子应用需按照规范进行少量改动,但是不影响子系统独立开发部署及其功能