Micro-app踩坑记录

36 阅读9分钟

Micro-app总结

它借鉴了WebComponent的思想,模拟实现了ShadowDom的隔离特性,并结合CustomElement将微前端封装成一个类WebComponent组件

Micro-app文档

优点

1.每个应用都可以独立开发、独立运行、独立部署,降低耦合性

后续的维护复杂度较低

部署的灵活度更高

2.不用考虑技术框架

对兼容旧项目比较友好,可以降低开发成本

3.可扩展性好

可以在基座上新增其他子应用,以完成对整个项目的增量更新

缺点

1.基座和子应用对路由模式的选择不是完全的兼容

0.x版本中

基座用History模式,子应用可以用History,Hash

基座用Hash模式,子应用只能用Hash

Hash模式下子应用都要做相应的配置

1.x版本中

找不到相关描述,使用Hash模式基座,可以跳转Hash和History的子应用

2.静态资源的引入有时不会生效

一般情况下都会根据子应用路径自动资源路径补全

一般可生效的是引入link形式的JS,CSS;img标签

动态加载的图片不会自动补全

img标签为 [src]=''; `:src='' 这些都无法自动资源路径补全

3.基座和子应用之前的鉴权共享需要单独配置

子应用单独部署时,需要登录

作为子应用时,应该是基座登录一次,子应用不用再次登录

基座和子应用都为新项目时,整体架构重新设计即可

如果有一方为旧项目,新项目的鉴权可能需要为旧项目让步

4.基座和子应用的菜单展示需要单独配置

子应用单独部署时,需要展示菜单信息

作为子应用时,应该是基座展示菜单,而子应用不用

需要考虑的

基座和子应用共用一套缓存

用一套Local Storage,Session Storage

好处是鉴权可以尽量统一使用一个对象

坏处是每个系统之间的变量可能会混淆

对子应用的改造

登录

使用微前端后,基座和子应用共用一套内存,所以鉴权认证相关的本地存储使用一套即可

菜单权限

基座需要指定的路径前缀来判定当前的路由属于哪个子应用

  1. 根据指定路径前缀导航到基座里指定的子应用入口

    /iamss为例,所有带/iamss的路由都会跳到这个iamss-app组件内

    {
        path: "/iamss/:page*",
        name: "MILAMSS_IAMSS-LAYOUT",
        component: () => import("../layout/business.vue"),
        children: [
          {
            path: "",
            name: "MILAMSS_IAMSS",
            component: () => import("../pages/iamss-app/index.vue"),
          },
        ],
    }
    
  2. 在子应用入口文件内操作子应用路由

    官方给出了虚拟路由跳转的方式,此处用基座控制子应用跳转

    监听路由中的params,使用microApp.router.push()跳转

导航&头部

子应用单独部署时,正常展示菜单、头部等信息

作为子应用展示时菜单、头部等信息要隐藏

使用环境变量做判断

if (window.__MICRO_APP_ENVIRONMENT__) {
  console.log('我在微前端环境中')
}

退出登录

退出登录最好是在基座中控制,子应用中控制容易状态不同步,出现其他问题

如果只能在子应用中控制退出登录,可以发送消息给基座,同步信息

子应用:数据通信-子应用向主应用发送数据

基座:数据通信-主应用获取来自子应用的数据

部署

子应用必须支持跨域

在开发环境下,需要配置跨域

同样的,部署到服务器上也要支持跨域

需要在Nginx配置中添加相应的跨域请求头

可以参考官方文档:配置跨域

参考代码:

location / {
    # 指定访问的静态文件夹
    root   /app/www;
    # 指定index页面
    index  index.html index.htm;
    # add_header Cache-Control;
    add_header Access-Control-Allow-Origin *;
    # 这个字段指定当前uri 访问的文件顺序,
    # 即uri没有的话就访问uri/ 依次到index.html、404.配置这个字段的原因是angualr项目是单页应用,所以所有的uri都访问index.html
    try_files $uri $uri/ /index.html =404;
}

关于路由模式

官方文档:虚拟路由系统

在0.x版本中,基座和子应用对Hash和History的路由模式有约束

但是在1.x版本中,没有相关约束了,所以基座使用Hash,子应用使用Hash或者History都可以

使用search路由模式就可以

关于预加载

官方文档:预加载

如果出现了加载缓慢的情况,可以开启预加载,预加载会在空闲时间进行

如果使用的话,一般预加载等级为2就可以

首屏加载性能优化除了基座对子应用的预加载,还可以优化子应用url对应的页面

将页面做到轻量化

遇到的问题

静态资源的引入失效

问题出现

一般情况下都会根据子应用路径自动资源路径补全

一般可生效的是引入link形式的JS,CSS;img标签

动态加载的图片不会自动补全

img标签为 [src]=''; :src='' 这些都无法自动资源路径补全

解决方案

  1. 在动态加载的路径前面加上IP端口号
  2. 在动态在家的路径前加上指定前缀,基座做代理

子应用部分样式高度塌陷

问题出现

使用百分比高度或者使用calc计算高度的DOM,有时候会获取不到,导致高度为0

解决方案

逐层检查,将塌陷的外部容器指定一个确定的高

Angular子应用路由跳转页面报404

问题出现

子应用内直接点击菜单跳转,页面报404,只有江苏旧系统有这个问题

是旧系统引入了error-tips库,库内部报错导致页面导航失败

解决方案

统一在基座操作子应用路由

在Angular子应用页面与基座页面切换,子应用显示异常

问题出现

从子应用页面切换到基座,再切换到子应用,子应用会显示默认的页面,而不是指定页面

路由未跳转,中间经历了一次子应用卸载和挂载

解决方案

切换到基座会销毁Micro-app组件,所以会发生卸载,再次进入子应用时,会重新挂载,是正常流程

没有进入到指定页面是因为默认展示URL的页面,如果设置了default-page则会展示default-page

跳转到指定页面需要使用Micro-app的路由,基座控制子应用跳转页面

PS: 长时间不切换页面,再次切换子应用页面,跳转失效,目前没有找到有效方法

在Angular子应用中退出登录报错:NgDevModel不存在

问题出现

子应用操作退出时,控制台报错NgDevModel不存在,只有江苏旧系统有这个问题

Session Storage中的back_url是子应用内部路由,退出登录又是子应用引入的@yz-business/manage-center的操作,如果back_url是基座的路径就没问题

解决方案

back_url由内部路由修改为基座登录页面

子应用开启导航恢复导致页面跳转错误

问题出现

在基座中控制子应用跳转页面,子应用会跳转到导航恢复指定的页面,导致页面跳转错误

解决方案

在基座控制子应用跳转和子应用导航恢复之间对比优先级,子应用导航恢复优先级

在子应用导航恢复应用处添加判断,只有不在微前端环境下且开启了导航恢复才回进行恢复

从而保证作为子应用和单独部署不出问题

在Angular子应用中导出、下载功能报错

问题出现

涉及到超链接a标签,a.download的代码在子应用中,可能都会遇到此类报错

a标签下载资源时会根据现有Window对象创建MouseEvent,即点击事件的event参数

在子应用情况下,获取的window并不是真的window,所以会报错提示转换失败

目前遇到的场景:使用打印视图导出、yz-ngx-grid的导出、ECharts的下载都会报错

Uncaught TypeError: Failed to construct 'MouseEvent': Failed to read the 'view' property from 'UIEventInit': Failed to convert value to 'Window'.

解决方案

1.使用iframe沙箱
  • 使用iframe沙箱后,Angular子应用的浮窗组件:下拉框、日期选择框都会出现卡顿和部分失效的问题,视图更新问题频发
  • 同样的组件在Vue中没有出现,目前公司开发的Vue项目都是由Vite构建,必须使用iframe沙箱才可以作为子应用运行
2.下载方法使用dispatchEvent

图片下载和表格导出都可以参考方法重写,因为好多都是使用的a.download()

转换a.click()的触发方式

const JPG_ELE = document.createElement('a');
JPG_ELE.download = 'print.jpg';
JPG_ELE.href = 'xxxxx'
JPG_ELE.click();
// 判断是否在Micro-app微前端环境下
if ((window as any).rawWindow && (window as any).__MICRO_APP_ENVIRONMENT__) {
  // 如果是,则使用rawWindow的dispatchEvent方法触发点击事件
  JPG_ELE.dispatchEvent(
    new MouseEvent('click', {
      bubbles: false,
      cancelable: false,
      view: (window as any).rawWindow,
    }),
  );
} else {
  // 如果不是,则使用click方法触发点击事件
  JPG_ELE.click();
}
3.ECharts等封装好的包

ECharts源码中做了相应的容错,但是依旧报错

Micro-app的真实Window在window.rawWindow下,ECharts获取到的类型不是Window,而是CustomWindow,导致转换失败

在ECharts中也是同样的操作,但ECharts是封装好的包,所以需要给ECharts打个补丁

我们使用patch-package为ECharts@5.3.3打补丁

这样每次安装ECharts@5.3.3的时候都会应用该补丁

如果升级了ECharts,需要重新打补丁

  1. 修改现有node_modules/echarts的源码
// 原来的版本,从v5.0.1到最新的v6.0.0版本,这里的window获取都这样
var $a = document.createElement('a');
$a.download = title + '.' + type;
$a.target = '_blank';
$a.href = url;
var evt = new MouseEvent('click', {
    // some micro front-end framework, window maybe is a Proxy
    view: document.defaultView,
    bubbles: true,
    cancelable: false
});
$a.dispatchEvent(evt);

window的获取修改为micro-app适配的

var $a = document.createElement('a');
$a.download = title + '.' + type;
$a.target = '_blank';
$a.href = url;
const WINDOW_MICRO_APP = window.rawWindow || document.defaultView;
var evt = new MouseEvent('click', {
    // some micro front-end framework, window maybe is a Proxy
    view: WINDOW_MICRO_APP,
    bubbles: true,
    cancelable: false
});
$a.dispatchEvent(evt);
  1. 安装patch-package Node14.x,Node16.x需要安装6.x
npm install patch-package@6.5.1 --save-dev
  1. 创建补丁文件
npx patch-package echarts
  1. 修改 package.json 在 package.json 中添加 postinstall 脚本:
{
  "scripts": {
    "postinstall": "patch-package",
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "lint": "ng lint"
  }
}

5.测试补丁应用

# 验证补丁是否应用
npm run postinstall

在Angular子应用ng-zorro-antd级联选择框点击失效

问题出现

级联选择框只能选择到第一层,无法显示和点击第二层

解决方案

将zone.js的引入由组件中引入更改为main.ts中引入

建议zone.js的引入保持在main.ts中,如果报错则可以尝试放入到组件中

可以探讨的

Micro-app对Angular的支持并不是很完善

在官方的相关议题下,Angular的解决方案寥寥无几

尤其是Iframe沙箱下,视图更新情况不是很理想