背景
公司需要做一个hybrid app,而且这个可以兼容web端,所以做在同一个项目。项目需要内嵌其他业务部分的项目,很自然地想到用micro-zoe/microapp来做微前端方案。
技术栈
windows系统
父应用: vue3全家桶+micro-zoe/microapp,路由是用hash模式
子应用:vue2全家桶,yarn,路由是用histroy模式
遇到问题.
在hybrid端,父应用正常加载,子应用只加载了main.js,没有加载页面。
排查定位&尝试过的方案
- 在web端,父应用和子应用都正常加载。起码说明
micro-app的配置是没有问题的,子应用的配置是iframe模式,虚拟路由模式是pure。 - 在hybrid端,子应用打印出来的
this.$route,路由信息带了windows的盘符,譬如说,页面路由是/pageA,但是打印出来的是/D:/pageA。 - 基于2的信息,判断是路由
history模式导致的,具体来说,应该是路由配置的base参数有关 - 尝试过在父应用传参子应用
<micro-app baseroue="/"></micro-app>,也试过<micro-app baseroue="http://domain.com/"></micro-app>也不行,在子应用console出来的__MICRO_APP_BASE_ROUTE__还是空字符串,原来配置了pure模式的虚拟路由,就不会透传baseroute,所以在子应用通过__MICRO_APP_BASE_ROUTE__读取出来时空字符串 - 也尝试过在子应用的
new Router的时候写死配置base为/,也不行 - 搜了ai,尝试了
<base href="/">的方案,也不行,而且发现了就算是iframe模式,也会html的head头也会污染父应用,很奇妙,因为父应用的资源都加载失败了,具体来说就是加载了hybrid所在的盘的目录的根目录。 - 想到在
web端部署一切正常,所以肯定是在hybrid端这个环境导致的问题。而且在子应用出来的path和fullPath都有问题,就判断是vue-router在hybrid环境导致的解析问题 - 我不想读源码,直接扔给ai去读
vue-router的源码,定位到原因,就是normalizeBase、resolvePath、parsePath、getLocation这四个函数导致的,其中就是读取了window.location,所以肯定会读到file协议,所以解析下来的都是错的。我需要做的就是把他做兼容。让ai给了一些改动建议 - 用了
patch-package和postinstall-postinstall来打补丁,具体的方法不赘述了
最后的解决方案
- 需要锁定当前的
vue-router版本,以防版本变动 修改
vue-router/dist/vue-router.esm.jsexport 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(/\/$/, '') }- 注意,需要跟你的项目打包
target决定改哪个vue-router/dist/vue-router.*.js
总结
ai时代排查问题就是高效~~~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。