背景
最近,公司要搞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配置,禁止在打包时转换
三、子应用持久化的缓存数据与主应用冲突
因为持久化时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.
报错原因:
- 图片跨域:ECharts 绘制的图表中如果使用了跨域图片(如通过
graphic元素插入图片,或系列中的image类型标记),这些图片被绘制到 canvas 后,canvas 会被标记为“被污染”(tainted)。一旦 canvas 被污染,任何读取其像素数据的操作(如toDataURL、getImageData等)都会被禁止。 - 微前端环境的跨域特性:无界微前端可能使用 iframe 或 Web Components 来隔离子应用。如果子应用与主应用不同域,且子应用内部加载的图片资源没有正确的跨域头,canvas 就会继承子应用的跨域状态,导致污染。
- ECharts 内部机制:ECharts 默认使用 canvas 渲染,当图表中包含图片(例如地图背景、自定义系列图片)时,这些图片的加载默认不会设置
crossOrigin属性,如果图片跨域且服务器未返回正确的 CORS 头,就会污染 canvas。
解决方案: 将图标转为二进制代码或svg字符串进行引入