乾坤切换应用样式丢失-动态修复
!注:样式丢失只存在于按需加载的情况,把所有
import()
改成import from
也是可以解决问题的,但是这样包会很大,影响首屏加载
qiankun
在子应用之间切换(或者多次切换),如果子应用的路由是按需加载的,很大概率动态插入的 link[rel="stylesheet"]
缺少,会导致子应用部分样式丢失
经过分析,使用 webpack
打包的项目
-
mini-css-extract-plugin
输出单独的CSS -
webpackPrefetch 属性(
vue-cli4
默认开启),看下index.html
里<head>
会插入标签如下<link href="{cdn}/{chunkName}.{hash}.css" rel="prefetch">
用来预下载资源
正常情况下(这里单指 css 资源)
- 一个资源至少有两个
link
,其中link[rel="prefetch"]
用来预下载资源;link[rel="stylesheet"]
用来加载样式; - 相同的样式可能会有多个 chunk 合并成一个文件;
- 包含同一个
chunkName
的link
一个是link[rel="prefetch"]
,另一个是link[rel="stylesheet"]
才是正常的; - 当样式丢失的时候,至少有一个
link[rel="prefetch"]
不存在对应的link[rel="stylesheet"]
;
这里就可以利用这个 link[rel="prefetch"]
来实现样式重载
前置准备
按需加载调整
-
import(/* webpackChunkName: "404" */ "@/components/404.vue");
这样打包后输出的文件如下:
404.75102c64.css
-
tsconfig.json
检查下
compilerOptions
,确保removeComments
不要启用webpackChunkName
尽量精简命名,打包输出的内容可能会包含多个 chunk,此时文件名也是由多个 chunk 使用~拼接的,会有点长,可以通过config.optimization.splitChunks.minSize = 100 * 1024;
配置 chunk 最小文件大小
路由对应 chunkName
- links
HTMLElement[]
: 当前chunkName
在head
下的所有link
标签 - paths
string[]
: 路由path
的部分路径(或者完整路径),足够准确即可
路由配置:
{
path: '/aaa/create/test1',
name: 'test1',
component: () => import(/* webpackChunkName: "test" */ '@/components/test1.vue')
},
{
path: '/aaa/create/test2',
name: 'test2',
component: () => import(/* webpackChunkName: "test" */ '@/components/test2.vue')
},
缓存配置:
const chunkNameCacheMap: {
[chunkName: string]: { links: HTMLElement[]; paths: string[] };
} = {
test: {
links: [],
paths: ["/create/test1", "/create/test2"],
},
};
动态修复
在合适的位置执行 fixDynamicCSS(router)
关键步骤
-
webpackChunkName
要跟路由对应,确保唯一 -
监听路由变化,通过模糊但准确的当前
path
信息匹配到chunkName
-
querySelectorAll
对应chunkName
的所有link
css 标签这里利用
querySelector
原生支持的CSS
选择器,匹配到*.css
同时包含chunkName
查找所有元素chunkItem.links = Array.from( document.head.querySelectorAll(`link[href*=".css"][href*="${chunkName}"]`) );
-
遍历找到的所有
links
,如果找不到当前link[rel="prefetch"]
相同的href
对应的link[rel="stylesheet"]
,则设置为rel="stylesheet"
,这样就会被浏览器作为样式加载了
修复方法
import { Router } from "vue-router";
const isProduction = process.env.NODE_ENV === "production";
/** 这里注意与router下按需加载的路由有关,webpackChunkName要跟路由对应,确保唯一 */
const chunkNameCacheMap: {
[chunkName: string]: { links: HTMLElement[]; paths: string[] };
} = {
test: { links: [], paths: ["/aaa/create/test1"] },
};
/**
* 按需加载时样式丢失检测与修复
*/
export function fixDynamicCSS(router: Router) {
if (!isProduction) return;
router.afterEach(({ path }) => {
const chunkNameCacheItem = Object.entries(chunkNameCacheMap).filter(
([_, item]) => item.paths.some((i) => path.includes(i))
);
if (!chunkNameCacheItem?.length) return;
chunkNameCacheItem.forEach(([chunkName, chunkItem]) => {
// 打包后的文件路径如 index.7e74b2d9.css
if (!chunkItem.links?.length) {
chunkItem.links = Array.from(
document.head.querySelectorAll(
`link[href*=".css"][href*="${chunkName}"]`
)
);
}
chunkItem.links.forEach((item) => {
if (item.getAttribute("rel") === "prefetch") {
const hasOneStyleSheet = chunkItem.links.find(
(i) =>
i.getAttribute("href") === item.getAttribute("href") &&
i.getAttribute("rel") === "stylesheet"
);
if (!hasOneStyleSheet) item.setAttribute("rel", "stylesheet");
}
});
});
});
}
转载自:https://juejin.cn/post/7237441562664730681