阿里qiankun微应用:微引用打开微应用,路由记录栈出现undefined问题。

1,640 阅读2分钟

声明

  1. 本文讨论的history.pushState所处场景,仅限于使用阿里 qiankun 微应用框架改造后的主应用环境

    • 因为 vue-router 自身并不监听window.popstate事件(至少vue-router不会对用户手动调用的history.pushState做出响应)
    • 经过 阿里 qiankun 改造后的主应用,会监听 window.popstate,根据变化的 history 记录,动态改变主应用的vue-router记录,并将路由变化分发给各个微应用。
  2. 文中有时会使用主应用和各微应用的代指名称

    • 主应用 :appMain
    • 微应用 :app1app2....
    • 其他微应用 :appX

bug复现场景

  • app1服务开启,
  • app3服务未开启
  • appMain服务开启,加载app1成功,加载app3失败
Uncaught TypeError: application 'app3' died in status LOADING_SOURCE_CODE: Failed to fetch
// or
Uncaught (in promise) TypeError: Failed to fetch

app1页面提示app3加载失败.png

app3提示app3加载失败.png

操作步骤

  • 步骤1:在app1中点击按钮前往 app3。(通过window.history.pushState({},"","/app3")) 因为app3服务未开启,appMain.$router监听到 history栈变化(由乾坤生成的监听,vue本身貌似没有监听这个变化),加载app3失败且同时app1.mount(),乾坤容器呈空白。

  • 步骤2:然后调用appMain.$router.push()添加新的路由,页面没有反应,浏览器history记录也没有变化,控制台会出现关于vueRouter 记录underfined 问题 。

vue-router.esm-bundler.js?6c02:72 
[Vue Router warn]: Error with push/replace State DOMException: Failed to execute 'replaceState' on 'History': A history state object with URL 'http://192.168.1.111:8080undefined/' cannot be created in a document with origin 'http://192.168.1.111:8080' and URL 'http://192.168.1.111:8080/childApp/app3'.
    at History.eval [as replaceState] (webpack-internal:///./node_modules/single-spa/lib/esm/single-spa.min.js:33:10677)
    at changeLocation (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm-bundler.js:566:60)
    at Object.push (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm-bundler.js:601:9)
    at finalizeNavigation (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm-bundler.js:3202:31)
    at eval (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm-bundler.js:3078:27)

$router.push() 失败报错.png

该问题,在appMain或其他微应用中,通过history.pushState添加新的不存在的路由记录后,再调用this.$router.push() 都会导致该问题出现。 通过history.pushState添加新的不存在的路由记录.gif

而使用appMain.$router.push添加任何不存在的路由记录,都不会出现该问题。 通过主应用的router根实例添加任何不存在的路由记录.gif

尝试过的操作的:

  1. history.pushState()添加的未知路由后,主应用不使用router.push("/404"),而是改用router.replace("/404"),则不会出现。
  2. 微应用使用history.pushState(state)时,state设置为null,则不会出现该问题。

问题思考

可参考线索

  • 通过appMain.$router.push()添加的未知路由则不会导致该bug
  • 通过history.pushState()添加的未知路由会导致该bug
  • history.pushState()是否会触发vueRouter的导航守卫 乾坤项目: 非乾坤项目:不会

现在,我们手头仅有的线索都指向history.pushState(),我们看看vue-router 4.x文档有关 history.pushState用法 的解释:

history-state-的用法.png

我们发现,vue-router官方推荐避免使用pushState,推荐使用replaceState,且重点标注了需要保留history.state状态

vue-router保留的当前状态究竟是什么呢?

我们分别将$router.push()app1.history.pushState前后的history.state打印出来,看看他们的区别。

  • $router.push()前后的history.state:
router.beforeEach((to, from, next) => {
    console.log("history.state before $router.push()",JSON.parse(JSON.stringify(history.state)));
    next();
});
router.afterEach((to, from) => {
    console.log("history.state after $router.push()",JSON.parse(JSON.stringify(history.state)));
});

$router.push()前后的history.state.png

可以看到,通过vue-router自身的push方法,history.state中,始终都记录了同样的状态字段。 我们可以在vue-router.js找到匹配的结构

vue-router.js中对应的history.state结构.png

  • history.pushState()前后的history.state:
console.log("history.state before",JSON.parse(JSON.stringify(history.state)));
window.history.pushState(
    {origin: 'son-app1',id: 'test-000001',orderId: '100000011',},
    '',
    '/childApp/app3'
);
console.log("history.state after",JSON.parse(JSON.stringify(history.state)));

history.pushState()前后的history.state.png

  • history.pushState(null,"","/path")后的history.state:

history.pushState(null)后的history.state.png

所以,大胆猜测一下:vur-touer的路由模拟,需要history.state中记录相关的状态,而我们手动history.pushState()时,并没有记录或保存vue-router需要的state有关状态,才导致出现的该问题呢?

验证猜想

我们先将app1.history.pushState(state)的state,补全至符合vue-router格式试试。(快捷省事,直接复用:实际项目需要另行记录back、current、scoll...等状态值)

window.history.pushState(
        history.state,
        '',
        '/childApp/app1/myFee'
);

试一下

验证结果.gif

OK,到这里,history.pushState会导致vue-router undeifned 问题就解决了。