mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-18 11:39:08 +00:00
Add a serviceworker (#234)
* Add a serviceworker * rename + fix random extra character * Fixing worker typings * Fixing types properly this time. * Once of those rare cases where this matters. * Naming the things. * Move registration to the app (so we can use snackbar later) * Moving SW plugin later so it picks up things like HTML * MVP service worker * Two stage-service worker * Fix prerendering by conditionally awaiting Custom Elements polyfill. * Fix icon 404's * add doc comment to autoswplugin * Fix type
This commit is contained in:
committed by
Jake Archibald
parent
e4e130c5d6
commit
7d42d4f973
63
src/sw/index.ts
Normal file
63
src/sw/index.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
cacheOrNetworkAndCache, cleanupCache, cacheOrNetwork, cacheBasics, cacheAdditionalProcessors,
|
||||
} from './util';
|
||||
import { get } from 'idb-keyval';
|
||||
|
||||
// Give TypeScript the correct global.
|
||||
declare var self: ServiceWorkerGlobalScope;
|
||||
// This is populated by webpack.
|
||||
declare var BUILD_ASSETS: string[];
|
||||
|
||||
const version = '1.0.0';
|
||||
const versionedCache = 'static-' + version;
|
||||
const dynamicCache = 'dynamic';
|
||||
const expectedCaches = [versionedCache, dynamicCache];
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(async function () {
|
||||
const promises = [];
|
||||
promises.push(cacheBasics(versionedCache, BUILD_ASSETS));
|
||||
|
||||
// If the user has already interacted with the app, update the codecs too.
|
||||
if (await get('user-interacted')) {
|
||||
promises.push(cacheAdditionalProcessors(versionedCache, BUILD_ASSETS));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}());
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(async function () {
|
||||
// Remove old caches.
|
||||
const promises = (await caches.keys()).map((cacheName) => {
|
||||
if (!expectedCaches.includes(cacheName)) return caches.delete(cacheName);
|
||||
});
|
||||
|
||||
await Promise.all<any>(promises);
|
||||
}());
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
// We only care about GET.
|
||||
if (event.request.method !== 'GET') return;
|
||||
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
// Don't care about other-origin URLs
|
||||
if (url.origin !== location.origin) return;
|
||||
|
||||
if (url.pathname.startsWith('/demo-') || url.pathname.startsWith('/wc-polyfill')) {
|
||||
cacheOrNetworkAndCache(event, dynamicCache);
|
||||
cleanupCache(event, dynamicCache, BUILD_ASSETS);
|
||||
return;
|
||||
}
|
||||
|
||||
cacheOrNetwork(event);
|
||||
});
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data === 'cache-all') {
|
||||
event.waitUntil(cacheAdditionalProcessors(versionedCache, BUILD_ASSETS));
|
||||
}
|
||||
});
|
||||
1
src/sw/missing-types.d.ts
vendored
Normal file
1
src/sw/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import '../missing-types';
|
||||
18
src/sw/tsconfig.json
Normal file
18
src/sw/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"lib": [
|
||||
"webworker",
|
||||
"esnext"
|
||||
],
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"noUnusedLocals": true,
|
||||
"sourceMap": true,
|
||||
"allowJs": false,
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
||||
105
src/sw/util.ts
Normal file
105
src/sw/util.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import webpDataUrl from 'url-loader!../codecs/tiny.webp';
|
||||
|
||||
export function cacheOrNetwork(event: FetchEvent): void {
|
||||
event.respondWith(async function () {
|
||||
const cachedResponse = await caches.match(event.request);
|
||||
return cachedResponse || fetch(event.request);
|
||||
}());
|
||||
}
|
||||
|
||||
export function cacheOrNetworkAndCache(event: FetchEvent, cacheName: string): void {
|
||||
event.respondWith(async function () {
|
||||
const { request } = event;
|
||||
// Return from cache if possible.
|
||||
const cachedResponse = await caches.match(request);
|
||||
if (cachedResponse) return cachedResponse;
|
||||
|
||||
// Else go to the network.
|
||||
const response = await fetch(request);
|
||||
const responseToCache = response.clone();
|
||||
|
||||
event.waitUntil(async function () {
|
||||
// Cache what we fetched.
|
||||
const cache = await caches.open(cacheName);
|
||||
await cache.put(request, responseToCache);
|
||||
}());
|
||||
|
||||
// Return the network response.
|
||||
return response;
|
||||
}());
|
||||
}
|
||||
|
||||
export function cleanupCache(event: FetchEvent, cacheName: string, keepAssets: string[]) {
|
||||
event.waitUntil(async function () {
|
||||
const cache = await caches.open(cacheName);
|
||||
|
||||
// Clean old entries from the dynamic cache.
|
||||
const requests = await cache.keys();
|
||||
const promises = requests.map((cachedRequest) => {
|
||||
// Get pathname without leading /
|
||||
const assetPath = new URL(cachedRequest.url).pathname.slice(1);
|
||||
// If it isn't one of our keepAssets, we don't need it anymore.
|
||||
if (!keepAssets.includes(assetPath)) return cache.delete(cachedRequest);
|
||||
});
|
||||
|
||||
await Promise.all<any>(promises);
|
||||
}());
|
||||
}
|
||||
|
||||
export async function cacheBasics(cacheName: string, buildAssets: string[]) {
|
||||
const toCache = ['/', '/assets/favicon.ico'];
|
||||
|
||||
const prefixesToCache = [
|
||||
// First interaction JS & CSS:
|
||||
'first-interaction.',
|
||||
// Main app JS & CSS:
|
||||
'main-app.',
|
||||
// Little icons for the demo images on the homescreen:
|
||||
'icon-demo-',
|
||||
// Site logo:
|
||||
'logo.',
|
||||
];
|
||||
|
||||
const prefixMatches = buildAssets.filter(
|
||||
asset => prefixesToCache.some(prefix => asset.startsWith(prefix)),
|
||||
);
|
||||
|
||||
toCache.push(...prefixMatches);
|
||||
|
||||
const cache = await caches.open(cacheName);
|
||||
await cache.addAll(toCache);
|
||||
}
|
||||
|
||||
export async function cacheAdditionalProcessors(cacheName: string, buildAssets: string[]) {
|
||||
let toCache = [];
|
||||
|
||||
const prefixesToCache = [
|
||||
// Worker which handles image processing:
|
||||
'processor-worker.',
|
||||
// processor-worker imports:
|
||||
'process-',
|
||||
];
|
||||
|
||||
const prefixMatches = buildAssets.filter(
|
||||
asset => prefixesToCache.some(prefix => asset.startsWith(prefix)),
|
||||
);
|
||||
|
||||
const wasm = buildAssets.filter(asset => asset.endsWith('.wasm'));
|
||||
|
||||
toCache.push(...prefixMatches, ...wasm);
|
||||
|
||||
const supportsWebP = await (async () => {
|
||||
if (!self.createImageBitmap) return false;
|
||||
const response = await fetch(webpDataUrl);
|
||||
const blob = await response.blob();
|
||||
return createImageBitmap(blob).then(() => true, () => false);
|
||||
})();
|
||||
|
||||
// No point caching the WebP decoder if it's supported natively:
|
||||
if (supportsWebP) {
|
||||
toCache = toCache.filter(asset => !/webp[\-_]dec/.test(asset));
|
||||
}
|
||||
|
||||
const cache = await caches.open(cacheName);
|
||||
await cache.addAll(toCache);
|
||||
}
|
||||
Reference in New Issue
Block a user