PWA 的性能优化对于提供良好的用户体验至关重要。以下是全面的性能优化策略:
1. 资源加载优化
预缓存关键资源
javascript// sw.js const CACHE_NAME = 'my-pwa-v1'; const CRITICAL_ASSETS = [ '/', '/index.html', '/styles/main.css', '/scripts/app.js', '/images/logo.png' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(CRITICAL_ASSETS)) .then(() => self.skipWaiting()) ); });
懒加载非关键资源
javascript// 图片懒加载 const images = document.querySelectorAll('img[data-src]'); const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.removeAttribute('data-src'); observer.unobserve(img); } }); }); images.forEach(img => imageObserver.observe(img)); // 组件懒加载 const LazyComponent = React.lazy(() => import('./LazyComponent'));
代码分割
javascript// 使用动态 import 进行代码分割 async function loadFeature() { const module = await import('./feature.js'); module.init(); } // React 中的代码分割 const Home = React.lazy(() => import('./Home')); const About = React.lazy(() => import('./About'));
2. 缓存策略优化
智能缓存策略
javascriptself.addEventListener('fetch', event => { const url = new URL(event.request.url); // 静态资源:缓存优先 if (url.pathname.match(/\.(css|js|png|jpg|jpeg|gif|svg|woff|woff2)$/)) { event.respondWith(cacheFirst(event.request)); } // API 请求:网络优先 else if (url.pathname.startsWith('/api/')) { event.respondWith(networkFirst(event.request)); } // HTML 文档:网络优先,失败时返回缓存 else if (event.request.mode === 'navigate') { event.respondWith(networkFirstWithFallback(event.request)); } // 其他:缓存同时更新 else { event.respondWith(staleWhileRevalidate(event.request)); } }); function cacheFirst(request) { return caches.match(request).then(response => { return response || fetch(request).then(networkResponse => { const responseClone = networkResponse.clone(); caches.open('static-cache').then(cache => { cache.put(request, responseClone); }); return networkResponse; }); }); } function networkFirst(request) { return fetch(request).then(networkResponse => { const responseClone = networkResponse.clone(); caches.open('dynamic-cache').then(cache => { cache.put(request, responseClone); }); return networkResponse; }).catch(() => caches.match(request)); } function staleWhileRevalidate(request) { return caches.match(request).then(cachedResponse => { const fetchPromise = fetch(request).then(networkResponse => { caches.open('dynamic-cache').then(cache => { cache.put(request, networkResponse.clone()); }); return networkResponse; }); return cachedResponse || fetchPromise; }); }
缓存版本管理
javascriptconst CACHE_VERSION = 'v2'; const CACHE_NAME = `my-pwa-${CACHE_VERSION}`; self.addEventListener('activate', event => { event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { if (cacheName !== CACHE_NAME && cacheName.startsWith('my-pwa-')) { return caches.delete(cacheName); } }) ); }).then(() => self.clients.claim()) ); });
3. 图片优化
使用现代图片格式
html<picture> <source srcset="image.webp" type="image/webp"> <source srcset="image.jpg" type="image/jpeg"> <img src="image.jpg" alt="Description" loading="lazy"> </picture>
响应式图片
html<img src="image-small.jpg" srcset="image-small.jpg 480w, image-medium.jpg 768w, image-large.jpg 1024w" sizes="(max-width: 480px) 480px, (max-width: 768px) 768px, 1024px" alt="Description" loading="lazy" >
图片压缩和优化
javascript// 使用 sharp 库压缩图片 const sharp = require('sharp'); async function optimizeImage(inputPath, outputPath) { await sharp(inputPath) .resize(800, 600, { fit: 'inside' }) .webp({ quality: 80 }) .toFile(outputPath); }
4. JavaScript 优化
减少包体积
javascript// 使用 Tree Shaking // 只导入需要的函数 import { debounce } from 'lodash-es'; // 避免导入整个库 // import _ from 'lodash'; // ❌ 避免
使用 Web Workers
javascript// 主线程 const worker = new Worker('worker.js'); worker.postMessage({ data: largeDataSet }); worker.onmessage = (event) => { console.log('Processed data:', event.data); }; // worker.js self.onmessage = (event) => { const result = processData(event.data.data); self.postMessage(result); };
防抖和节流
javascript// 防抖 function debounce(func, wait) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } // 节流 function throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // 使用示例 window.addEventListener('resize', debounce(handleResize, 300)); window.addEventListener('scroll', throttle(handleScroll, 100));
5. CSS 优化
关键 CSS 内联
html<style> /* 关键 CSS */ body { margin: 0; font-family: Arial; } .header { background: #333; color: white; } </style> <link rel="preload" href="styles/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> <noscript><link rel="stylesheet" href="styles/main.css"></noscript>
CSS 压缩和优化
javascript// 使用 cssnano 压缩 CSS const cssnano = require('cssnano'); const postcss = require('postcss'); postcss([cssnano]) .process(css, { from: undefined }) .then(result => { console.log(result.css); });
使用 CSS 变量
css:root { --primary-color: #007bff; --secondary-color: #6c757d; --spacing: 1rem; } .button { background: var(--primary-color); padding: var(--spacing); }
6. 网络优化
使用 HTTP/2
nginx# Nginx 配置 server { listen 443 ssl http2; server_name example.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; }
启用压缩
javascript// Express.js 中启用压缩 const compression = require('compression'); const express = require('express'); const app = express(); app.use(compression());
CDN 加速
html<!-- 使用 CDN 加载资源 --> <link rel="stylesheet" href="https://cdn.example.com/styles/main.css"> <script src="https://cdn.example.com/scripts/app.js"></script>
7. 性能监控
使用 Performance API
javascript// 页面加载时间 window.addEventListener('load', () => { const perfData = performance.getEntriesByType('navigation')[0]; console.log('Page load time:', perfData.loadEventEnd - perfData.fetchStart); console.log('DOM ready time:', perfData.domContentLoadedEventEnd - perfData.fetchStart); }); // 资源加载时间 const resources = performance.getEntriesByType('resource'); resources.forEach(resource => { console.log(`${resource.name}: ${resource.duration}ms`); });
使用 Web Vitals
javascriptimport { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'; getCLS(console.log); getFID(console.log); getFCP(console.log); getLCP(console.log); getTTFB(console.log);
8. Service Worker 优化
优化 Service Worker 更新
javascript// 定期检查更新 setInterval(() => { navigator.serviceWorker.getRegistration().then(registration => { if (registration) { registration.update(); } }); }, 60 * 60 * 1000); // 每小时检查一次
使用 Cache Storage API
javascript// 检查缓存大小 async function getCacheSize() { const cache = await caches.open('my-cache'); const keys = await cache.keys(); let totalSize = 0; for (const request of keys) { const response = await cache.match(request); const blob = await response.blob(); totalSize += blob.size; } console.log(`Cache size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`); }
最佳实践总结
- 预缓存关键资源:确保首次加载快速
- 使用合适的缓存策略:根据资源类型选择策略
- 优化图片:使用现代格式和响应式图片
- 代码分割:减少初始加载时间
- 懒加载:延迟加载非关键资源
- 压缩资源:减小文件体积
- 使用 CDN:加速资源加载
- 监控性能:持续跟踪和优化性能指标
- 定期更新缓存:确保内容新鲜度
- 测试不同网络条件:确保在各种网络下都有良好体验