背景
公司需要做一个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
出来的还是空字符串,原来配置了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.js
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(/\/$/, '')
}
- 注意,需要跟你的项目打包
target
决定改哪个vue-router/dist/vue-router.*.js
总结
ai时代排查问题就是高效~~~