Skip to content

Instantly share code, notes, and snippets.

@jeffposnick
Created October 6, 2019 16:54
Show Gist options
  • Save jeffposnick/8b3465e933275f15e6cb1ce6737403c0 to your computer and use it in GitHub Desktop.
Save jeffposnick/8b3465e933275f15e6cb1ce6737403c0 to your computer and use it in GitHub Desktop.
Workbox CDN + JS service worker migrated to a custom bundle + TS equivalent
import {CacheFirst, NetworkOnly} from 'workbox-strategies';
import {cacheNames} from 'workbox-core';
import {cleanupOutdatedCaches, getCacheKeyForURL, precacheAndRoute} from 'workbox-precaching';
import {ExpirationPlugin} from 'workbox-expiration';
import {initialize as initializeOfflineAnalytics} from 'workbox-google-analytics';
import {registerRoute, setCatchHandler} from 'workbox-routing';
import {RouteHandlerCallbackOptions} from 'workbox-core/types';
import {skipWaiting} from 'workbox-core';
import nunjucks from 'nunjucks/browser/nunjucks';
precacheAndRoute(self.__WB_MANIFEST);
cleanupOutdatedCaches();
async function getPrecachedResponse(url: string) {
const cacheKey = getCacheKeyForURL(url);
if (!cacheKey) {
throw new Error(`${url} is not in the precache manifest.`);
}
const cache = await caches.open(cacheNames.precache);
const cachedResponse = await cache.match(cacheKey);
if (!cachedResponse) {
throw new Error(`${url} is not precached.`);
}
return cachedResponse;
}
const CacheStorageLoader = nunjucks.Loader.extend({
async: true,
getSource: async function(name: string, callback: Function) {
try {
const path = `/_posts/_includes/${name}`;
const cachedResponse = await getPrecachedResponse(path);
const src = await cachedResponse.text();
callback(null, {src, path, noCache: false});
} catch(error) {
callback(error);
}
}
});
const nunjucksEnv = new nunjucks.Environment(
new CacheStorageLoader()
);
let _site: {string: any};
async function getSiteData() {
if (!_site) {
const siteDataResponse = await getPrecachedResponse('/_posts/_data/site.json');
_site = await siteDataResponse.json();
}
return _site;
}
const postHandler = async (options: RouteHandlerCallbackOptions) => {
const {params} = options;
if (!(params && Array.isArray(params))) {
throw new Error(`Couldn't get parameters from router.`);
}
const site = await getSiteData();
// params[3] corresponds to post.fileSlug in 11ty.
const cachedResponse = await getPrecachedResponse(`/_posts/${params[3]}.json`);
const context = await cachedResponse.json();
context.site = site;
context.content = context.html;
const html: string = await new Promise((resolve, reject) => {
nunjucksEnv.render(
context.layout,
context,
(error: Error | undefined, html: string) => {
if (error) {
return reject(error);
}
return resolve(html);
}
);
});
const headers = {
'content-type': 'text/html',
};
return new Response(html, {headers});
};
registerRoute(
new RegExp('/(\\d{4})/(\\d{2})/(\\d{2})/(.+)\\.html'),
postHandler
);
registerRoute(
new RegExp('/assets/images/'),
new CacheFirst({
cacheName: 'images',
plugins: [
new ExpirationPlugin({
maxEntries: 20,
}),
],
})
);
// If anything goes wrong when handling a route, return the network response.
setCatchHandler(new NetworkOnly());
initializeOfflineAnalytics();
skipWaiting();
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.0.0-alpha.1/workbox-sw.js');
importScripts('https://cdn.jsdelivr.net/npm/[email protected]/browser/nunjucks.min.js');
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
workbox.precaching.cleanupOutdatedCaches();
const CacheStorageLoader = nunjucks.Loader.extend({
async: true,
getSource: async function(name, callback) {
try {
const path = `/_posts/_includes/${name}`;
const cachedResponse = await caches.match(
workbox.precaching.getCacheKeyForURL(path), {
cacheName: workbox.core.cacheNames.precache,
}
);
const src = await cachedResponse.text();
callback(null, {src, path, noCache: false});
} catch(error) {
callback(error);
}
}
});
const nunjucksEnv = new nunjucks.Environment(
new CacheStorageLoader()
);
let _site;
async function initSiteData() {
if (!_site) {
const siteDataResponse = await caches.match(
workbox.precaching.getCacheKeyForURL('/_posts/_data/site.json'), {
cacheName: workbox.core.cacheNames.precache,
}
);
_site = await siteDataResponse.json();
}
return _site;
}
const postHandler = async ({params}) => {
const site = await initSiteData();
// params[3] corresponds to post.fileSlug in 11ty.
const cachedResponse = await caches.match(
workbox.precaching.getCacheKeyForURL(`/_posts/${params[3]}.json`), {
cacheName: workbox.core.cacheNames.precache,
}
);
const context = await cachedResponse.json();
context.site = site;
context.content = context.html;
const html = await new Promise((resolve, reject) => {
nunjucksEnv.render(
context.layout,
context,
(error, html) => {
if (error) {
return reject(error);
}
return resolve(html);
}
);
});
const headers = {
'content-type': 'text/html',
};
return new Response(html, {headers});
};
workbox.routing.registerRoute(
new RegExp('/(\\d{4})/(\\d{2})/(\\d{2})/(.+)\\.html'),
postHandler
);
workbox.routing.registerRoute(
new RegExp('/assets/images/'),
new workbox.strategies.CacheFirst({
cacheName: 'images',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 20,
}),
],
})
);
workbox.routing.registerRoute(
new RegExp('https://storage\\.googleapis\\.com/workbox-cdn/releases/.+/workbox-window\\.prod\\.mjs'),
new workbox.strategies.CacheFirst()
);
// If anything goes wrong when handling a route, return the network response.
workbox.routing.setCatchHandler(new workbox.strategies.NetworkOnly());
workbox.googleAnalytics.initialize();
workbox.core.skipWaiting();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment