微前端资源预加载策略让子应用秒级切换一、微前端架构中的资源加载性能瓶颈微前端架构将单体前端应用拆分为多个独立子应用提升了团队的开发自治性。但随之而来的性能问题是子应用切换时的资源加载延迟。当用户从主应用导航到某个子应用时浏览器需要下载该子应用的所有资源HTML、JavaScript、CSS导致明显的白屏时间。在网络条件较差的情况下这个问题尤为突出。资源预加载技术能够在用户实际访问子应用之前提前加载关键资源从而实现秒级切换体验。// 微前端架构中典型的子应用注册 import { registerMicroApps, start } from qiankun; registerMicroApps([ { name: app1, entry: //localhost:3001, container: #subapp, activeRule: /app1, // 存在的问题首次进入 /app1 时需要完整下载 app1 的所有资源 }, { name: app2, entry: //localhost:3002, container: #subapp, activeRule: /app2, // 如果 app2 资源较大如 2MB首次加载可能需要 3-5 秒 } ]); start(); // 启动微前端此时才会开始加载子应用资源性能影响的具体表现首次加载慢用户首次访问子应用时需要等待所有资源下载完成重复加载用户在不同子应用间切换时可能需要重复下载部分资源缓存失效用户清除缓存或资源更新后需要重新下载通过预加载策略可以将子应用资源加载时间隐藏在用户其他操作期间从而提升切换体验。二、微前端资源预加载的技术方案主流的微前端框架qiankun、single-spa、MicroApp都提供了资源预加载机制。技术方案可以分为以下几类1. 静态预加载在主应用加载完成后立即预加载所有子应用的关键资源。2. 动态预加载根据用户行为预测下一个可能访问的子应用按需预加载。3. 智能预加载结合用户历史访问数据使用机器学习模型预测用户意图进行精准预加载。graph TD A[主应用启动] -- B{预加载策略选择} B --|静态| C[预加载所有子应用] B --|动态| D[根据用户行为预加载] B --|智能| E[AI 预测用户意图] C -- F[资源下载队列] D -- F E -- F F -- G{网络状况判断} G --|良好| H[正常预加载] G --|较差| I[延迟预加载br/或降低优先级] H -- J[资源缓存到 Service Worker] I -- J J -- K[用户访问子应用] K -- L{资源是否已缓存} L --|是| M[秒级切换] L --|否| N[正常加载br/显示加载进度] style A fill:#e1f5fe style J fill:#fff3e0 style M fill:#e8f5e9 style N fill:#fce4ecqiankun 的预加载机制qiankun 内置了资源预加载功能可以在启动时配置。// 主应用配置使用 qiankun 的预加载 import { registerMicroApps, start, prefetchApps } from qiankun; // 方案 1在 start() 时配置预加载 start({ prefetch: true, // 预加载所有子应用资源 // 或者指定预加载的时机 prefetch: all, // 预加载所有子应用 // prefetch: smart, // 智能预加载仅预加载当前页面可能访问的子应用 }); // 方案 2手动指定需要预加载的子应用 prefetchApps([ { name: app1, entry: //localhost:3001 }, { name: app2, entry: //localhost:3002 } ]); // 方案 3在路由切换时动态预加载 import { prefetch } from qiankun; // 当用户 hover 到某个菜单项时提前预加载 const handleMenuHover (appName: string) { prefetch(appName); // 立即预加载指定子应用 };single-spa 的预加载机制single-spa 没有内置预加载需要手动实现或使用插件。场景描述在实际微前端项目中静态预加载应用启动时预加载所有子应用往往带来资源浪费。一个包含 6 个子应用的中台系统每个子应用打包后约 400KB全部预加载意味着用户还没打开任何业务页面浏览器就已经下载了 2.4MB 的 JS——在移动端 4G 网络下体验很差。更合理的策略是分级预加载高优先级用户大概率访问的首页级子应用立即预加载中优先级在空闲时间预加载低优先级只在用户明确导航意图时才预加载。// 分级预加载策略 const priorityConfig { high: [dashboard, task-list], // 立即预加载 medium: [reports, settings], // 空闲时预加载 low: [admin, audit] // hover 触发预加载 };另一个容易被忽视的问题是预加载的资源需要处理好版本号。如果子应用发布了新版本但 Service Worker 仍在返回旧版本的缓存用户看到的就是过期的页面。解决方案是将子应用的版本号嵌入资源 URL 中如/app1/v1.2.0/main.js使版本变更天然引发缓存更新。// 使用 single-spa 手动实现预加载 import { registerApplication, start } from single-spa; import { loadApp } from ./loadApp; // 自定义的加载函数 // 注册子应用 registerApplication({ name: app1, app: () loadApp(./app1), activeWhen: [/app1] }); // 预加载函数 async function prefetchApp(appName: string) { const app window.singleSpa.getAppNames().find(name name appName); if (app !window.__LOADED_APPS__?.[appName]) { try { // 手动触发资源加载但不挂载 await System.import(appName); // 如果使用 SystemJS window.__LOADED_APPS__ window.__LOADED_APPS__ || {}; window.__LOADED_APPS__[appName] true; console.log(Prefetched ${appName}); } catch (error) { console.error(Failed to prefetch ${appName}:, error); } } } // 在空闲时间预加载 function prefetchInIdleTime(appNames: string[]) { if (requestIdleCallback in window) { requestIdleCallback(() { appNames.forEach(appName prefetchApp(appName)); }, { timeout: 2000 }); } else { // 降级方案使用 setTimeout setTimeout(() { appNames.forEach(appName prefetchApp(appName)); }, 2000); } } // 启动后预加载 start(); prefetchInIdleTime([app1, app2]);三、实战智能预加载系统的完整实现以下实现一个完整的智能预加载系统支持静态预加载、动态预加载和基于用户行为的预测。1. 预加载管理器// services/prefetchManager.ts interface AppResource { name: string; entry: string; resources: string[]; // 需要预加载的资源 URL 列表 priority: high | medium | low; size: number; // 总大小字节 } interface PrefetchConfig { maxConcurrent: number; // 最大并发预加载数 maxTotalSize: number; // 最大预加载总大小字节 networkThreshold: number; // 网络速度阈值kbps低于此值时不预加载 idleTimeout: number; // 空闲超时时间毫秒 } export class PrefetchManager { private apps: Mapstring, AppResource new Map(); private prefetchedApps: Setstring new Set(); private config: PrefetchConfig; private currentLoads: number 0; private loadQueue: string[] []; constructor(config: PartialPrefetchConfig {}) { this.config { maxConcurrent: 3, maxTotalSize: 5 * 1024 * 1024, // 5MB networkThreshold: 500, // 500 kbps idleTimeout: 2000, ...config }; } /** * 注册子应用资源信息 */ registerApp(app: AppResource): void { this.apps.set(app.name, app); } /** * 预加载单个子应用 */ async prefetch(appName: string): Promisevoid { // 检查是否已预加载 if (this.prefetchedApps.has(appName)) { console.log(App ${appName} already prefetched); return; } const app this.apps.get(appName); if (!app) { console.error(App ${appName} not registered); return; } // 检查网络状况 const networkSpeed await this.checkNetworkSpeed(); if (networkSpeed this.config.networkThreshold) { console.log(Network too slow (${networkSpeed} kbps), skipping prefetch for ${appName}); return; } // 检查并发限制 if (this.currentLoads this.config.maxConcurrent) { this.loadQueue.push(appName); return; } this.currentLoads; try { await this.loadAppResources(app); this.prefetchedApps.add(appName); console.log(Successfully prefetched ${appName}); } catch (error) { console.error(Failed to prefetch ${appName}:, error); } finally { this.currentLoads--; this.processQueue(); } } /** * 批量预加载子应用 */ async prefetchBatch(appNames: string[], strategy: parallel | sequential parallel): Promisevoid { if (strategy parallel) { await Promise.allSettled(appNames.map(name this.prefetch(name))); } else { for (const name of appNames) { await this.prefetch(name); } } } /** * 根据用户行为智能预加载 */ enableSmartPrefetch(): void { // 监听鼠标移动预测用户意图 let lastHoverTime: number 0; let hoverTimer: NodeJS.Timeout | null null; document.addEventListener(mousemove, (event) { const target event.target as HTMLElement; const menuItem target.closest([data-app-name]); if (menuItem) { const appName menuItem.getAttribute(data-app-name); if (appName !this.prefetchedApps.has(appName)) { // 防抖用户 hover 超过 300ms 才触发预加载 if (hoverTimer) { clearTimeout(hoverTimer); } hoverTimer setTimeout(() { console.log(Smart prefetch triggered for ${appName}); this.prefetch(appName); }, 300); } } }); // 监听路由变化预加载可能访问的下一个子应用 window.addEventListener(popstate, (event) { const nextApp this.predictNextApp(window.location.pathname); if (nextApp) { this.prefetch(nextApp); } }); } /** * 预测用户下一个可能访问的子应用 */ private predictNextApp(currentPath: string): string | null { // 简化的预测逻辑实际应用中可以使用机器学习模型 const pathAppMap: Recordstring, string[] { /dashboard: [app1, app2], /app1: [app2, app3], /app2: [app1, app3], /settings: [app1] }; const predictions pathAppMap[currentPath]; if (predictions predictions.length 0) { // 返回第一个未预加载的应用 const nextApp predictions.find(app !this.prefetchedApps.has(app)); return nextApp || null; } return null; } /** * 加载子应用资源 */ private async loadAppResources(app: AppResource): Promisevoid { // 方案 1使用 link relprefetch推荐用于生产环境 if (connection in navigator this.supportsLinkPrefetch()) { return this.prefetchViaLink(app); } // 方案 2使用 fetch 手动下载兼容性更好 return this.prefetchViaFetch(app); } /** * 使用 link relprefetch 预加载 */ private prefetchViaLink(app: AppResource): Promisevoid { return new Promise((resolve, reject) { const links: HTMLLinkElement[] []; for (const resource of app.resources) { const link document.createElement(link); link.rel prefetch; link.href resource; link.as this.getResourceType(resource); link.onload () { console.log(Prefetched resource: ${resource}); }; link.onerror () { console.error(Failed to prefetch resource: ${resource}); }; document.head.appendChild(link); links.push(link); } // 简单处理假设所有资源都能成功预加载 setTimeout(() { links.forEach(link link.remove()); resolve(); }, 100); }); } /** * 使用 fetch 手动预加载 */ private async prefetchViaFetch(app: AppResource): Promisevoid { const cache await caches.open(micro-frontend-prefetch); for (const resource of app.resources) { try { // 检查是否已缓存 const cachedResponse await cache.match(resource); if (cachedResponse) { console.log(Resource already cached: ${resource}); continue; } // 下载并缓存 const response await fetch(resource, { mode: cors }); if (!response.ok) { throw new Error(Failed to fetch ${resource}: ${response.statusText}); } await cache.put(resource, response); console.log(Cached resource: ${resource}); } catch (error) { console.error(Error prefetching ${resource}:, error); } } } /** * 检查网络速度 */ private checkNetworkSpeed(): Promisenumber { return new Promise((resolve) { if (connection in navigator) { const connection (navigator as any).connection; const effectiveType connection.effectiveType; // 估算网络速度单位kbps const speedMap: Recordstring, number { slow-2g: 50, 2g: 150, 3g: 700, 4g: 2000 }; resolve(speedMap[effectiveType] || 700); } else { // 降级方案假设网络良好 resolve(1000); } }); } /** * 判断浏览器是否支持 link relprefetch */ private supportsLinkPrefetch(): boolean { const link document.createElement(link); return link.relList link.relList.supports?.(prefetch) || false; } /** * 根据资源 URL 判断资源类型 */ private getResourceType(url: string): string { if (url.endsWith(.js)) return script; if (url.endsWith(.css)) return style; if (url.match(/\.(png|jpg|jpeg|gif|webp|svg)$/)) return image; if (url.match(/\.(woff|woff2|ttf|eot)$/)) return font; return fetch; } /** * 处理预加载队列 */ private processQueue(): void { if (this.loadQueue.length 0 || this.currentLoads this.config.maxConcurrent) { return; } const nextApp this.loadQueue.shift(); if (nextApp) { this.prefetch(nextApp); } } }2. 与主应用集成场景描述在微前端主应用启动时我们曾经遇到过一个问题预加载资源过多导致首屏渲染被拖慢。主应用自己的 React/Vue 渲染和子应用预加载抢 Bandwidth 和 CPU。用户看到的不是秒级切换而是主页面 loading 更久了的反向体验。// 解决办法把预加载放到 React 完成 hydration 之后 // 或者使用 requestIdleCallback 确保不在关键路径上 ReactDOM.hydrateRoot(document.getElementById(root), App /); // 等首屏渲染稳定后再预加载 requestIdleCallback( () { prefetchManager.prefetchBatch(secondaryApps); }, { timeout: 3000 } );另一个需要注意的点是在 qiankun 中如果同时启用了内置的prefetch和自定义的 PrefetchManager会导致重复下载。务必确保两者不会叠加。最稳妥的做法是将 qiankun 的prefetch设为false全部交由自定义管理器统一控制。// main.ts - 主应用入口 import { registerMicroApps, start } from qiankun; import { PrefetchManager } from ./services/prefetchManager; // 初始化预加载管理器 const prefetchManager new PrefetchManager({ maxConcurrent: 2, maxTotalSize: 3 * 1024 * 1024, // 3MB networkThreshold: 300, idleTimeout: 3000 }); // 注册子应用资源信息实际应用中可以从配置服务获取 prefetchManager.registerApp({ name: app1, entry: //localhost:3001, resources: [ //localhost:3001/static/js/main.js, //localhost:3001/static/css/main.css ], priority: high, size: 500 * 1024 // 500KB }); prefetchManager.registerApp({ name: app2, entry: //localhost:3002, resources: [ //localhost:3002/static/js/main.js, //localhost:3002/static/css/main.css ], priority: medium, size: 800 * 1024 // 800KB }); // 注册微前端应用 registerMicroApps([ { name: app1, entry: //localhost:3001, container: #subapp, activeRule: /app1 }, { name: app2, entry: //localhost:3002, container: #subapp, activeRule: /app2 } ]); // 启动微前端 start({ prefetch: false // 禁用 qiankun 内置的预加载使用自定义管理器 }); // 在主应用加载完成后开始预加载 window.addEventListener(load, () { console.log(Main app loaded, starting prefetch...); // 策略 1立即预加载高优先级应用 prefetchManager.prefetchBatch([app1]); // 策略 2在空闲时间预加载其他应用 prefetchManager.prefetchBatch([app2], sequential); // 策略 3启用智能预加载 prefetchManager.enableSmartPrefetch(); });四、预加载策略的工程化优化1. Service Worker 集成使用 Service Worker 缓存预加载的资源确保离线可用和快速访问。场景描述Service Worker 的缓存策略要特别小心。如果采用 Cache-First 策略优先返回缓存子应用发布了新版本后用户可能一直在使用旧版本的 JS直到 Service Worker 更新完成——而 SW 的更新有生命周期延迟。我们遇到过新版本的 API 接口变更后缓存中的旧 JS 仍然调用旧接口导致生产环境大面积报错。// 推荐对子应用资源使用 Network-First 策略 self.addEventListener(fetch, (event) { if (event.request.url.includes(/subapps/)) { event.respondWith( fetch(event.request) .then(response { // 网络成功后更新缓存 const clone response.clone(); caches.open(subapps-v1).then(cache cache.put(event.request, clone)); return response; }) .catch(() caches.match(event.request)) // 离线时回退到缓存 ); } });这样在线用户永远拿到最新版本只有真正的离线用户才使用缓存。// service-worker.ts const CACHE_NAME micro-frontend-v1; const PREFETCH_CACHE prefetch-cache-v1; // 安装时预缓存关键资源 self.addEventListener(install, (event) { event.waitUntil( caches.open(CACHE_NAME).then((cache) { return cache.addAll([ /, /index.html, /static/js/main.js ]); }) ); }); // 拦截请求优先返回缓存的资源 self.addEventListener(fetch, (event) { event.respondWith( caches.match(event.request).then((response) { if (response) { console.log(Serving from cache: ${event.request.url}); return response; } return fetch(event.request).then((response) { // 动态缓存新资源 if (response response.status 200 response.type basic) { const responseToCache response.clone(); caches.open(PREFETCH_CACHE).then((cache) { cache.put(event.request, responseToCache); }); } return response; }); }) ); });2. 预加载效果监控收集预加载的性能数据持续优化策略。// 预加载性能监控 interface PrefetchMetrics { appName: string; strategy: static | dynamic | smart; startTime: number; endTime: number; resourceCount: number; totalSize: number; cacheHitRate: number; improvementMs: number; // 预加载带来的性能提升毫秒 } export class PrefetchMonitor { private metrics: PrefetchMetrics[] []; /** * 记录预加载性能 */ recordPrefetch(metrics: PrefetchMetrics): void { this.metrics.push(metrics); // 上报到监控服务 this.reportMetrics(metrics); } /** * 计算预加载效果 */ evaluateEffectiveness(): { averageImprovement: number; cacheHitRate: number; recommendedStrategy: string; } { if (this.metrics.length 0) { return { averageImprovement: 0, cacheHitRate: 0, recommendedStrategy: static }; } const totalImprovement this.metrics.reduce((sum, m) sum m.improvementMs, 0); const averageImprovement totalImprovement / this.metrics.length; const totalRequests this.metrics.reduce((sum, m) sum m.resourceCount, 0); const totalCached this.metrics.reduce((sum, m) sum m.resourceCount * m.cacheHitRate, 0); const cacheHitRate totalCached / totalRequests; // 根据数据推荐策略 const strategyEffectiveness this.metrics.reduce((acc, m) { acc[m.strategy] (acc[m.strategy] || 0) m.improvementMs; return acc; }, {} as Recordstring, number); const recommendedStrategy Object.entries(strategyEffectiveness) .sort((a, b) b[1] - a[1])[0][0]; return { averageImprovement, cacheHitRate, recommendedStrategy }; } private async reportMetrics(metrics: PrefetchMetrics): Promisevoid { try { await fetch(/api/metrics/prefetch, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(metrics) }); } catch (error) { console.error(Failed to report prefetch metrics:, error); } } }3. 自适应预加载根据设备性能和网络状况动态调整预加载策略。// 设备性能评估 export function assessDeviceCapability(): high | medium | low { const memory (navigator as any).deviceMemory || 4; // 单位GB const cores navigator.hardwareConcurrency || 4; if (memory 8 cores 8) { return high; } else if (memory 4 cores 4) { return medium; } else { return low; } } // 根据设备能力调整预加载策略 export function getAdaptivePrefetchConfig() { const capability assessDeviceCapability(); const networkSpeed (navigator as any).connection?.effectiveType || 4g; const configs { high: { maxConcurrent: 4, maxTotalSize: 10 * 1024 * 1024, // 10MB enableSmartPrefetch: true }, medium: { maxConcurrent: 2, maxTotalSize: 5 * 1024 * 1024, // 5MB enableSmartPrefetch: true }, low: { maxConcurrent: 1, maxTotalSize: 2 * 1024 * 1024, // 2MB enableSmartPrefetch: false } }; // 网络状况差时进一步降低配置 if (networkSpeed slow-2g || networkSpeed 2g) { configs.low.maxTotalSize 1 * 1024 * 1024; // 1MB configs.low.enableSmartPrefetch false; } return configs[capability]; }五、总结微前端资源预加载是提升子应用切换体验的关键技术。通过静态预加载、动态预加载和智能预加载的组合策略可以显著减少用户等待时间。工程化实践中需要关注 Service Worker 缓存、性能监控、自适应策略等环节。结合设备能力和网络状况动态调整预加载行为能够在性能和资源消耗之间取得平衡。对于大型微前端项目建议建立统一的预加载管理平台集中配置各子应用的预加载策略并持续追踪优化效果。
微前端资源预加载策略:让子应用秒级切换
微前端资源预加载策略让子应用秒级切换一、微前端架构中的资源加载性能瓶颈微前端架构将单体前端应用拆分为多个独立子应用提升了团队的开发自治性。但随之而来的性能问题是子应用切换时的资源加载延迟。当用户从主应用导航到某个子应用时浏览器需要下载该子应用的所有资源HTML、JavaScript、CSS导致明显的白屏时间。在网络条件较差的情况下这个问题尤为突出。资源预加载技术能够在用户实际访问子应用之前提前加载关键资源从而实现秒级切换体验。// 微前端架构中典型的子应用注册 import { registerMicroApps, start } from qiankun; registerMicroApps([ { name: app1, entry: //localhost:3001, container: #subapp, activeRule: /app1, // 存在的问题首次进入 /app1 时需要完整下载 app1 的所有资源 }, { name: app2, entry: //localhost:3002, container: #subapp, activeRule: /app2, // 如果 app2 资源较大如 2MB首次加载可能需要 3-5 秒 } ]); start(); // 启动微前端此时才会开始加载子应用资源性能影响的具体表现首次加载慢用户首次访问子应用时需要等待所有资源下载完成重复加载用户在不同子应用间切换时可能需要重复下载部分资源缓存失效用户清除缓存或资源更新后需要重新下载通过预加载策略可以将子应用资源加载时间隐藏在用户其他操作期间从而提升切换体验。二、微前端资源预加载的技术方案主流的微前端框架qiankun、single-spa、MicroApp都提供了资源预加载机制。技术方案可以分为以下几类1. 静态预加载在主应用加载完成后立即预加载所有子应用的关键资源。2. 动态预加载根据用户行为预测下一个可能访问的子应用按需预加载。3. 智能预加载结合用户历史访问数据使用机器学习模型预测用户意图进行精准预加载。graph TD A[主应用启动] -- B{预加载策略选择} B --|静态| C[预加载所有子应用] B --|动态| D[根据用户行为预加载] B --|智能| E[AI 预测用户意图] C -- F[资源下载队列] D -- F E -- F F -- G{网络状况判断} G --|良好| H[正常预加载] G --|较差| I[延迟预加载br/或降低优先级] H -- J[资源缓存到 Service Worker] I -- J J -- K[用户访问子应用] K -- L{资源是否已缓存} L --|是| M[秒级切换] L --|否| N[正常加载br/显示加载进度] style A fill:#e1f5fe style J fill:#fff3e0 style M fill:#e8f5e9 style N fill:#fce4ecqiankun 的预加载机制qiankun 内置了资源预加载功能可以在启动时配置。// 主应用配置使用 qiankun 的预加载 import { registerMicroApps, start, prefetchApps } from qiankun; // 方案 1在 start() 时配置预加载 start({ prefetch: true, // 预加载所有子应用资源 // 或者指定预加载的时机 prefetch: all, // 预加载所有子应用 // prefetch: smart, // 智能预加载仅预加载当前页面可能访问的子应用 }); // 方案 2手动指定需要预加载的子应用 prefetchApps([ { name: app1, entry: //localhost:3001 }, { name: app2, entry: //localhost:3002 } ]); // 方案 3在路由切换时动态预加载 import { prefetch } from qiankun; // 当用户 hover 到某个菜单项时提前预加载 const handleMenuHover (appName: string) { prefetch(appName); // 立即预加载指定子应用 };single-spa 的预加载机制single-spa 没有内置预加载需要手动实现或使用插件。场景描述在实际微前端项目中静态预加载应用启动时预加载所有子应用往往带来资源浪费。一个包含 6 个子应用的中台系统每个子应用打包后约 400KB全部预加载意味着用户还没打开任何业务页面浏览器就已经下载了 2.4MB 的 JS——在移动端 4G 网络下体验很差。更合理的策略是分级预加载高优先级用户大概率访问的首页级子应用立即预加载中优先级在空闲时间预加载低优先级只在用户明确导航意图时才预加载。// 分级预加载策略 const priorityConfig { high: [dashboard, task-list], // 立即预加载 medium: [reports, settings], // 空闲时预加载 low: [admin, audit] // hover 触发预加载 };另一个容易被忽视的问题是预加载的资源需要处理好版本号。如果子应用发布了新版本但 Service Worker 仍在返回旧版本的缓存用户看到的就是过期的页面。解决方案是将子应用的版本号嵌入资源 URL 中如/app1/v1.2.0/main.js使版本变更天然引发缓存更新。// 使用 single-spa 手动实现预加载 import { registerApplication, start } from single-spa; import { loadApp } from ./loadApp; // 自定义的加载函数 // 注册子应用 registerApplication({ name: app1, app: () loadApp(./app1), activeWhen: [/app1] }); // 预加载函数 async function prefetchApp(appName: string) { const app window.singleSpa.getAppNames().find(name name appName); if (app !window.__LOADED_APPS__?.[appName]) { try { // 手动触发资源加载但不挂载 await System.import(appName); // 如果使用 SystemJS window.__LOADED_APPS__ window.__LOADED_APPS__ || {}; window.__LOADED_APPS__[appName] true; console.log(Prefetched ${appName}); } catch (error) { console.error(Failed to prefetch ${appName}:, error); } } } // 在空闲时间预加载 function prefetchInIdleTime(appNames: string[]) { if (requestIdleCallback in window) { requestIdleCallback(() { appNames.forEach(appName prefetchApp(appName)); }, { timeout: 2000 }); } else { // 降级方案使用 setTimeout setTimeout(() { appNames.forEach(appName prefetchApp(appName)); }, 2000); } } // 启动后预加载 start(); prefetchInIdleTime([app1, app2]);三、实战智能预加载系统的完整实现以下实现一个完整的智能预加载系统支持静态预加载、动态预加载和基于用户行为的预测。1. 预加载管理器// services/prefetchManager.ts interface AppResource { name: string; entry: string; resources: string[]; // 需要预加载的资源 URL 列表 priority: high | medium | low; size: number; // 总大小字节 } interface PrefetchConfig { maxConcurrent: number; // 最大并发预加载数 maxTotalSize: number; // 最大预加载总大小字节 networkThreshold: number; // 网络速度阈值kbps低于此值时不预加载 idleTimeout: number; // 空闲超时时间毫秒 } export class PrefetchManager { private apps: Mapstring, AppResource new Map(); private prefetchedApps: Setstring new Set(); private config: PrefetchConfig; private currentLoads: number 0; private loadQueue: string[] []; constructor(config: PartialPrefetchConfig {}) { this.config { maxConcurrent: 3, maxTotalSize: 5 * 1024 * 1024, // 5MB networkThreshold: 500, // 500 kbps idleTimeout: 2000, ...config }; } /** * 注册子应用资源信息 */ registerApp(app: AppResource): void { this.apps.set(app.name, app); } /** * 预加载单个子应用 */ async prefetch(appName: string): Promisevoid { // 检查是否已预加载 if (this.prefetchedApps.has(appName)) { console.log(App ${appName} already prefetched); return; } const app this.apps.get(appName); if (!app) { console.error(App ${appName} not registered); return; } // 检查网络状况 const networkSpeed await this.checkNetworkSpeed(); if (networkSpeed this.config.networkThreshold) { console.log(Network too slow (${networkSpeed} kbps), skipping prefetch for ${appName}); return; } // 检查并发限制 if (this.currentLoads this.config.maxConcurrent) { this.loadQueue.push(appName); return; } this.currentLoads; try { await this.loadAppResources(app); this.prefetchedApps.add(appName); console.log(Successfully prefetched ${appName}); } catch (error) { console.error(Failed to prefetch ${appName}:, error); } finally { this.currentLoads--; this.processQueue(); } } /** * 批量预加载子应用 */ async prefetchBatch(appNames: string[], strategy: parallel | sequential parallel): Promisevoid { if (strategy parallel) { await Promise.allSettled(appNames.map(name this.prefetch(name))); } else { for (const name of appNames) { await this.prefetch(name); } } } /** * 根据用户行为智能预加载 */ enableSmartPrefetch(): void { // 监听鼠标移动预测用户意图 let lastHoverTime: number 0; let hoverTimer: NodeJS.Timeout | null null; document.addEventListener(mousemove, (event) { const target event.target as HTMLElement; const menuItem target.closest([data-app-name]); if (menuItem) { const appName menuItem.getAttribute(data-app-name); if (appName !this.prefetchedApps.has(appName)) { // 防抖用户 hover 超过 300ms 才触发预加载 if (hoverTimer) { clearTimeout(hoverTimer); } hoverTimer setTimeout(() { console.log(Smart prefetch triggered for ${appName}); this.prefetch(appName); }, 300); } } }); // 监听路由变化预加载可能访问的下一个子应用 window.addEventListener(popstate, (event) { const nextApp this.predictNextApp(window.location.pathname); if (nextApp) { this.prefetch(nextApp); } }); } /** * 预测用户下一个可能访问的子应用 */ private predictNextApp(currentPath: string): string | null { // 简化的预测逻辑实际应用中可以使用机器学习模型 const pathAppMap: Recordstring, string[] { /dashboard: [app1, app2], /app1: [app2, app3], /app2: [app1, app3], /settings: [app1] }; const predictions pathAppMap[currentPath]; if (predictions predictions.length 0) { // 返回第一个未预加载的应用 const nextApp predictions.find(app !this.prefetchedApps.has(app)); return nextApp || null; } return null; } /** * 加载子应用资源 */ private async loadAppResources(app: AppResource): Promisevoid { // 方案 1使用 link relprefetch推荐用于生产环境 if (connection in navigator this.supportsLinkPrefetch()) { return this.prefetchViaLink(app); } // 方案 2使用 fetch 手动下载兼容性更好 return this.prefetchViaFetch(app); } /** * 使用 link relprefetch 预加载 */ private prefetchViaLink(app: AppResource): Promisevoid { return new Promise((resolve, reject) { const links: HTMLLinkElement[] []; for (const resource of app.resources) { const link document.createElement(link); link.rel prefetch; link.href resource; link.as this.getResourceType(resource); link.onload () { console.log(Prefetched resource: ${resource}); }; link.onerror () { console.error(Failed to prefetch resource: ${resource}); }; document.head.appendChild(link); links.push(link); } // 简单处理假设所有资源都能成功预加载 setTimeout(() { links.forEach(link link.remove()); resolve(); }, 100); }); } /** * 使用 fetch 手动预加载 */ private async prefetchViaFetch(app: AppResource): Promisevoid { const cache await caches.open(micro-frontend-prefetch); for (const resource of app.resources) { try { // 检查是否已缓存 const cachedResponse await cache.match(resource); if (cachedResponse) { console.log(Resource already cached: ${resource}); continue; } // 下载并缓存 const response await fetch(resource, { mode: cors }); if (!response.ok) { throw new Error(Failed to fetch ${resource}: ${response.statusText}); } await cache.put(resource, response); console.log(Cached resource: ${resource}); } catch (error) { console.error(Error prefetching ${resource}:, error); } } } /** * 检查网络速度 */ private checkNetworkSpeed(): Promisenumber { return new Promise((resolve) { if (connection in navigator) { const connection (navigator as any).connection; const effectiveType connection.effectiveType; // 估算网络速度单位kbps const speedMap: Recordstring, number { slow-2g: 50, 2g: 150, 3g: 700, 4g: 2000 }; resolve(speedMap[effectiveType] || 700); } else { // 降级方案假设网络良好 resolve(1000); } }); } /** * 判断浏览器是否支持 link relprefetch */ private supportsLinkPrefetch(): boolean { const link document.createElement(link); return link.relList link.relList.supports?.(prefetch) || false; } /** * 根据资源 URL 判断资源类型 */ private getResourceType(url: string): string { if (url.endsWith(.js)) return script; if (url.endsWith(.css)) return style; if (url.match(/\.(png|jpg|jpeg|gif|webp|svg)$/)) return image; if (url.match(/\.(woff|woff2|ttf|eot)$/)) return font; return fetch; } /** * 处理预加载队列 */ private processQueue(): void { if (this.loadQueue.length 0 || this.currentLoads this.config.maxConcurrent) { return; } const nextApp this.loadQueue.shift(); if (nextApp) { this.prefetch(nextApp); } } }2. 与主应用集成场景描述在微前端主应用启动时我们曾经遇到过一个问题预加载资源过多导致首屏渲染被拖慢。主应用自己的 React/Vue 渲染和子应用预加载抢 Bandwidth 和 CPU。用户看到的不是秒级切换而是主页面 loading 更久了的反向体验。// 解决办法把预加载放到 React 完成 hydration 之后 // 或者使用 requestIdleCallback 确保不在关键路径上 ReactDOM.hydrateRoot(document.getElementById(root), App /); // 等首屏渲染稳定后再预加载 requestIdleCallback( () { prefetchManager.prefetchBatch(secondaryApps); }, { timeout: 3000 } );另一个需要注意的点是在 qiankun 中如果同时启用了内置的prefetch和自定义的 PrefetchManager会导致重复下载。务必确保两者不会叠加。最稳妥的做法是将 qiankun 的prefetch设为false全部交由自定义管理器统一控制。// main.ts - 主应用入口 import { registerMicroApps, start } from qiankun; import { PrefetchManager } from ./services/prefetchManager; // 初始化预加载管理器 const prefetchManager new PrefetchManager({ maxConcurrent: 2, maxTotalSize: 3 * 1024 * 1024, // 3MB networkThreshold: 300, idleTimeout: 3000 }); // 注册子应用资源信息实际应用中可以从配置服务获取 prefetchManager.registerApp({ name: app1, entry: //localhost:3001, resources: [ //localhost:3001/static/js/main.js, //localhost:3001/static/css/main.css ], priority: high, size: 500 * 1024 // 500KB }); prefetchManager.registerApp({ name: app2, entry: //localhost:3002, resources: [ //localhost:3002/static/js/main.js, //localhost:3002/static/css/main.css ], priority: medium, size: 800 * 1024 // 800KB }); // 注册微前端应用 registerMicroApps([ { name: app1, entry: //localhost:3001, container: #subapp, activeRule: /app1 }, { name: app2, entry: //localhost:3002, container: #subapp, activeRule: /app2 } ]); // 启动微前端 start({ prefetch: false // 禁用 qiankun 内置的预加载使用自定义管理器 }); // 在主应用加载完成后开始预加载 window.addEventListener(load, () { console.log(Main app loaded, starting prefetch...); // 策略 1立即预加载高优先级应用 prefetchManager.prefetchBatch([app1]); // 策略 2在空闲时间预加载其他应用 prefetchManager.prefetchBatch([app2], sequential); // 策略 3启用智能预加载 prefetchManager.enableSmartPrefetch(); });四、预加载策略的工程化优化1. Service Worker 集成使用 Service Worker 缓存预加载的资源确保离线可用和快速访问。场景描述Service Worker 的缓存策略要特别小心。如果采用 Cache-First 策略优先返回缓存子应用发布了新版本后用户可能一直在使用旧版本的 JS直到 Service Worker 更新完成——而 SW 的更新有生命周期延迟。我们遇到过新版本的 API 接口变更后缓存中的旧 JS 仍然调用旧接口导致生产环境大面积报错。// 推荐对子应用资源使用 Network-First 策略 self.addEventListener(fetch, (event) { if (event.request.url.includes(/subapps/)) { event.respondWith( fetch(event.request) .then(response { // 网络成功后更新缓存 const clone response.clone(); caches.open(subapps-v1).then(cache cache.put(event.request, clone)); return response; }) .catch(() caches.match(event.request)) // 离线时回退到缓存 ); } });这样在线用户永远拿到最新版本只有真正的离线用户才使用缓存。// service-worker.ts const CACHE_NAME micro-frontend-v1; const PREFETCH_CACHE prefetch-cache-v1; // 安装时预缓存关键资源 self.addEventListener(install, (event) { event.waitUntil( caches.open(CACHE_NAME).then((cache) { return cache.addAll([ /, /index.html, /static/js/main.js ]); }) ); }); // 拦截请求优先返回缓存的资源 self.addEventListener(fetch, (event) { event.respondWith( caches.match(event.request).then((response) { if (response) { console.log(Serving from cache: ${event.request.url}); return response; } return fetch(event.request).then((response) { // 动态缓存新资源 if (response response.status 200 response.type basic) { const responseToCache response.clone(); caches.open(PREFETCH_CACHE).then((cache) { cache.put(event.request, responseToCache); }); } return response; }); }) ); });2. 预加载效果监控收集预加载的性能数据持续优化策略。// 预加载性能监控 interface PrefetchMetrics { appName: string; strategy: static | dynamic | smart; startTime: number; endTime: number; resourceCount: number; totalSize: number; cacheHitRate: number; improvementMs: number; // 预加载带来的性能提升毫秒 } export class PrefetchMonitor { private metrics: PrefetchMetrics[] []; /** * 记录预加载性能 */ recordPrefetch(metrics: PrefetchMetrics): void { this.metrics.push(metrics); // 上报到监控服务 this.reportMetrics(metrics); } /** * 计算预加载效果 */ evaluateEffectiveness(): { averageImprovement: number; cacheHitRate: number; recommendedStrategy: string; } { if (this.metrics.length 0) { return { averageImprovement: 0, cacheHitRate: 0, recommendedStrategy: static }; } const totalImprovement this.metrics.reduce((sum, m) sum m.improvementMs, 0); const averageImprovement totalImprovement / this.metrics.length; const totalRequests this.metrics.reduce((sum, m) sum m.resourceCount, 0); const totalCached this.metrics.reduce((sum, m) sum m.resourceCount * m.cacheHitRate, 0); const cacheHitRate totalCached / totalRequests; // 根据数据推荐策略 const strategyEffectiveness this.metrics.reduce((acc, m) { acc[m.strategy] (acc[m.strategy] || 0) m.improvementMs; return acc; }, {} as Recordstring, number); const recommendedStrategy Object.entries(strategyEffectiveness) .sort((a, b) b[1] - a[1])[0][0]; return { averageImprovement, cacheHitRate, recommendedStrategy }; } private async reportMetrics(metrics: PrefetchMetrics): Promisevoid { try { await fetch(/api/metrics/prefetch, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(metrics) }); } catch (error) { console.error(Failed to report prefetch metrics:, error); } } }3. 自适应预加载根据设备性能和网络状况动态调整预加载策略。// 设备性能评估 export function assessDeviceCapability(): high | medium | low { const memory (navigator as any).deviceMemory || 4; // 单位GB const cores navigator.hardwareConcurrency || 4; if (memory 8 cores 8) { return high; } else if (memory 4 cores 4) { return medium; } else { return low; } } // 根据设备能力调整预加载策略 export function getAdaptivePrefetchConfig() { const capability assessDeviceCapability(); const networkSpeed (navigator as any).connection?.effectiveType || 4g; const configs { high: { maxConcurrent: 4, maxTotalSize: 10 * 1024 * 1024, // 10MB enableSmartPrefetch: true }, medium: { maxConcurrent: 2, maxTotalSize: 5 * 1024 * 1024, // 5MB enableSmartPrefetch: true }, low: { maxConcurrent: 1, maxTotalSize: 2 * 1024 * 1024, // 2MB enableSmartPrefetch: false } }; // 网络状况差时进一步降低配置 if (networkSpeed slow-2g || networkSpeed 2g) { configs.low.maxTotalSize 1 * 1024 * 1024; // 1MB configs.low.enableSmartPrefetch false; } return configs[capability]; }五、总结微前端资源预加载是提升子应用切换体验的关键技术。通过静态预加载、动态预加载和智能预加载的组合策略可以显著减少用户等待时间。工程化实践中需要关注 Service Worker 缓存、性能监控、自适应策略等环节。结合设备能力和网络状况动态调整预加载行为能够在性能和资源消耗之间取得平衡。对于大型微前端项目建议建立统一的预加载管理平台集中配置各子应用的预加载策略并持续追踪优化效果。