-
Notifications
You must be signed in to change notification settings - Fork 299
Cache Playground assets to enable offline support #1535
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 124 commits
6d9e165
75bc2bb
6c2b0ad
57daebb
42be4fc
be0aa0e
19da646
1170ab5
9df228d
fd8dc13
8beee0c
6cf5fef
1f2347e
f1e2784
dbb43aa
1ec9d62
df506d1
3c98912
e703eff
2e3910c
d2ca9c2
4a1f8d0
efb4b57
b2ba22b
1aad0d0
db8c25f
d7d10fb
de6e819
2ac8e60
782d222
c34fd37
5e4d336
aa68518
e5993f8
149cd34
d0254b8
67c3893
de206a1
265088c
a6f20bd
06c4a2f
42e2913
e568b9e
2b09fad
d4dd04c
ebfa224
44c2d8e
8ecc3ad
37c0b91
e1961cc
ee044ae
a01cb3b
f8dab44
ff10c52
5a6b574
6f48337
d34f066
cad7fb0
ba75171
b9ec8c3
1feb41a
03b0138
a2a8d86
f3fe08d
cca0db5
966f7bf
eba73a6
14f3eb1
24f2eeb
1182acb
15717aa
83d4c5d
da7fad9
7c69b09
9aff520
97d95f3
f0378d0
7c205fa
e03c9ad
9f2d128
9f1ad7c
7c886d4
e23cbb8
946a6d7
a746657
33dc880
2567903
29f7868
bdb6d41
194f7e9
da4b2d1
0746125
2110676
83499b5
9657274
733ab4a
8f858d1
e7d7f81
c50cc9e
b37f835
d5b8198
973e88d
34b481c
4adaffa
7eb35d7
6fecfc1
f1e34ec
038982f
b321514
1245978
ec9d917
61d4d23
13ee2f0
582b722
a6a1555
ef533dc
fbe1c65
cb3e05c
ec6a38d
7e82e58
b316de4
ac95c5e
353522c
5eee778
b888b97
a6462e0
c8796d6
f0d00e8
8003609
60c8c60
c4533d9
77a813f
26abb6b
fa8c800
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
|
||
declare const self: ServiceWorkerGlobalScope; | ||
|
||
import { getURLScope, removeURLScope } from '@php-wasm/scopes'; | ||
import { getURLScope, isURLScoped, removeURLScope } from '@php-wasm/scopes'; | ||
import { applyRewriteRules } from '@php-wasm/universal'; | ||
import { | ||
awaitReply, | ||
|
@@ -15,6 +15,7 @@ import { wordPressRewriteRules } from '@wp-playground/wordpress'; | |
import { reportServiceWorkerMetrics } from '@php-wasm/logger'; | ||
|
||
import { buildVersion } from 'virtual:remote-config'; | ||
import { WorkerCache } from './src/lib/worker-caching'; | ||
|
||
if (!(self as any).document) { | ||
// Workaround: vite translates import.meta.url | ||
|
@@ -27,8 +28,66 @@ if (!(self as any).document) { | |
|
||
reportServiceWorkerMetrics(self); | ||
|
||
const cache = new WorkerCache(buildVersion); | ||
|
||
/** | ||
* For offline mode to work we need to cache all required assets. | ||
* | ||
* These assets are listed in the `/assets-required-for-offline-mode.json` file | ||
* and contain JavaScript, CSS, and other assets required to load the site without | ||
* making any network requests. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's make it clear we skipped the |
||
*/ | ||
cache.cacheOfflineModeAssets(); | ||
|
||
/** | ||
* Cleanup old cache. | ||
* | ||
* We cache data based on `buildVersion` which is updated whenever Playground is built. | ||
* So when a new version of Playground is deployed, the service worker will remove the old cache and cache the new assets. | ||
* | ||
* If your build version doesn't change while developing locally check `buildVersionPlugin` for more details on how it's generated. | ||
*/ | ||
cache.cleanup(); | ||
bgrgicak marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, let's make it clear we've skipped the |
||
|
||
/** | ||
* Handle fetch events and respond with cached assets if available. | ||
*/ | ||
self.addEventListener('fetch', (event) => { | ||
const url = new URL(event.request.url); | ||
|
||
/** | ||
* Don't cache requests to the service worker script itself. | ||
*/ | ||
if (url.pathname.startsWith(self.location.pathname)) { | ||
return; | ||
} | ||
|
||
/** | ||
* Don't cache requests to scoped URLs or if the referrer URL is scoped. | ||
* | ||
* These requests are made to the PHP Worker Thread and are not static assets. | ||
*/ | ||
if (isURLScoped(url)) { | ||
return; | ||
} | ||
let referrerUrl; | ||
try { | ||
referrerUrl = new URL(event.request.referrer); | ||
} catch (e) { | ||
// ignore | ||
} | ||
if (referrerUrl && isURLScoped(referrerUrl)) { | ||
return; | ||
} | ||
|
||
/** | ||
* Respond with cached assets if available. | ||
* If the asset is not cached, fetch it from the network and cache it. | ||
*/ | ||
event.respondWith(cache.cachedFetch(event.request)); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the future, we could merge the fetch lister from I didn't want to do it now because I'm not sure if removing |
||
|
||
initializeServiceWorker({ | ||
cacheVersion: buildVersion, | ||
handleRequest(event) { | ||
const fullUrl = new URL(event.request.url); | ||
let scope = getURLScope(fullUrl); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { isURLScoped } from '@php-wasm/scopes'; | ||
|
||
export class WorkerCache { | ||
bgrgicak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
readonly cacheNamePrefix = 'playground-cache'; | ||
|
||
private cacheName: string; | ||
|
||
constructor(cacheVersion: string) { | ||
this.cacheName = `${this.cacheNamePrefix}-${cacheVersion}`; | ||
} | ||
|
||
addCache = async (key: Request, response: Response) => { | ||
const clonedResponse = response.clone(); | ||
const cache = await caches.open(this.cacheName); | ||
await cache.put(key, clonedResponse); | ||
}; | ||
|
||
getCache = async (key: Request) => { | ||
const cache = caches.open(this.cacheName); | ||
return await cache.then((c) => c.match(key, { ignoreSearch: true })); | ||
}; | ||
|
||
isValidHostname = (url: URL) => { | ||
bgrgicak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/** | ||
* The development environment uses Vite which doesn't work offline because it dynamically generates assets. | ||
* Check the README for offline development instructions. | ||
*/ | ||
if ( | ||
url.href.startsWith('http://127.0.0.1:5400/') || | ||
url.pathname.startsWith('/website-server/') | ||
) { | ||
return false; | ||
} | ||
|
||
/** | ||
* Scoped URLs are requests made to the PHP Worker Thread. | ||
* These requests are not cached because they are not static assets. | ||
*/ | ||
if (isURLScoped(url)) { | ||
return false; | ||
} | ||
|
||
/** | ||
* Don't cache PHP files because they are dynamic. | ||
*/ | ||
if (url.pathname.endsWith('.php')) { | ||
return false; | ||
} | ||
return ['playground.wordpress.net', '127.0.0.1', 'localhost'].includes( | ||
bgrgicak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
url.hostname | ||
); | ||
}; | ||
|
||
cleanup = async () => { | ||
const keys = await caches.keys(); | ||
const oldKeys = keys.filter( | ||
(key) => | ||
key.startsWith(this.cacheNamePrefix) && key !== this.cacheName | ||
); | ||
return Promise.all(oldKeys.map((key) => caches.delete(key))); | ||
}; | ||
|
||
cachedFetch = async (request: Request): Promise<Response> => { | ||
const url = new URL(request.url); | ||
if (!this.isValidHostname(url)) { | ||
return await fetch(request); | ||
} | ||
const cacheKey = request; | ||
const cache = await this.getCache(cacheKey); | ||
if (cache) { | ||
return cache; | ||
} | ||
const response = await fetch(request); | ||
await this.addCache(cacheKey, response); | ||
return response; | ||
}; | ||
|
||
cacheOfflineModeAssets = async (): Promise<any> => { | ||
if (!this.isValidHostname(new URL(location.href))) { | ||
return; | ||
} | ||
|
||
const cache = await caches.open(this.cacheName); | ||
|
||
// Get the cache manifest and add all the files to the cache | ||
const manifestResponse = await fetch( | ||
'/assets-required-for-offline-mode.json' | ||
); | ||
const websiteUrls = await manifestResponse.json(); | ||
await cache.addAll([...websiteUrls, ...['/']]); | ||
}; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.