乐闻世界logo
搜索文章和话题

How does PWA implement update mechanism? How to ensure users use the latest version?

2月18日 21:57

PWA update mechanism is very important for ensuring users always use the latest version of the app. Here's the complete PWA update process and best practices:

Service Worker Update Process

1. Update Detection

Browsers check for Service Worker updates in the following situations:

  • When navigating to the app page
  • When Service Worker events are triggered (such as push, sync, etc.)
  • Automatic check every 24 hours

2. Update Lifecycle

javascript
// sw.js const CACHE_VERSION = 'v2'; const CACHE_NAME = `my-pwa-${CACHE_VERSION}`; // Install event self.addEventListener('install', event => { console.log('Installing new Service Worker:', CACHE_VERSION); event.waitUntil( caches.open(CACHE_NAME) .then(cache => { return cache.addAll([ '/', '/index.html', '/styles/main.css', '/scripts/app.js', '/images/logo.png' ]); }) .then(() => { // Skip waiting, activate immediately return self.skipWaiting(); }) ); }); // Activate event self.addEventListener('activate', event => { console.log('Activating new Service Worker:', CACHE_VERSION); event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { // Delete old version caches if (cacheName.startsWith('my-pwa-') && cacheName !== CACHE_NAME) { console.log('Deleting old cache:', cacheName); return caches.delete(cacheName); } }) ); }).then(() => { // Immediately control all clients return self.clients.claim(); }) ); });

3. Notify Users of Updates

javascript
// Listen for updates in main thread let newWorker; if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js') .then(registration => { // Check if there's a new Service Worker registration.addEventListener('updatefound', () => { newWorker = registration.installing; newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { // New Service Worker available showUpdateNotification(); } }); }); }); } // Show update notification function showUpdateNotification() { const notification = document.createElement('div'); notification.innerHTML = ` <div class="update-notification"> <span>New version available</span> <button id="update-btn">Update Now</button> <button id="dismiss-btn">Later</button> </div> `; notification.style.cssText = ` position: fixed; bottom: 20px; right: 20px; background: #007bff; color: white; padding: 15px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 9999; font-family: Arial, sans-serif; `; document.body.appendChild(notification); // Update now button document.getElementById('update-btn').addEventListener('click', () => { newWorker.postMessage({ action: 'skipWaiting' }); window.location.reload(); }); // Later button document.getElementById('dismiss-btn').addEventListener('click', () => { notification.remove(); }); }

Manual Update Trigger

javascript
// Manually check for updates async function checkForUpdates() { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.getRegistration(); if (registration) { await registration.update(); console.log('Checked for updates'); } } } // Periodically check for updates (every hour) setInterval(checkForUpdates, 60 * 60 * 1000); // Check for updates when page gets focus window.addEventListener('focus', checkForUpdates);

Cache Update Strategy

1. Versioned Caches

javascript
// Use version numbers to manage caches const CACHE_VERSIONS = { static: 'v1', dynamic: 'v1', images: 'v1' }; const CACHE_NAMES = { static: `static-${CACHE_VERSIONS.static}`, dynamic: `dynamic-${CACHE_VERSIONS.dynamic}`, images: `images-${CACHE_VERSIONS.images}` }; // Update specific cache type function updateCacheType(type) { CACHE_VERSIONS[type] = 'v' + (parseInt(CACHE_VERSIONS[type].slice(1)) + 1); CACHE_NAMES[type] = `${type}-${CACHE_VERSIONS[type]}`; }

2. Smart Cache Update

javascript
self.addEventListener('fetch', event => { const url = new URL(event.request.url); // For HTML documents, always fetch latest version from network if (event.request.mode === 'navigate') { event.respondWith( fetch(event.request) .then(response => { const responseClone = response.clone(); caches.open(CACHE_NAMES.dynamic).then(cache => { cache.put(event.request, responseClone); }); return response; }) .catch(() => caches.match(event.request)) ); } // For static resources, use cache first else if (url.pathname.match(/\.(css|js|png|jpg|jpeg|gif|svg|woff|woff2)$/)) { event.respondWith(cacheFirst(event.request)); } // For API requests, use network first else if (url.pathname.startsWith('/api/')) { event.respondWith(networkFirst(event.request)); } });

Pre-cache Update

javascript
// Pre-cache critical resources during install self.addEventListener('install', event => { const CRITICAL_ASSETS = [ '/', '/index.html', '/styles/main.css', '/scripts/app.js', '/offline.html' ]; event.waitUntil( caches.open(CACHE_NAMES.static) .then(cache => { return cache.addAll(CRITICAL_ASSETS); }) ); }); // Update pre-cache during activate self.addEventListener('activate', event => { event.waitUntil( caches.open(CACHE_NAMES.static) .then(cache => { return cache.addAll([ '/styles/main.css', '/scripts/app.js' ]); }) ); });

Background Sync Update

javascript
// Register background sync self.addEventListener('sync', event => { if (event.tag === 'sync-updates') { event.waitUntil(syncUpdates()); } }); async function syncUpdates() { try { // Get latest resource list const response = await fetch('/api/updates'); const updates = await response.json(); // Update cache const cache = await caches.open(CACHE_NAMES.dynamic); for (const update of updates) { await cache.add(update.url); } console.log('Background sync completed'); } catch (error) { console.error('Background sync failed:', error); } } // Request background sync in main thread async function requestBackgroundSync() { const registration = await navigator.serviceWorker.ready; await registration.sync.register('sync-updates'); }

Update Strategy Selection

1. Immediate Update

javascript
// Force immediate update function forceUpdate() { if ('serviceWorker' in navigator) { navigator.serviceWorker.getRegistration().then(registration => { if (registration && registration.waiting) { registration.waiting.postMessage({ action: 'skipWaiting' }); } }); } }

2. Delayed Update

javascript
// Update when user is idle function updateWhenIdle() { if ('serviceWorker' in navigator) { navigator.serviceWorker.getRegistration().then(registration => { if (registration) { registration.update(); } }); } } // Use requestIdleCallback window.requestIdleCallback(updateWhenIdle);

3. Smart Update

javascript
// Decide update strategy based on network conditions function smartUpdate() { if ('connection' in navigator) { const connection = navigator.connection; // Update on Wi-Fi or fast network if (connection.effectiveType === '4g' || connection.type === 'wifi') { checkForUpdates(); } // Delay update on slow network else { setTimeout(checkForUpdates, 60000); // Update after 1 minute } } }

Update Best Practices

1. Version Management

javascript
// Use semantic versioning const VERSION = { major: 1, minor: 2, patch: 3 }; const CACHE_VERSION = `v${VERSION.major}.${VERSION.minor}.${VERSION.patch}`; // Increment version function incrementVersion(type) { if (type === 'major') { VERSION.major++; VERSION.minor = 0; VERSION.patch = 0; } else if (type === 'minor') { VERSION.minor++; VERSION.patch = 0; } else { VERSION.patch++; } }

2. Rollback Mechanism

javascript
// Keep old version caches const MAX_CACHE_VERSIONS = 3; self.addEventListener('activate', event => { event.waitUntil( caches.keys().then(cacheNames => { // Get all version numbers const versions = cacheNames .filter(name => name.startsWith('my-pwa-')) .map(name => name.replace('my-pwa-', '')) .sort() .reverse(); // Delete old versions, keep recent versions const versionsToDelete = versions.slice(MAX_CACHE_VERSIONS); return Promise.all( versionsToDelete.map(version => { return caches.delete(`my-pwa-${version}`); }) ); }) ); });

3. Update Notification

javascript
// Provide detailed update information function showDetailedUpdateNotification(updateInfo) { const notification = document.createElement('div'); notification.innerHTML = ` <div class="update-notification"> <h3>New Version Available</h3> <p>Version: ${updateInfo.version}</p> <p>Changes:</p> <ul> ${updateInfo.changes.map(change => `<li>${change}</li>`).join('')} </ul> <button id="update-btn">Update Now</button> <button id="dismiss-btn">Later</button> </div> `; document.body.appendChild(notification); document.getElementById('update-btn').addEventListener('click', () => { forceUpdate(); window.location.reload(); }); document.getElementById('dismiss-btn').addEventListener('click', () => { notification.remove(); }); }

Monitoring and Debugging

1. Update Logging

javascript
// Log update events function logUpdateEvent(event) { const logData = { timestamp: Date.now(), event: event.type, version: CACHE_VERSION, userAgent: navigator.userAgent }; // Send to server fetch('/api/update-log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(logData) }); } // Listen to Service Worker events self.addEventListener('install', logUpdateEvent); self.addEventListener('activate', logUpdateEvent);

2. Debug Tools

javascript
// Add debug information if (location.hostname === 'localhost') { self.addEventListener('install', event => { console.log('[SW] Installing:', CACHE_VERSION); }); self.addEventListener('activate', event => { console.log('[SW] Activating:', CACHE_VERSION); }); self.addEventListener('fetch', event => { console.log('[SW] Fetch:', event.request.url); }); }

Summary

Key points for PWA updates:

  1. Version Management: Use version numbers to manage caches
  2. Update Detection: Periodically check for Service Worker updates
  3. User Notification: Notify users promptly when new version is available
  4. Smooth Updates: Provide good update experience
  5. Rollback Mechanism: Keep old versions for rollback
  6. Smart Strategy: Choose update strategy based on network conditions
  7. Monitoring Logs: Record update events for debugging
  8. Testing Validation: Test update process under different conditions
标签:PWA