记一次 - qiankun搭建微前端之踩坑大全

5,438 阅读1分钟
- 项目中用到的版本号
- "vue": "^3.0.0"
- "vue-router": "^4.2.4"
- "vuex": "^4.1.0"
- "webpack": "^5.88.1"
- "webpack-cli": "^5.1.4"
- "webpack-dev-server": "^4.15.1"
- "@vue/babel-plugin-jsx": "^1.1.5"
- "@vue/cli-plugin-eslint": "~4.5.14"
- "@vue/cli-service": "^5.0.8"
- "@vue/compiler-sfc": "^3.3.4"

1、application 'microChild' died in status UNMOUNTING: instance.$destroy is not a function

  • 问题描述:在初始化项目之后,离开子应用报错
  • 原因:创建vue实例之后,往实例挂在三方插件,直接导出一个变量,此变量不是vue实例,导致原型链上没有此方法
/** 正确写法 */
let instance = null;
function render(props = {}) {
  const { container } = props;
  instance = createApp(App); // 这样才是创建了vue实例,此后才是往vue实例上挂载三方插件
  instance.use(router).use(store).mount(container ? container.querySelector("#app") : "#app");
}


export async function unmount(props) {
  instance.unmount(); // 销毁实例 vue3命名为unmounted vue2命名为destroyed
}

2、[qiankun] Set window.VUE_DEVTOOLS_HOOK_REPLAY while sandbox destroyed or inactive in microChild!

qiankun 报警告:Set window.VUE_DEVTOOLS_HOOK_REPLAY while sandbox destroyed or inactive in microChild! 
(设置窗口。VUE_DEVTOOLS_HOOK_REPLAY,而沙盒销毁或在microChild中不活动!)
子应用是vue时的警告,会影响调试,不影响运行 

3、子应用的样式影响了主应用 - 样式隔离

start({
  sandbox: {
    experimentalStyleIsolation: true
  }
});

- qiankun 提供的一个实验性的样式隔离特性
- 改写后的样式代码结构如下:div[data-qiankun="microChild"] #app

4、application 'microChild' died in status LOADING_SOURCE_CODE: [qiankun]: Target container with #microContainer not existed while microChild loading!

  • 问题描述:当把加载子应用的div没有放在根路由的页面下,就会报这个错
意思是在加载子应用时,没有找到注册的 container

1、可能 id 的名称与注册的不一致
    <div id="microContainer" />
    
    const microAppConfig = [
      {
        name: 'microChild', // 唯一性,可用微应用的名称
        entry: MICRO_CHILD, // 
        container: '#microContainer', // 微应用挂载的dom
        activeRule: '/microChild', // 触发匹配微应用的路由规则
      },
    ];
    
2、可能 start 的时机不对
    应该在匹配子应用路由的页面的 onMounted(() => {}})) 中调用

5、qiankun 在主应用的某个路由页面渲染子应用

### 主应用
1、注册路由
{
    // vue-router4.x 写法
    path: '/microChild/:pathMatch(.*)',// 通配符,访问 microChild 这个路由的渲染到这个页面
    name: '子应用的渲染页面',
    component: () => import('@/pages/layout.vue'), // layout是我的布局页面,渲染router-view和子应用div
}

2、layout.vue
    <router-view></router-view>
    <!-- 子应用渲染容器 -->
    <section id="microContainer"></section>
    
    import { start } from "qiankun";
    onMounted(() => {
        if (!window.qiankunStarted) {
            window.qiankunStarted = true
            start()
          }    
    }))
    
    
### 子应用
export default createRouter({
  history: createWebHistory(),
  base: "/microChild", // 与主应用配置一致
  routes
})

7、主、子应用切换频繁后,加载子应用失败(qiankun主应用访问子应用白屏)

1、A plugin must either be a function or an object with an "install" function (插件要么是一个函数,要么是一个带有“install”函数的对象) 2、Failed to resolve component: router-view If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement (日志含义解析组件:router-view失败 如果这是一个本地自定义元素,请确保通过compilerOptions.isCustomElement将其从组件解析中排除)

  • 怀疑是子应用 加载/卸载 的问题,导致子应用初始化挂载页面失败
  • 在子应用导出的生命周期函数中卸载了应用解决了此问题
export async function unmount(props) { instance.unmount(); }

8、[Vue warn]: There is already an app instance mounted on the host container.

If you want to mount another app on the same host container, you need to unmount the previous app by calling app.unmount() first

([Vue warning]:主机容器上已经挂载了一个应用实例。

如果你想在同一个主机容器上挂载另一个应用,你需要先通过调用app.unmount()来卸载前一个应用)

  • 我认为这个问题同 #issue7

9、主应用和子应用的通信问题,通信隔离

  • 问题描述:
  • 1、主应用在vuex中存储公共数据,(如登录信息),在微前端环境,子应用只使用vuex中的登录信息
  • 2、子应用独立运行时,用自己的vuex存储登录信息
  • 3、公共数据只允许在主应用进行修改
  • 4、子应用通过vuex获取主应用的实时数据
主/子应用防止vuex刷新页面数据丢失

const sessionLoginInfo = sessionStorage.getItem("loginInfo");

if (sessionLoginInfo) {
store.replaceState(
  Object.assign({}, store.state, {
    loginInfo: JSON.parse(sessionLoginInfo),
  })
);
}

window.addEventListener("beforeunload", () => {
    sessionStorage.setItem("loginInfo", JSON.stringify(store.state.loginInfo));
});

  • 主应用
1、登录后存储登录数据
store.commit("SAVE_LOGIN_INFO", loginInfo);
sessionStorage.setItem("loginInfo", JSON.stringify(loginInfo));
2、传递数据给子应用
impor { registerMicroApps } from 'qiankun';
import store from "@/store"
registerMicroApps(
    [
        {
            name: 'microChild', // 唯一性,可用微应用的名称
            entry: MICRO_CHILD, // 
            container: '#microContainer', // 微应用挂在的dom
            activeRule: '/microChild', // 触发匹配微应用的路由规则
            props: { // 提供给子应用的数据
              store, // 将主应用的store实例传给子应用
            }
          }    
    ]
)
  • 子应用
main.js
1、在子应用加载完成的生命周期中,获取主应用传递的数据
2、并存在自己本地的vuex中,(此处可以创建从主应用获取的共享数据的状态管理 和 子应用私有的状态管理, 主应用的状态管理不建议子应用直接修改)

export async function mount(props) {
  props.onGlobalStateChange(state => (state, props) => {
      // state: 变更后的状态; prev 变更前的状态
      console.log('子应用接收数据props-----', props);
      console.log('子应用接收数据state-----', state);
      // 从主应用接收过来的store实例
      const parentStore = props.store.getters.getLoginInfo;
      store.commit("SAVE_LOGIN_INFO", parentStore)  
  }), true);
  render(props);
}

store.js
const store = createStore({
  state: {},
  getters: {},
  mutations: {},
  modules: {
    base
  }
})

base.js
// 从主应用传递的公共数据,建议不可修改
export const base = {
  state: {
    loginInfo: {}
  },
  getters: {
    getLoginInfo(state) {
      return state.loginInfo
    }
  },
  mutations: {
    SAVE_LOGIN_INFO(state, val) {
      state.loginInfo = val;
    }
  }
}

10、使用 用户中心系统作为子系统访问时,控制台报错 [qiankun] lifecycle not found from microChild entry exports, fallback to get from window['microChild']

  • 从microrchild入口导出中找不到生命周期,回退到从窗口[' microChild ']获取
  • 找不到导出的qiankun的生命周期,但是在 微前端项目 中 入口main.js 中有 export 生命周期

11、访问 《微前端项目》window上挂在的子应用名称为 undefined [与 10 的问题一样]

image.png

12、访问子应用报错 Uncaught (in promise) TypeError: Failed to fetch

  • 访问资源的网络问题,应该是 index.html 中通过script 引入了第三方应用导致的,去掉就可以了

13、主应用传给子应用的数据进行本地缓存,vuex 状态管理,在子应用的当前页面刷新,数据丢失的问题。该如何解决更好呢?

  • 目前的解决办法是,主应用缓存自己的数据,通过 qiankun api 传递给微应用,在微应用存储自己的数据

14、qiankun子应用中http请求接口404的问题?

  • 在 主应用 中配置子应用的代理
  • 以 子应用为 microChild 为例
- 主应用配置
proxy: {
  // microChild 是用户中心的匹配规则
  ["/microChild/api"]: {
    changeOrigin: true,
    target: "http://microChild.host.cn/api",
    pathRewrite: {
      ['^' + "/microChild/api"]: '',
    },
  },
},

- 子应用配置
const getAbsoluteValueForTuple = () => {
  if (window.__POWERED_BY_QIANKUN__) {
    return process.env.VUE_APP_BASE_URL + API_HOST
  }
  return API_HOST
}

axios.defaults.baseURL = getAbsoluteValueForTuple();

15、qiankun子应用,1)element组件库的一些弹窗组件挂载到body上的组件样式受到了影响?2)在【微应用】子应用中设置换肤失效的问题?

  • 在 qiankun 环境,微应用中获取到的 window 对象是主应用中的,所以在 qiankun 环境下,建议从 document 去查找文档对象

16、 qiankun子应用在 index.html 引入第三方js 库,导致加载报错的问题?

image.png

  • 原因:qiankun是通过 import-html-entry 劫持 script 标签,把 script 标签注释掉,通过fetch 请求资源,这样导致了子应用请求这些 script 标签产生加载失败的问题
- 解决方案
start({
    // 自定义的 fetch 方法, 遇到指定的第三方库不做拦截
    async fetch(url, ...args) {
      console.log("fetch------url:", url, "args--------", ...args)
      if (url === "http://localhost:8080/echarts.min.js") {
        return {
          async text() {
            return ""
          }
        }
      }
      return window.fetch(url, ...args);
    },
})

16、qiankun 子应用 window的值有变化?

image.png

17、qiankun 环境,刷新子应用页面,热更新失效,报错?

  • 主、子应用端口不同,导致子应用拿不到.hot-update.json 文件,导致热更新失败
  • 可以在子应用中配置如下:
publicPath: process.env.NODE_ENV === 'development' ? "http://localhost:8080/": "/",

18、qiankun环境中【用户中心】在当前路由,例如/uc/uc001 刷新页面,报错

  • 背景:主应用开启了严格的样式隔离模式
sandbox: { 
    strictStyleIsolation: true, // 开启严格的样式隔离模式 
},
  • 官方说:基于 ShadowDOM 的严格样式隔离并不是一个可以无脑使用的方案,大部分情况下都需要接入应用做一些适配后才能正常在 ShadowDOM 中运行起来
  • 设置为true后的报错:子应用通过getElementById 找不到 DOM

image.png

  • 暂时解决:把这个配置设置为false即可正常显示子应用
  • 原因:qiankun 环境的 strictStyleIsolation 开启时,这种模式下 qiankun 会为每个微应用的容器包裹上一个 shadow dom 节点,从而确保微应用的样式不会对全局造成影响。但这不是无脑解决方案,需要在应用中做一些适配才能这个奶茶给你的在 shadow DOM 中运行。
  • shadow DOM
    • 这个链接是对此的解释

developer.mozilla.org/zh-CN/docs/…

    • shadow DOM 可以将一个隐藏的、独立的DOM附加到一个元素上,来完成标记结构、样式、行为隐藏起来。

19、vue-router4.x 在全局路由守卫中,动态创建路由,解决路由跳转死循环问题

  • 在全局路由守卫,在跳转路由的时候都会触发回调。
  • vue-router4.x中,可以通过返回值蓝控制,不推荐使用next,是因为在开发中很容易多次调用next函数
  • 在创建完路由表之后,直接跳转路由。反之,先创建路由表
  • 我这里是把路由表的数据存储到了 vuex 中
router.beforeEach((to) => {
    const routeRaw = store.getters.getRouterRaw;
    // 判断是否有路由表缓存
    if (routeRaw) {
      return true
    } else {
      // 创建路由表的方法
      setRouterDataInQiankun()
      if (hasNecessaryRoute(to)) {
        return to.fullPath
      }
    }
})

// 判断路由表中是否有跳转的路由
function hasNecessaryRoute(to) {
  const routerList = router.getRoutes();
  const routerItem = routerList.find(i => i.path === to.fullPath)
  if (routerItem) {
    return router.hasRoute(routerItem.meta.name)
  }
  return false
}

20、子应用,动态路由刷新当前页面,渲染页面失败问题(主应用菜单首次点击跳转子应用路由成功,再次点击跳转子应用该页面路由跳转失效)?

  • 这是 Vue-router 动态路由表的问题
  • 是因为把动态路由存储到了 vuex 中,刷新页面,vuex数据丢失
  • 要解决这个问题,必须在刷新页面之后重新执行添加路由这一步骤
- 主应用
1. 切换路由时,存储当前激活路由到 vuex,传递给子应用

- 子应用

// 解决刷新页面空白问题
const getRouterRaw = this.$store.getters.getRouterRaw;
const currentActiveMenu = this.$store.getters.getActivateMenu;
// 1. 当前无路由表 -〉 创建路由表
// 2. 当前存在激活路由 -> 手动跳转激活路由
if (!getRouterRaw) {
    setRouterDataInQiankun()
    // 当前激活路由
    if (currentActiveMenu) {
      this.$router.push({
        path: currentActiveMenu.currentActivePath,
        params: currentActiveMenu
      });
    }
}