Technical Foundations of PWAs
Service Workers
The backbone of Progressive Web Apps:
What Are Service Workers?:
- JavaScript files that run separately from the main browser thread
- Act as network proxies between web applications and the network
- Enable offline functionality and background processing
- Persist beyond page refreshes and browser restarts
- Event-driven architecture with lifecycle events
Service Worker Lifecycle:
- Registration
- Installation
- Activation
- Idle
- Termination
- Update
Example Service Worker Registration:
// Check if service workers are supported
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker registered with scope:', registration.scope);
})
.catch(error => {
console.error('Service Worker registration failed:', error);
});
});
}
Example Service Worker Implementation:
// service-worker.js
const CACHE_NAME = 'my-pwa-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/main.js',
'/images/logo.png',
'/offline.html'
];
// Install event - cache critical assets
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Cache opened');
return cache.addAll(urlsToCache);
})
.then(() => self.skipWaiting())
);
});
// Activate event - clean up old caches
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
}).then(() => self.clients.claim())
);
});
// Fetch event - serve from cache, fall back to network
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
// Clone the request
const fetchRequest = event.request.clone();
return fetch(fetchRequest)
.then(response => {
// Check if valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone the response
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
})
.catch(() => {
// Network failed, serve offline page
if (event.request.mode === 'navigate') {
return caches.match('/offline.html');
}
});
})
);
});
Service Worker Capabilities:
- Caching strategies
- Background sync
- Push notifications
- Content prefetching
- Navigation preload
- Periodic background sync
Web App Manifest
Defining the installable experience:
Purpose of the Manifest:
- Enables “Add to Home Screen” functionality
- Defines how the app appears when installed
- Specifies launch behavior and orientation
- Sets theme colors and icons
- Controls display mode (fullscreen, standalone, etc.)
Example Web App Manifest:
{
"name": "Weather PWA",
"short_name": "Weather",
"description": "Weather forecast information",
"start_url": "/index.html",
"display": "standalone",
"background_color": "#3E4EB8",
"theme_color": "#2F3BA2",
"orientation": "portrait-primary",
"icons": [
{
"src": "/images/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"shortcuts": [
{
"name": "Today's Weather",
"short_name": "Today",
"description": "View today's weather forecast",
"url": "/today",
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
},
{
"name": "Weekly Forecast",
"short_name": "Week",
"description": "View weekly weather forecast",
"url": "/week",
"icons": [{ "src": "/images/week.png", "sizes": "192x192" }]
}
]
}
Key Manifest Properties:
- name: Full application name
- short_name: Name for home screen
- icons: App icons in various sizes
- start_url: Initial URL when launched
- display: Presentation mode
- background_color: Splash screen color
- theme_color: UI theme color
- orientation: Preferred orientation
Linking the Manifest:
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#2F3BA2">
<link rel="apple-touch-icon" href="/images/icons/icon-192x192.png">
Caching Strategies
Approaches for reliable content delivery:
Common Caching Strategies:
- Cache First: Check cache before network
- Network First: Try network, fall back to cache
- Stale-While-Revalidate: Serve cached, update in background
- Cache Only: Only serve from cache
- Network Only: Only serve from network
Example Cache-First Strategy:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Return cached response if found
if (response) {
return response;
}
// Otherwise fetch from network
return fetch(event.request)
.then(response => {
// Don't cache if not valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone and cache the response
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});
Example Stale-While-Revalidate Strategy:
self.addEventListener('fetch', event => {
event.respondWith(
caches.open(CACHE_NAME).then(cache => {
return cache.match(event.request).then(cachedResponse => {
const fetchPromise = fetch(event.request).then(networkResponse => {
// Update cache with fresh response
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
// Return cached response immediately, or wait for network
return cachedResponse || fetchPromise;
});
})
);
});
Choosing the Right Strategy:
- Use Cache First for static assets
- Use Network First for API requests
- Use Stale-While-Revalidate for content that updates periodically
- Use Cache Only for offline-specific assets
- Use Network Only for non-cacheable content