Progressive Web App (PWA) optimization
Progressive Web App (PWA) Optimization
PWAs combine the advantages of web and native applications, delivering a web experience closer to native apps. The key to optimizing PWAs lies in improving performance, reliability, and user experience, covering aspects such as resource loading, caching strategies, background synchronization, and more.
Resource Loading Optimization
Resource loading speed directly impacts the first-screen rendering time of a PWA. The following strategies can significantly enhance performance:
-
Code Splitting and Lazy Loading
Use dynamicimport()
to achieve on-demand loading and reduce the initial bundle size:// Dynamically load non-critical modules button.addEventListener('click', () => { import('./analytics.js').then(module => { module.trackEvent('click'); }); });
-
Preloading Critical Resources
Preload critical CSS and fonts in the HTML head:<link rel="preload" href="/styles/main.css" as="style"> <link rel="preload" href="/fonts/roboto.woff2" as="font" crossorigin>
-
HTTP/2 Server Push
Configure the server to proactively push critical resources, reducing round-trip time (RTT). Nginx example:http2_push /styles/main.css; http2_push /scripts/app.js;
Service Worker Caching Strategies
Service Worker is the core of a PWA. A well-designed caching strategy enhances the offline experience:
-
Layered Caching Strategy
- Precache: Static resources determined at build time (e.g., CSS, JS)
- Runtime Cache: Dynamic content (e.g., API responses, user data)
const PRECACHE = 'precache-v1'; const RUNTIME = 'runtime'; self.addEventListener('install', event => { event.waitUntil( caches.open(PRECACHE).then(cache => cache.addAll([ '/', '/styles/main.css', '/scripts/app.js' ]) ) ); }); self.addEventListener('fetch', event => { if (event.request.url.startsWith('https://api.example.com')) { event.respondWith( caches.open(RUNTIME).then(cache => fetch(event.request).then(response => { cache.put(event.request, response.clone()); return response; }).catch(() => caches.match(event.request)) ) ); } else { event.respondWith( caches.match(event.request).then(response => response || fetch(event.request) ) ); } });
-
Cache Update Mechanism
Use versioned cache names and theactivate
event to clean up old caches:self.addEventListener('activate', event => { const cacheWhitelist = [PRECACHE, RUNTIME]; event.waitUntil( caches.keys().then(keyList => Promise.all(keyList.map(key => { if (!cacheWhitelist.includes(key)) { return caches.delete(key); } })) ) ); });
Network Request Optimization
-
Background Sync
Automatically retry failed requests when the network is restored:self.addEventListener('sync', event => { if (event.tag === 'sync-comments') { event.waitUntil( sendFailedComments().then(() => showNotification('Comments synced') ) ); } }); // Register sync task navigator.serviceWorker.ready.then(registration => { registration.sync.register('sync-comments'); });
-
Request Fallback Handling
Implement graceful degradation for API requests:function fetchWithFallback(url) { return fetch(url) .catch(() => fetchFromCache(url)) .catch(() => getPlaceholderData()); }
Rendering Performance Optimization
-
Virtual List for Long Lists
UseIntersection Observer
to implement dynamic rendering:const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { renderItem(entry.target.dataset.index); observer.unobserve(entry.target); } }); }); items.forEach((item, index) => { const placeholder = document.createElement('div'); placeholder.dataset.index = index; observer.observe(placeholder); list.appendChild(placeholder); });
-
Web Worker for Heavy Computations
Offload CPU-intensive tasks from the main thread:// main.js const worker = new Worker('compute.js'); worker.postMessage({data: largeDataSet}); worker.onmessage = e => updateUI(e.data); // compute.js self.onmessage = e => { const result = heavyComputation(e.data); self.postMessage(result); };
App Shell Architecture
-
Minimal Initial HTML
Include only the critical skeleton structure:<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>App Shell</title> <link rel="manifest" href="/manifest.json"> <style> /* Inline critical CSS */ .header { position: fixed; top: 0; } .skeleton { animation: pulse 1.5s infinite; } </style> </head> <body> <div class="header"></div> <div class="content"> <div class="skeleton"></div> </div> <script src="/app.js" async></script> </body> </html>
-
Dynamic Content Injection
Populate content via JavaScript:window.addEventListener('DOMContentLoaded', () => { fetchContent().then(data => { document.querySelector('.content').innerHTML = renderPosts(data.posts); }); });
Performance Monitoring and Tuning
-
Key Metrics Collection
UsePerformanceObserver
to monitor core metrics:const observer = new PerformanceObserver(list => { for (const entry of list.getEntries()) { if (entry.name === 'first-contentful-paint') { console.log('FCP:', entry.startTime); sendToAnalytics({metric: 'FCP', value: entry.startTime}); } } }); observer.observe({type: 'paint', buffered: true});
-
Memory Leak Detection
Periodically check memory usage:setInterval(() => { const memory = performance.memory; if (memory.usedJSHeapSize > memory.jsHeapSizeLimit * 0.7) { console.warn('Memory usage high:', memory); } }, 10000);
Offline Experience Enhancement
-
Custom Offline Pages
Provide meaningful offline states for different routes:self.addEventListener('fetch', event => { if (event.request.mode === 'navigate') { event.respondWith( fetch(event.request).catch(() => caches.match('/offline.html') ) ); } });
-
IndexedDB Data Persistence
Implement a complete offline data layer:function openDB() { return new Promise((resolve, reject) => { const request = indexedDB.open('appDB', 2); request.onupgradeneeded = e => { const db = e.target.result; if (!db.objectStoreNames.contains('posts')) { db.createObjectStore('posts', {keyPath: 'id'}); } }; request.onsuccess = e => resolve(e.target.result); request.onerror = reject; }); } async function savePost(post) { const db = await openDB(); const tx = db.transaction('posts', 'readwrite'); tx.objectStore('posts').put(post); return tx.complete; }
Push Notification Optimization
-
Permission Request Timing
Request notification permission after user interaction:document.getElementById('notify-btn').addEventListener('click', () => { Notification.requestPermission().then(permission => { if (permission === 'granted') { showThankYouMessage(); } }); });
-
Personalized Notification Content
Use server push for dynamic content:self.addEventListener('push', event => { const data = event.data.json(); event.waitUntil( self.registration.showNotification(data.title, { body: data.message, icon: '/icons/notification.png', data: {url: data.link} }) ); }); self.addEventListener('notificationclick', event => { event.notification.close(); event.waitUntil( clients.openWindow(event.notification.data.url) ); });
Build Optimization
-
Modern JavaScript Bundling
Configure webpack to generate both modern and legacy bundles:// webpack.config.js module.exports = [{ entry: './app.js', output: { filename: 'app.legacy.js', path: path.resolve(__dirname, 'dist') }, target: ['web', 'es5'] }, { entry: './app.js', output: { filename: 'app.modern.js', path: path.resolve(__dirname, 'dist') }, target: ['web', 'es2017'] }];
-
Resource Hashing
Ensure cache update validity:output: { filename: '[name].[contenthash:8].js', path: path.resolve(__dirname, 'dist') }
Security Enhancements
-
Content Security Policy (CSP)
Prevent XSS attacks:<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://*.example.com">
-
HTTPS Enforcement
Verify request security in the Service Worker:if (!location.protocol.startsWith('https') && !location.hostname.includes('localhost')) { throw new Error('PWA requires HTTPS in production'); }
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn