umijs / qiankun

📦 🚀 Blazing fast, simple and complete solution for micro frontends.
https://qiankun.umijs.org
MIT License
15.72k stars 2.01k forks source link

关于样式隔离的疑问——开启沙箱后,子应用中挂载到ducument.body的组件样式无法生效 #1316

Closed PickACatName closed 3 years ago

PickACatName commented 3 years ago

关于样式隔离的疑问——开启了沙箱后,当子应用中使用了类似element的dialog,drawer等挂在document.body上的组件时,子应用中组件内嵌页面的样式无法生效,有什么好的解决方案么?

poorli commented 3 years ago

image

https://qiankun.umijs.org/zh/api api 里面对样式基于 ShadowDOM 的严格样式隔离有说明

首先说下为什么不生效: 你说的 dialog 组件 DOM 是挂载在主应用的 document.body 但是样式加载在子应用的 ShadowDOM 里面,ShadowDOM 里的样式不会在主应用中生效

如何解决: 你需要看下组件能否挂载在子应用的 DOM 元素上,不要挂载在 document.body

vant 的 toast 有类似能力

import { Toast } from 'vant';

Vue.use(Toast);

Toast.setDefaultOptions({ getContainer: () => container}); // 挂载在子应用的 container 上
PickACatName commented 3 years ago

好的,感谢,已解决

haitaodesign commented 3 years ago

好的,感谢,已解决

你的解决方案是什么,element-ui 的?

PickACatName commented 3 years ago

好的,感谢,已解决

你的解决方案是什么,element-ui 的?

弹窗抽屉之类的组件使用了v-transfer-dom指令挂载到子应用的DOM元素上,像el-popover之类的组件目前暂时用了行内样式

realhao commented 3 years ago

解决思路:ShadowDOM + popup 类组件挂载到子应用某个节点里 解决步骤: 1、主应用 vue2,开启 ShadowDOM 沙箱

start({
    sandbox: {
        strictStyleIsolation: true,
    }
})

2、子应用 public/index.html,增加容器节点

<div id="microAppPopup"></div>

3、子应用 vue3,入口暴露生命周期获取子应用 container

export async function mount(props: any) {
  const { container } = props;
  const app = await startup(props, container);
  if (app) {
    app.config.globalProperties.mainAppConfig = props;
  }
}

4、App.vue 里查询子应用的 popup 类组件容器节点

<template>
  <a-config-provider :locale="locale" :getPopupContainer="getPopupContainer">
    <router-view />
  </a-config-provider>
</template>
<script>
import { getCurrentInstance } from 'vue';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
export default {
  setup() {
    const getPopupContainer = (el) => {
      const vueActiveInstance = getCurrentInstance();
      if (!vueActiveInstance) {
        console.log(`未找到 vue 激活实例,使用 document.body`);
        return document.body;
      }
      const { appContext } = vueActiveInstance;
      const didaConfig = appContext.app?.config?.globalProperties?.mainAppConfig;
      const container = didaConfig?.mainAppConfig?.container;
      console.log('getPopupContainer', el, container);
      if (typeof container?.querySelector === 'function') {
        const wrap = container?.querySelector('#microAppPopup');
        console.log('getPopupContainer', wrap);
        return wrap;
      }
      console.log('getPopupContainer 使用 document.body');
      return document.body;
    };
    return {
      locale: zhCN,
      getPopupContainer,
    };
  },
};
</script>
lovedaan commented 2 years ago

点击date-pinker文本框获得焦点,date-picker面板会闪动然后马上消失,不知道什么原因,审查元素看date-picker元素被添加到父应用的body同层,但是即使我代理document.body的appendChild方法到子应用的容器内,还是有这个闪动问题,感觉跟楼主这个问题不是同一个

Ghjsw commented 2 years ago

解决思路:ShadowDOM + popup 类组件挂载到子应用某个节点里 解决步骤: 1、主应用 vue2,开启 ShadowDOM 沙箱

start({
    sandbox: {
        strictStyleIsolation: true,
    }
})

2、子应用 public/index.html,增加容器节点

<div id="microAppPopup"></div>

3、子应用 vue3,入口暴露生命周期获取子应用 container

export async function mount(props: any) {
  const { container } = props;
  const app = await startup(props, container);
  if (app) {
    app.config.globalProperties.mainAppConfig = props;
  }
}

4、App.vue 里查询子应用的 popup 类组件容器节点

<template>
  <a-config-provider :locale="locale" :getPopupContainer="getPopupContainer">
    <router-view />
  </a-config-provider>
</template>
<script>
import { getCurrentInstance } from 'vue';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
export default {
  setup() {
    const getPopupContainer = (el) => {
      const vueActiveInstance = getCurrentInstance();
      if (!vueActiveInstance) {
        console.log(`未找到 vue 激活实例,使用 document.body`);
        return document.body;
      }
      const { appContext } = vueActiveInstance;
      const didaConfig = appContext.app?.config?.globalProperties?.mainAppConfig;
      const container = didaConfig?.mainAppConfig?.container;
      console.log('getPopupContainer', el, container);
      if (typeof container?.querySelector === 'function') {
        const wrap = container?.querySelector('#microAppPopup');
        console.log('getPopupContainer', wrap);
        return wrap;
      }
      console.log('getPopupContainer 使用 document.body');
      return document.body;
    };
    return {
      locale: zhCN,
      getPopupContainer,
    };
  },
};
</script>

element-ui的 应该怎么解决呢

songyazhao commented 2 years ago

vue2版本,element-ui,我暂时通过以下方式解决了,应该也适用于其他UI组件库

const proxy = ({ container }) => {
  if (document.body.appendChild.__isProxy__) return
  revocable = Proxy.revocable(document.body.appendChild, {
    apply(target, thisArg, [node]) {
      if (container) {
        container.appendChild(node)
      } else {
        target.call(thisArg, node)
      }
    }
  })
  document.body.appendChild = revocable.proxy
  document.body.appendChild.__isProxy__ = true
}

export const mount = async props => {
  proxy(props)
  ...
}

export const unmount = async () => {
  ...
  revocable.revoke()
  revocable = null
}
litandota commented 2 years ago

vue2版本,element-ui,我暂时通过以下方式解决了,应该也适用于其他UI组件库

const proxy = ({ container }) => {
  if (document.body.appendChild.__isProxy__) return
  revocable = Proxy.revocable(document.body.appendChild, {
    apply(target, thisArg, [node]) {
      if (container) {
        container.appendChild(node)
      } else {
        target.call(thisArg, node)
      }
    }
  })
  document.body.appendChild = revocable.proxy
  document.body.appendChild.__isProxy__ = true
}

export const mount = async props => {
  proxy(props)
  ...
}

export const unmount = async () => {
  ...
  revocable.revoke()
  revocable = null
}

这样做后会有报错,请教大佬是为什么呢? image @songyazhao

Caveolaes commented 2 years ago

点击其他的路由,再点击回子应用的路由,会报错,页面为空白,重新刷新页面才可以

Ec2-slj commented 1 year ago

好的,感谢,已解决

你的解决方案是什么,element-ui 的?

弹窗抽屉之类的组件使用了v-transfer-dom指令挂载到子应用的DOM元素上,像el-popover之类的组件目前暂时用了行内样式

v-transfer-dom指令的代码方便展示下吗

Rahim-Chan commented 1 year ago

好的,感谢,已解决

你的解决方案是什么,element-ui 的? 要不要尝试我的重写的包 https://github.com/Rahim-Chan/qiankun-rewrite

Rahim-Chan commented 1 year ago

要不要尝试我的重写的包 https://github.com/Rahim-Chan/qiankun-rewrite

要不要尝试我的重写的包,可以完美解决这个问题 https://github.com/Rahim-Chan/qiankun-rewrite

aique127 commented 1 year ago

vue2版本,element-ui,我暂时通过以下方式解决了,应该也适用于其他UI组件库

const proxy = ({ container }) => {
  if (document.body.appendChild.__isProxy__) return
  revocable = Proxy.revocable(document.body.appendChild, {
    apply(target, thisArg, [node]) {
      if (container) {
        container.appendChild(node)
      } else {
        target.call(thisArg, node)
      }
    }
  })
  document.body.appendChild = revocable.proxy
  document.body.appendChild.__isProxy__ = true
}

export const mount = async props => {
  proxy(props)
  ...
}

export const unmount = async () => {
  ...
  revocable.revoke()
  revocable = null
}

你好,这个方式重写body.appendChild方法会把主应用和其它子应用(同时激活多个子应用)的也重写了,导致主应用的弹窗加到了这个子应用里。

gansong99 commented 1 year ago

vue2版本,element-ui,我暂时通过以下方式解决了,应该也适用于其他UI组件库

const proxy = ({ container }) => {
  if (document.body.appendChild.__isProxy__) return
  revocable = Proxy.revocable(document.body.appendChild, {
    apply(target, thisArg, [node]) {
      if (container) {
        container.appendChild(node)
      } else {
        target.call(thisArg, node)
      }
    }
  })
  document.body.appendChild = revocable.proxy
  document.body.appendChild.__isProxy__ = true
}

export const mount = async props => {
  proxy(props)
  ...
}

export const unmount = async () => {
  ...
  revocable.revoke()
  revocable = null
}

你好,这个方式重写body.appendChild方法会把主应用和其它子应用(同时激活多个子应用)的也重写了,导致主应用的弹窗加到了这个子应用里。

你解决了嘛,大佬