Skip to content

Commit

Permalink
perf(service-worker): split cache by network first and cache first
Browse files Browse the repository at this point in the history
  • Loading branch information
benji6 committed Feb 19, 2024
1 parent 5361a26 commit c6cce20
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 29 deletions.
4 changes: 2 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"private": true,
"scripts": {
"build": "rm -rf dist && mkdir dist && run-p build:* && node scripts/injectCacheList",
"build:parcel": "BUILD_TIME=$(node -e 'console.log((new Date).toISOString())') CACHE_LIST=CACHE_LIST parcel build src/index.html",
"build": "rm -rf dist && mkdir dist && run-p build:* && node scripts/injectCacheLists",
"build:parcel": "BUILD_TIME=$(node -e 'console.log((new Date).toISOString())') CACHE_FIRST_CACHE=CACHE_FIRST_CACHE NETWORK_FIRST_CACHE=NETWORK_FIRST_CACHE parcel build src/index.html",
"build:robots": "touch dist/robots.txt",
"fmt": "npm run test:lint --fix && prettier -u --write '**/*'",
"icons": "node scripts/icons",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,31 @@ const compileCacheList = async (pathToRead = "", cacheList = []) => {
const filteredFiles = files.filter(
(file) => !(ignoreSet.has(file) || file.endsWith(".map")),
);
const cacheList = ["/", ...filteredFiles];
const networkFirstCache = ["/"];
const cacheFirstCache = [];
for (const file of filteredFiles) {
if (/^.+\..{8}\..{2,5}$/.test(file)) cacheFirstCache.push(file);
else networkFirstCache.push(file);
}

// These checks are intentionally paranoid
if (
cacheFirstCache.some((resource) =>
["icon-without-css", "robots", "service-worker", "webmanifest"].some(
(word) => resource.includes(word),
),
) ||
cacheFirstCache.includes("/") ||
cacheFirstCache.some((resource) => networkFirstCache.includes(resource))
)
throw Error("Check cache lists");

const serviceWorkerPath = path.join(BUILD_PATH, "service-worker.js");
const serviceWorker = await fs.readFile(serviceWorkerPath, "utf-8");
return fs.writeFile(
serviceWorkerPath,
serviceWorker.replace("CACHE_LIST", cacheList),
serviceWorker
.replace("CACHE_FIRST_CACHE", cacheFirstCache)
.replace("NETWORK_FIRST_CACHE", networkFirstCache),
);
})();
80 changes: 55 additions & 25 deletions client/src/service-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,34 @@ import { initializeApp } from "firebase/app";
import { join as pathJoin } from "path";

const CACHE_NAME = "v1";
const CACHE_LIST =
const CACHE_FIRST_CACHE =
process.env.NODE_ENV === "production"
? (process.env.CACHE_LIST as string).split(",")
? process.env.CACHE_FIRST_CACHE!.split(",")
: [];
const NETWORK_FIRST_CACHE =
process.env.NODE_ENV === "production"
? process.env.NETWORK_FIRST_CACHE!.split(",")
: [];

const cacheList = [...CACHE_FIRST_CACHE, ...NETWORK_FIRST_CACHE];
const cacheSet = new Set(cacheList);
const cacheFirstSetWithFullUrls = new Set(
CACHE_FIRST_CACHE.map((resource) =>
pathJoin(location.protocol, location.host, resource),
),
);
const networkFirstSetWithFullUrls = new Set(
NETWORK_FIRST_CACHE.map((resource) =>
pathJoin(location.protocol, location.host, resource),
),
);

const sw: any = self;

const firebaseApp = initializeApp(FIREBASE_CONFIG);

getMessaging(firebaseApp);

const cacheSet = new Set(CACHE_LIST);
const cacheListWithHost = CACHE_LIST.map((resource) =>
pathJoin(location.host, resource),
);

const rejectAfterTimeout = (t: number): Promise<never> =>
new Promise((_, reject) =>
setTimeout(() => reject(Error(`Timed out after ${t}ms`)), t),
Expand All @@ -39,7 +51,7 @@ const customFetch = async (request: Request): Promise<Response> => {

sw.oninstall = (event: any) =>
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(CACHE_LIST)),
caches.open(CACHE_NAME).then((cache) => cache.addAll(cacheList)),
);

sw.onactivate = (event: any) => {
Expand All @@ -58,27 +70,45 @@ sw.onactivate = (event: any) => {

/* eslint-disable no-console */
sw.onfetch = (event: any) => {
if (!cacheListWithHost.some((item) => event.request.url.endsWith(item))) {
const isInCacheFirstCache = cacheFirstSetWithFullUrls.has(event.request.url);
const isInNetworkFirstCache = networkFirstSetWithFullUrls.has(
event.request.url,
);
if (!isInCacheFirstCache && !isInNetworkFirstCache) {
console.log("URL not within cache: ", event.request.url);
console.log(cacheFirstSetWithFullUrls, networkFirstSetWithFullUrls);
return;
}
console.time(`${event.request.url}`);
console.time(event.request.url);
if (isInNetworkFirstCache)
return event.respondWith(
(async () => {
const cache = await caches.open(CACHE_NAME);
try {
const networkResponse = await customFetch(event.request);
event.waitUntil(cache.put(event.request, networkResponse.clone()));
console.log(`${event.request.url}: Response from network first`);
return networkResponse;
} catch (e) {
const cachedResponse = await cache.match(event.request);
if (!cachedResponse) throw e;
console.log(`${event.request.url}: Response from cache fallback`);
return cachedResponse;
} finally {
console.timeEnd(event.request.url);
}
})(),
);
event.respondWith(
(async () => {
const cache = await caches.open(CACHE_NAME);
try {
const networkResponse = await customFetch(event.request);
event.waitUntil(cache.put(event.request, networkResponse.clone()));
console.log(`${event.request.url}: Response from network`);
return networkResponse;
} catch (e) {
const cachedResponse = await cache.match(event.request);
if (!cachedResponse) throw e;
console.log(`${event.request.url}: Response from cache`);
caches
.open(CACHE_NAME)
.then((cache) => cache.match(event.request))
.then((cachedResponse) => {
if (!cachedResponse)
throw Error(`${event.request.url}: Not found in cache`);
console.log(`${event.request.url}: Response from cache first`);
console.timeEnd(event.request.url);
return cachedResponse;
} finally {
console.timeEnd(`${event.request.url}`);
}
})(),
}),
);
};

0 comments on commit c6cce20

Please sign in to comment.