项目背景
主应用 vue2 + element;
子应用 vue3 + element-plus;
问题描述
主应用使用element而在子应用中使用element-plus,导致出现两个问题:
- 子应用中element-plus的样式和主应用中element的样式同名,导致被样式覆盖
- 由于像对话框等组件都是默认放在body下面的,所以会被放在主应用的body下而不是子应用的body下导致样式出现偏差
问题一场景复现
主应用触发
子应用触发
问题二场景复现
子应用的组件绑定到了主应用的body中
针对问题一的解决思考
使用严格沙箱
使用
在加载子应用时,添加strictStyleIsolation: true
属性,实现形式为将整个子应用放到Shadow DOM
内进行嵌入,实现主子应用完全隔离
js
start({
sandbox: {
strictStyleIsolation: true,
},
})
问题
vue3项目根本无法正常运行
问下chatGPT吧
既然如此就下发解决一下吧
js
// 主
<div id="subapp-viewport"></div>
const apps = microApps.map(item => {
return {
...item,
container: '#subapp-viewport', // 子应用挂载的div
props: {
routerBase: item.activeRule, // 下发基础路由
getGlobalState: store.getGlobalState // 下发getGlobalState方法
}
}
})
// 子
function render(props){
const { container } = props
app = createApp(App)
app.use(store)
app.use(router)
debugger
app.mount(container ? container.querySelector('#app') : '#app')
}
但是子应用的弹窗、抽屉、popover因找不到主应用的body会丢失,或跑到整个屏幕外
原因:element-plus 的全局变量是通过 :root
选择器来作用到根节点的,但是在 shadow dom 中,是无法通过 :root
来选中根节点的,也就导致了这部分的样式失效 解决:在 shadow dom 中也是存在根节点的,这个根节点名为 shadow host ,我们可以用 :host
来代替 :root
选中 shadow dom 对应的根节点 方案:写一个 loader,在 webpack 打包文件的时候把 :root
替换成 :host
js
const replaceStyleLoader = function (source) {
return source.replace(/:root/g, ':host')
}
module.exports = myStyleLoader
js
module: {
rules: [
{
test: /\.scss$/,
include: [
path.resolve(__dirname, 'node_modules/element-plus/theme-chalk')
],
use: [
'style-loader',
'css-loader',
'replace-style-loader',
'sass-loader'
]
}
]
}
还有一个无法解决的问题:子应用的弹窗、抽屉、popover因找不到主应用的body会丢失或绑定到主应用的body(绑定到body可以理解,丢失真的神奇不过就复现了一两次所以以后在研究吧...)
结论:放弃这种方式,太麻烦而且主应用不方便去修改子应用的样式,还有就是对react不友好
使用实验性沙箱
在加载子应用时,添加experimentalStyleIsolation: true
属性,实现形式类似于vue中style标签中的scoped属性,qiankun会自动为子应用所有的样式增加后缀标签,如:div[data-qiankun-microName]
js
start({
sandbox: {
experimentalStyleIsolation: true,
},
})
也可以解决样式被覆盖的问题但同样无法解决子应用的弹窗、抽屉、popover因为绑定到主应用的body上而导致使用的是主应用的样式的问题
结论:放弃这种方式,无法解决问题二
针对问题二的思考
既然都无法解决问题二,第一个想到的点是实现元素隔离,但是qiankun并不支持该功能,重写的代价太大,所以就只能退而求其次将子应用的样式和class的el-前缀进行转换
思路一
使用element-plus的全局配置修改class和组件的名字(感觉就是主题色替换的思路),但是这里有个问题就是虽然class和组件的名字改变了但图标等个别组件是会通过已经打包好的样式引入的,所以还需要使用postCss插件替换一遍(注意:关闭qiankun的样式沙箱
)
js
// app.vue
<el-config-provider :locale="local" namspace="subel">
<router-view />
</el-config-provider>
// 新建style/element/index.scss
// element前缀样式修改,el改为subel
@forward 'element-plus/theme-chalk/src/mixins/config.scss' with (
$namespace: 'subel'
);
// vue.config.js
module:{
rules:[
{
test: /\.(scss|sass)$/,
exclude: resolve('src/style/element/index.scss'),
include: path.resolve(__dirname, './node_modules/element-plus/'),
use: [
{
loader: "sass-loader",
options: {
implementation: require("sass"),
prependData: `@use "@/style/element/index.scss" as *;`,
}
}
]
}
]
}
// postcss.config.js
const addCssPrefix = require('postcss-change-css-prefix-namespace')
module.exports = {
plugins: [
'postcss-preset-env',
addCssPrefix({
prefix: 'el-',
replace: 'subel-',
}),
],
}
思路二(目前感觉最好的思路了...)
既然都需要postCss转换那我直接转了不就得了。。。。
js
// app.vue
<el-config-provider :locale="local" namspace="subel">
<router-view />
</el-config-provider>
// postcss.config.js
const addCssPrefix = require('postcss-change-css-prefix-namespace')
module.exports = {
plugins: [
'postcss-preset-env',
addCssPrefix({
prefix: 'el-',
replace: 'subel-',
}),
],
}