(z cache)
Lighthouse
do wstępnego cache'owania
Dlaczego offline-first?
Symulacje fizyczne to strony wymagające dużej mocy obliczeniowej:
Three.js, niestandardowe moduły WASM i workery symulacji to często
200–800 KB na stronę. Przy wolnym lub niestabilnym połączeniu każda
wizyta wiąże się z bolesnym początkowym pobieraniem. Cache'owanie
przeglądarki przez nagłówki Cache-Control pomaga, ale
warstwa service workera daje nam pełną kontrolę nad tym,
które zasoby są cache'owane, kiedy i jak bardzo
nieaktualne mogą być.
Cykl życia service workera
Service worker to skrypt, który przeglądarka instaluje raz, a następnie uruchamia jako trwały proxy w tle. Jego cykl życia ma trzy kluczowe fazy: install (wstępne cache'owanie krytycznych zasobów), activate (czyszczenie starych cache'y) i fetch (przechwytywanie żądań sieciowych).
// sw.js — install: wstępne cache'owanie powłoki i wspólnych zasobów
const PRECACHE = 'shell-v4';
const PRECACHE_URLS = [
'/',
'/index.html',
'/offline.html',
'/shared/theme.css',
'/shared/components.css',
'/shared/components.js',
'/manifest.json',
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(PRECACHE)
.then(cache => cache.addAll(PRECACHE_URLS))
.then(() => self.skipWaiting()) // aktywuj natychmiast
);
});
Strategie cache według typu zasobu
Nie wszystkie zasoby powinny być cache'owane w ten sam sposób. Używamy czterech strategii:
// sw.js — fetch: kierowanie do odpowiedniej strategii
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);
// Pomijamy nie-GET i cross-origin
if (request.method !== 'GET' || url.origin !== location.origin) return;
// Zasoby wersjonowane → cache-first
if (/\.(wasm|js|css)$/.test(url.pathname) && /[?&]v=/.test(url.search)) {
event.respondWith(cacheFirst(request));
return;
}
// Strony HTML → stale-while-revalidate
if (request.headers.get('Accept')?.includes('text/html')) {
event.respondWith(staleWhileRevalidate(request, 'pages-v4'));
return;
}
});
async function staleWhileRevalidate(request, cacheName) {
const cache = await caches.open(cacheName);
const cached = await cache.match(request);
const fetchPromise = fetch(request).then(response => {
cache.put(request, response.clone());
return response;
});
return cached ?? fetchPromise;
}
async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) return cached;
const response = await fetch(request);
const cache = await caches.open('assets-v4');
cache.put(request, response.clone());
return response;
}
Wstępne cache'owanie stron symulacji w czasie działania
Symulacje są zbyt duże, aby wstępnie cache'ować je wszystkie od razu przy instalacji — zajęłoby to kilkaset megabajtów miejsca w cache i opóźniłoby instalację service workera. Zamiast tego cache'ujemy zasoby symulacji przy pierwszej wizycie użytkownika, dzięki czemu druga wizyta jest natychmiastowa.
Dedykowany SIM_CACHE ma politykę maksymalnego wieku:
wpisy starsze niż 7 dni są usuwane przy kolejnym zdarzeniu
activate, korzystając z magazynu metadanych ze znacznikami czasu w
IndexedDB.
Monit instalacji
Gdy spełnione są kryteria instalowalności przeglądarki (HTTPS +
service worker + manifest z ikonami + display: standalone), odpala
się zdarzenie beforeinstallprompt. Przechwytujemy je i
pokazujemy stonowany baner instalacji w naszej stopce:
let deferredPrompt;
window.addEventListener('beforeinstallprompt', e => {
e.preventDefault(); // zatrzymaj domyślny mini-pasek przeglądarki
deferredPrompt = e;
showInstallBanner();
});
function showInstallBanner() {
const banner = document.getElementById('install-banner');
if (!banner) return;
banner.hidden = false;
banner.querySelector('button').addEventListener('click', async () => {
banner.hidden = true;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log('Wynik instalacji:', outcome); // 'accepted' lub 'dismissed'
deferredPrompt = null;
});
}
Wynik PWA Lighthouse 100 — lista kontrolna
-
Service worker z zapasową stroną offline — nasz
offline.htmljest serwowany z cache, gdy sieć zawodzi i nie ma zcache'owanej strony. -
Manifest aplikacji webowej —
manifest.jsonz ikonami 192px i 512px,theme_color,background_color,display: standaloneistart_url. -
Wszędzie HTTPS — nasz hosting zapewnia to
automatycznie na własnej domenie
mysimulator.uk. - Brak mieszanej treści — wszystkie czcionki, skrypty i obrazy są hostowane u nas lub serwowane przez HTTPS CDN z hashami SRI.
- Ikona maskowalna — wpis ikony maskowalnej w manifeście z bezpieczną strefą, dzięki czemu ikona renderuje się poprawnie na każdym kształcie launchera na Androidzie.
- Szybkie pierwsze ładowanie — krytyczny CSS jest wstawiony inline; JS jest odroczony; Three.js ładuje się leniwie wewnątrz iframe każdej symulacji, więc nie blokuje LCP powłoki strony.
Limity pamięci cache: Przeglądarki zazwyczaj przyznają do 60% dostępnego miejsca na dysku na pamięć origin (obejmuje to IndexedDB, CacheStorage i localStorage). Na urządzeniu z 32 GB pamięci i 15 GB wolnego miejsca to ~9 GB. Zostajemy dobrze poniżej tego limitu, usuwając zasoby symulacji starsze niż 7 dni i ograniczając zcache'owane zasoby każdej symulacji do ~5 MB.