背景

公司需要做一个hybrid app,而且这个可以兼容web端,所以做在同一个项目。项目需要内嵌其他业务部分的项目,很自然地想到用micro-zoe/microapp来做微前端方案。

技术栈

windows系统
父应用: vue3全家桶+micro-zoe/microapp,路由是用hash模式
子应用:vue2全家桶,yarn,路由是用histroy模式

遇到问题.

hybrid端,父应用正常加载,子应用只加载了main.js,没有加载页面。

排查定位&尝试过的方案

  1. 在web端,父应用和子应用都正常加载。起码说明micro-app的配置是没有问题的,子应用的配置是iframe模式,虚拟路由模式是pure
  2. 在hybrid端,子应用打印出来的this.$route,路由信息带了windows的盘符,譬如说,页面路由是/pageA,但是打印出来的是/D:/pageA
  3. 基于2的信息,判断是路由history模式导致的,具体来说,应该是路由配置的base参数有关
  4. 尝试过在父应用传参子应用<micro-app baseroue="/"></micro-app>,也试过<micro-app baseroue="http://domain.com/"></micro-app>也不行,在子应用console出来的__MICRO_APP_BASE_ROUTE__还是空字符串,原来配置了pure模式的虚拟路由,就不会透传baseroute,所以在子应用通过__MICRO_APP_BASE_ROUTE__读取出来时空字符串
  5. 也尝试过在子应用的new Router的时候写死配置base/,也不行
  6. 搜了ai,尝试了<base href="/">的方案,也不行,而且发现了就算是iframe模式,也会htmlhead头也会污染父应用,很奇妙,因为父应用的资源都加载失败了,具体来说就是加载了hybrid所在的盘的目录的根目录。
  7. 想到在web端部署一切正常,所以肯定是在hybrid端这个环境导致的问题。而且在子应用出来的pathfullPath都有问题,就判断是vue-routerhybrid环境导致的解析问题
  8. 我不想读源码,直接扔给ai去读vue-router的源码,定位到原因,就是normalizeBaseresolvePathparsePath getLocation这四个函数导致的,其中就是读取了window.location,所以肯定会读到file协议,所以解析下来的都是错的。我需要做的就是把他做兼容。让ai给了一些改动建议
  9. 用了patch-packagepostinstall-postinstall来打补丁,具体的方法不赘述了

最后的解决方案

  1. 需要锁定当前的vue-router版本,以防版本变动
  2. 修改vue-router/dist/vue-router.esm.js

    export function getLocation(base: string): string {
      let path = window.location.pathname
    
      // 直接在原 path 变量上处理,不新增变量
      if (window.location.protocol === 'file:') {
     path = path
       .replace(/^\/[a-zA-Z]:\//, '/')
       .replace(/^\/(Volumes|Users|Applications)\/[^/]+(\/|$)/, '/');
      }
    
      const pathLowerCase = path.toLowerCase()
      const baseLowerCase = base.toLowerCase()
    
      if (base && ((pathLowerCase === baseLowerCase) ||
     (pathLowerCase.indexOf(cleanPath(baseLowerCase + '/')) === 0))) {
     path = path.slice(base.length)
      }
    
      return (path || '/') + window.location.search + window.location.hash
    }
    
    export function parsePath(path: string): {
      path: string;
      query: string;
      hash: string;
    } {
      let hash = ''
      let query = ''
    
      const hashIndex = path.indexOf('#')
      if (hashIndex >= 0) {
     hash = path.slice(hashIndex)
     path = path.slice(0, hashIndex)
      }
    
      const queryIndex = path.indexOf('?')
      if (queryIndex >= 0) {
     query = path.slice(queryIndex + 1)
     path = path.slice(0, queryIndex)
      }
    
      // 直接修改原 path 变量,不新增 cleanPath
      path = path
     .replace(/^\/[a-zA-Z]:\//, '/')
     .replace(/^\/(Volumes|Users|Applications)\/[^/]+(\/|$)/, '/');
    
      return {
     path,
     query,
     hash
      }
    }
    
    export function resolvePath(
      relative: string,
      base: string,
      append?: boolean
    ): string {
      // 直接在原变量上处理,不新增 cleanBase/cleanRelative
      if (base) {
     base = base
       .replace(/^\/[a-zA-Z]:\//, '/')
       .replace(/^\/(Volumes|Users|Applications)\/[^/]+(\/|$)/, '/');
      }
      if (relative.charAt(0) === '/') {
     relative = relative
       .replace(/^\/[a-zA-Z]:\//, '/')
       .replace(/^\/(Volumes|Users|Applications)\/[^/]+(\/|$)/, '/');
      }
    
      const firstChar = relative.charAt(0)
      if (firstChar === '/') {
     return relative
      }
    
      if (firstChar === '?' || firstChar === '#') {
     return base + relative
      }
    
      const stack = base.split('/')
    
      if (!append || !stack[stack.length - 1]) {
     stack.pop()
      }
    
      const segments = relative.replace(/^\//, '').split('/')
      for (let i = 0; i < segments.length; i++) {
     const segment = segments[i]
     if (segment === '..') {
       stack.pop()
     } else if (segment !== '.') {
       stack.push(segment)
     }
      }
    
      if (stack[0] !== '') {
     stack.unshift('')
      }
    
      return stack.join('/')
    }
    
    function normalizeBase(base: ?string): string {
      if (!base) {
     if (inBrowser) {
       const baseEl = document.querySelector('base')
       base = (baseEl && baseEl.getAttribute('href')) || '/'
       // 复用 base 变量,链式处理协议和系统前缀
       base = base
         .replace(/^[a-zA-Z]+:\/\/[^\/]+/, '') // 移除所有协议
         .replace(/^\/[a-zA-Z]:\//, '/') // 移除 Windows 盘符
         .replace(/^\/(Volumes|Users|Applications)\/[^/]+(\/|$)/, '/'); // 移除 macOS 根目录
     } else {
       base = '/'
     }
      }
      if (base.charAt(0) !== '/') {
     base = '/' + base
      }
      return base.replace(/\/$/, '')
    }
  3. 注意,需要跟你的项目打包target决定改哪个vue-router/dist/vue-router.*.js

总结

ai时代排查问题就是高效~~~


RockerLau
374 声望13 粉丝

Rocker Lau