无界微前端踩坑记录(WujieVue)

0 阅读4分钟

背景

最近,公司要搞SaaS平台,整合公司的各个项目,要开发一个主应用,用于管理子应用的数据、菜单权限及租户。子应用有多个,可扩展,且子应用可独立部署至客户方。这不就是微前端使用的场景嘛! 于是,我想到了大名鼎鼎的乾坤微前端架构,然后进行一番调研。发现现在用得比较多的微前端除了乾坤,还有腾讯的无界,京东microApp。我又进行了一番调研,最终选择无界微前端框架进行开发。 搭建框架过程中,踩了不少坑,在此记录一下,以免踩的这些坑如过眼烟云,在漫漫人生中忘却。 以下为使用无界的注意事项:

一、各种样式问题及svg图标问题

子应用的样式会出现各种问题,比如:

  • 引入的element plus下拉组件的位置偏移问题
  • 子应用雪碧图加载svg图标,子应用切换导致图标不显示的问题
  • 子应用高度问题
  • 子应用样式问题

解决方案:

配置相关的插件plugins

<WujieVue
      v-if="appData?.url"
      width="100%"
      height="100%"
      :name="appData?.key || 'subApp'"
      :url="getUrl(appData?.url)"
      :props="{
        ...appData,
        userInfo,
        token,
        appName: '子应用--' + appData?.name
      }"
      :plugins="plugins"
      :sync="true"
    />

plugins配置如下:

import { DocElementRectPlugin } from 'wujie-polyfill'

export const plugins = [
  // 使用火狐浏览器时 style依然有问题 引入插件wujie-polyfill
  DocElementRectPlugin(),
  {
    /**
     * 解决样式问题
     * @param element 真正插入的元素
     * @param iframeWindow 子应用的 window
     */
    patchElementHook(element, iframeWindow) {
      if (element.nodeName === 'STYLE') {
        element.insertAdjacentElement = function (_position, ele) {
          iframeWindow.document.head.appendChild(ele)
        }
      }
    }
  },
  {
    // 在子应用所有的css之前,为子应用插入样式
    cssBeforeLoaders: [
      // 强制使子应用body定位是relative
      { content: 'body{position: relative !important}' },
      // 解决子应用高度问题
      { content: 'html{height: 100% !important}' },
      { content: 'body{height: 100% !important}' },
      { content: '#app{height: 100% !important}' },
      // 确保微应用容器高度
      { content: '.wujie_app{height: 100% !important}' },
      { content: '.wujie_app iframe{height: 100% !important}' }
    ]
  },
  {
    // 解决子应用计算偏移量错误问题
    jsLoader: (code: any) => {
      // 替换popper.js内计算偏左侧偏移量
      const codes = code.replace(
        'left: elementRect.left - parentRect.left',
        'left: fixed ? elementRect.left : elementRect.left - parentRect.left'
      )
      // 替换popper.js内右侧偏移量
      return codes.replace('popper.right > data.boundaries.right', 'false')
    }
  },
  {
    // element 为真正插入的元素,iframeWindow 为子应用的 window, rawElement为原始插入元素
    /**
     * 解决子应用雪碧图加载svg图标,子应用切换导致图标不显示的问题
     * @param element 真正插入的元素
     * @param iframeWindow 子应用的 window
     */
    appendOrInsertElementHook(element, iframeWindow) {
      if (
        element.nodeName === 'svg' &&
        (element.getAttribute('aria-hidden') === 'true' ||
          element.style.display === 'none' ||
          element.style.visibility === 'hidden' ||
          (element.style.height === '0px' && element.style.width === '0px'))
      ) {
        iframeWindow.__WUJIE.styleSheetElements.push(element)
      }
    }
  }
]

二、子应用静态资源引入问题

由于子应用的静态资源是在主应用中渲染的,因此不能用相对路径,否则就有可能会因路径找不到而报错。

解决方案:

以全路径引入,封装方法,返回静态资源的路径。独立运行和作为子应用时的路径是不同的

/**
 * 无界微应用中获取绝对地址
 * @param path 相对地址,必须使用import导入,打包才会编译
 * @returns 绝对地址
 */
export const getAssetUrl = (path) => {
  const baseUrl = window.__WUJIE_PUBLIC_PATH__ || window.location.origin + '/' || ''
  return `${baseUrl}${path.startsWith('/') ? path.substring(1) : path}`
}

// public下的静态资源可以直接这样引入
getAssetUrl(`/static/QWeather-Icons-1.8.0/icons/${name}.svg`)

// assests下的静态资源需要先imoport,再引入。因为会经过webpack或vite打包
import correctTitleBg from '@/assets/imgs/price/predict-correct.png'
const titleStyle = {
  background: `url('${getAssetUrl(correctTitleBg)}') no-repeat`
}

需要注意的是:vite在打包的时候会自动转换为二进制,因此需要修改vite配置,禁止在打包时转换

image.png

三、子应用持久化的缓存数据与主应用冲突

因为持久化时localstorage中的key可能会相同,导致冲突而引入错误。

解决方案:

给子应用持久化的配置设置key值,key的来源可以是主应用调用子应用时传传过来的key。独立运行时可不配置

如用pinia时,配置persist的key

persist: {
    key: getAppKey() + '-common',
    paths: ['region', 'userInfo']
}
  
/**
 * 获取app唯一键值,用于作为微应用接入主应用时使用
 * @returns
 */
export function getAppKey() {
  // 无界微前端参数
  const mainProps = window.$wujie?.props
  return mainProps?.key || import.meta.env.VITE_APP_UNI_KEY || 'sub_app'
}

四、echarts引入图标的问题

echarts通过静态资源的方式引入图标,渲染没有问题。但是使用echarts工具导出图片就会报错,无法导出。

Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

报错原因:

  1. 图片跨域:ECharts 绘制的图表中如果使用了跨域图片(如通过 graphic 元素插入图片,或系列中的 image 类型标记),这些图片被绘制到 canvas 后,canvas 会被标记为“被污染”(tainted)。一旦 canvas 被污染,任何读取其像素数据的操作(如 toDataURLgetImageData 等)都会被禁止。
  2. 微前端环境的跨域特性:无界微前端可能使用 iframe 或 Web Components 来隔离子应用。如果子应用与主应用不同域,且子应用内部加载的图片资源没有正确的跨域头,canvas 就会继承子应用的跨域状态,导致污染。
  3. ECharts 内部机制:ECharts 默认使用 canvas 渲染,当图表中包含图片(例如地图背景、自定义系列图片)时,这些图片的加载默认不会设置 crossOrigin 属性,如果图片跨域且服务器未返回正确的 CORS 头,就会污染 canvas。

解决方案: 将图标转为二进制代码或svg字符串进行引入

其他相关问题可参考官网