A modern website that mimics the user experience of a native application
Websites can be progressively enhanced into PWAs
Website or web application are made installable by
Web applications can work offline by adding service workers
A JSON file that contains metadata for your app
The manifest must contain the apps name (or short_name), icons and presentational directives
It can be stored in manifest.json or
manifest.webmanifest
The manifest must be linked from your website's head,
eg.:
<link rel="manifest" href="/data/manifest.json">
manifest.json{
"name": "My First PWA",
"start_url": "/",
"icons": [
{ "src": "../images/logo_512.png", "sizes": "512x512" },
{ "src": "../images/logo_192.png",
"sizes": "192x192",
"purpose": "maskable"
}
],
"display": "standalone",
"background_color": "rgba(49, 54, 57, 1)",
"lang": "en"
}
fullscreen or
standalone as well as minimal-uiUse your browser's developer tools: Application -> Manifest
Note that PWAs can only be installed using a manifest file via
HTTPS
Alternatively, sites can be installed using
HTTP from 127.0.0.1 and localhost
Powerful Web APIs are available in these secure contexts
Service workers progessively enhance the PWA to work offline
This is done by caching selected resources using the service worker
To do this, the service worker has to
To recognize new or update resources, the PWA has to know about the changes
const VERSION = '0.6.0-1';
const CACHE_NAME = `study.find-santa.eu-${VERSION}`;
The VERSION constant must be updated when a resource
was changed
Cached ResourcesThe service worker has to maintain a list of cached (static) resources
const APP_STATIC_RESOURCES = [
'/',
'/index.html',
'/images/coat_of_arms_512.png',
'/images/favicon.png',
'/pos/',
'/pos/index.html',
'/pos/1/',
'/pos/1/index.html',
// ...
'/wmc/1/index.html',
];
install Event Handlerself.addEventListener('install', (event) => {
event.waitUntil(
(async () => {
const cache = await caches.open(CACHE_NAME);
try {
cache.addAll(APP_STATIC_RESOURCES); // atomic operation
} catch (error) { // falling back to individual caching
const promises = APP_STATIC_RESOURCES.map(async (resource) => {
try {
await cache.add(resource);
} catch (err) {
console.warn(`Failed to cache: ${resource}`, err.message);
}
});
await Promise.all(promises);
}
})(),
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(
(async () => {
const names = await caches.keys();
await Promise.all(
names.map((name) => {
if (name !== CACHE_NAME) {
return caches.delete(name);
}
return undefined;
}),
);
await clients.claim();
})(),
);
});
Think of the service worker acting as a proxy server
self.addEventListener('fetch', (event) => {
event.respondWith(
(async () => {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(event.request);
if (cachedResponse) {
return cachedResponse;
}
// try to load missing resource and deal with errors if that fails
})(),
);
});
Place the following code in a script tag of your entry
page
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/scripts/sw.js").then(
(registration) => {
console.log("Service worker registration successful:", registration);
},
(error) => {
console.error(`Service worker registration failed: ${error}`);
},
);
} else {
console.error("Service workers are not supported.");
}
Use your browser's developer tools: Application -> Service Workers
Remote debug your PWA
build number)When the service worker fails, first "unregister" it
While on the "Network" tab, do a hard reload (usually Ctrl + Shift + R)
study.find-santa.eu was turned into a PWA
It only serves the purpose of illustrating PWAs with 11ty
For this POC no plugin automating any of the steps is used
All relevant resources are available for download
PWAs are a powerful but not a universal solution
PWAs are getting increasingly powerful over time