-
Notifications
You must be signed in to change notification settings - Fork 275
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
Refreshless website deployments – load remote.html using the network-first strategy #1849
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
3de997a
Clean and safe service worker interactions via page reloads
adamziel 9a538f3
Use a much simpler approach to upgrading the web app
adamziel ceb27a4
Adjust e2e tests
adamziel 8d0c25f
Rename cachedFetch to cacheFirstFetch
adamziel eb75ec9
Update the docstring in service-worker.ts
adamziel 17bcdbd
Clarify the names of the caching strategies
adamziel 18af253
Resolve the deployment e2e tests conflicts
adamziel 98559f5
Adjust the e2e test
adamziel b2066fb
Adjust the deployment e2e test
adamziel c26663c
Treat index.html in a network-first way
adamziel cb211eb
Document loading the / in the network-first way
adamziel e01770a
Update E2E
adamziel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 0 additions & 116 deletions
116
packages/php-wasm/web/src/lib/register-service-worker.ts
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,8 +6,9 @@ | |
* ## Playground must be upgraded as early as possible after a new release | ||
* | ||
* New service workers call .skipWaiting(), immediately claim all the clients | ||
* that were controlled by the previous service worker, and forcibly refreshes | ||
* them. | ||
* that were controlled by the previous service worker and clears the offline | ||
* cache. The claimed clients are not forcibly refreshed. They just continue | ||
* running under the new service worker. | ||
* | ||
* Why? | ||
* | ||
|
@@ -16,22 +17,12 @@ | |
* the previous webapp version. Therefore, we can't allow the previous version | ||
* to run when a new version becomes available. | ||
* | ||
* ### Push notifications | ||
* | ||
* It would be supremely useful to proactively notify the webapp after a fresh deployment. | ||
* Playground doesn't do that yet but it likely will in the future. | ||
* | ||
* ## Caching strategy | ||
* | ||
* Playground relies on the **Cache only** strategy. It loads assets from | ||
* the network, caches them, and serves them from the cache. The assumption | ||
* is that all network requests yield the most recent version of the remote file. | ||
* | ||
* This helps us avoid the HTTP cache problem. | ||
* Playground uses caching heavily to achieve great loading speeds and provide | ||
* an offline mode. | ||
* | ||
* ### Cache layers | ||
* | ||
* We're dealing with the following cache layers: | ||
* Caching is a complex beast. Playground deals with the following cache layers: | ||
* | ||
* * HTTP cache in the browser | ||
* * CacheStorage in the service worker | ||
|
@@ -52,17 +43,34 @@ | |
* | ||
* ### CacheStorage in the service worker | ||
* | ||
* This servive worker uses a **Cache only** strategy to ensure all the loaded assets | ||
* come from the same webapp build. | ||
* Playground primarily relies on the **Cache first** strategy. This means assets are: | ||
* | ||
* 1. Loaded from the network without using any HTTP caching. | ||
* 2. Stored in the CacheStorage. | ||
* 3. Served from the CacheStorage on subsequent requests. | ||
* | ||
* While this strategy enables fast load times and an offline experience, it also | ||
* creates a substantial challenge. | ||
* | ||
* The **Cache only** strategy means Playground only loads each assets from | ||
* the network once, caches it, and serves it from the cache from that point on. | ||
* When a new Playground version is deployed, all the clients will load an old | ||
* version of the `remote.html` file on their next visit. Unfortunately, that old | ||
* `remote.html` file contains hardcoded references to assets that may not be | ||
* cached and no longer exist in the new webapp build. | ||
* | ||
* The only times Playground reaches to the network are: | ||
* To solve this problem, we use the **Network first** strategy when `remote.html` | ||
* is requested. This introduces a small network overhead, but it guarantees loading | ||
* the most recent version of `remote.html` and all the referenced assets. | ||
* | ||
* * Before the service worker is installed. | ||
* * When the service worker is being activated. | ||
* * On CacheStorage cache miss occurs. | ||
* Similarly, we use the **Network first** strategy for the `/` path. This is | ||
* useful in situations where the user didn't visit Playground in a while, | ||
* they have a stale version of the `/` route cached, and they open Playground. | ||
* If we loaded the cached version, they'd see the old Playground website on their | ||
* first visit and then the new Playground website only on their second visit. | ||
* | ||
* There's still a small window of time between loading the remote.html file and | ||
* fetching the new assets when a new deployment would break the application. | ||
* This should be very rare, but when it happens we provide an error message asking | ||
* the user to reload the page. | ||
* | ||
* ### Edge Cache on playground.wordpress.net | ||
* | ||
|
@@ -87,7 +95,7 @@ | |
* | ||
* * PR that turned off HTTP caching: https://github.com/WordPress/wordpress-playground/pull/1822 | ||
* * Exploring all the cache layers: https://github.com/WordPress/wordpress-playground/issues/1774 | ||
* * Cache only strategy: https://web.dev/articles/offline-cookbook#cache-only | ||
* * Cache first strategy: https://web.dev/articles/offline-cookbook#cache-falling-back-to-network | ||
* * Service worker caching and HTTP caching: https://web.dev/articles/service-worker-caching-and-http-caching | ||
*/ | ||
|
||
|
@@ -105,7 +113,8 @@ import { wordPressRewriteRules } from '@wp-playground/wordpress'; | |
import { reportServiceWorkerMetrics } from '@php-wasm/logger'; | ||
|
||
import { | ||
cachedFetch, | ||
cacheFirstFetch, | ||
networkFirstFetch, | ||
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. Solid naming. Clear and explicit. ✊ ❤️ |
||
cacheOfflineModeAssetsForCurrentRelease, | ||
isCurrentServiceWorkerActive, | ||
purgeEverythingFromPreviousRelease, | ||
|
@@ -168,17 +177,6 @@ self.addEventListener('install', (event) => { | |
* registration. It shouldn't have unwanted side effects in our case. All these | ||
* pages would get controlled eventually anyway. | ||
* | ||
* ## Upgrading other browser tabs | ||
* | ||
* This activation hook upgrades all the Playground browser tabs to the latest | ||
* service worker version, and that service worker upgrades them the latest version | ||
* of the webapp. | ||
* | ||
* The moment a new Playground version is deployed, the existing browser tabs | ||
* won't be able to load assets from the network. The older Playground version | ||
* they're running contains hardcoded URLs to assets that no longer exist on | ||
* the server. | ||
* | ||
* See: | ||
* * The service worker lifecycle https://web.dev/articles/service-worker-lifecycle | ||
* * Clients.claim() docs https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim | ||
|
@@ -191,35 +189,6 @@ self.addEventListener('activate', function (event) { | |
await purgeEverythingFromPreviousRelease(); | ||
cacheOfflineModeAssetsForCurrentRelease(); | ||
} | ||
|
||
// Reload all clients that were controlled by the previous service worker | ||
// so they can load the new version of the app without any stale assets | ||
// whatsoever. | ||
const windowClients = await self.clients.matchAll({ | ||
type: 'window', | ||
includeUncontrolled: true, | ||
}); | ||
|
||
for (const client of windowClients) { | ||
let url; | ||
try { | ||
url = new URL(client.url); | ||
} catch (e) { | ||
// Ignore | ||
return; | ||
} | ||
|
||
if ( | ||
url.pathname.startsWith('/remote.html') || | ||
url.pathname.startsWith('/scope:') | ||
) { | ||
return; | ||
} | ||
|
||
// @TODO: Store temporary sites in OPFS to avoid destroying in-memory | ||
// changes in tabs that are already open. | ||
client.navigate(client.url); | ||
} | ||
} | ||
event.waitUntil(doActivate()); | ||
}); | ||
|
@@ -273,8 +242,41 @@ self.addEventListener('fetch', (event) => { | |
return; | ||
} | ||
|
||
// Use Cache Only strategy to serve regular static assets. | ||
return event.respondWith(cachedFetch(event.request)); | ||
/** | ||
* Always fetch the fresh version of `/remote.html` and `/` from the network. | ||
* | ||
* This is the secret sauce that enables seamless upgrades of the | ||
* running Playground clients when a new version is deployed on | ||
* the server. | ||
* | ||
* ## The problem with deployments | ||
* | ||
* App deployments remove all the static assets associated with the | ||
* previous app version. Meanwhile, the remote.html file we've cached | ||
* for offline usage still holds references to those assets. | ||
* | ||
* If we just loaded the cached remote.html file, the site would crash | ||
* with seemingly random errors. | ||
* | ||
* Instead, we fetch the most recent version of remote.html from the network. | ||
* It references the static assets that are now available on the server and | ||
* should work just fine. | ||
* | ||
* Relatedly, loading the `/` path using the network first strategy ensures | ||
* that the user sees the latest version of the webapp even if they aleady | ||
* have the previous version cached in CacheStorage. | ||
* | ||
* This very simple resolution took multiple iterations to get right. See | ||
* https://github.com/WordPress/wordpress-playground/issues/1821 for more | ||
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. ❤️ |
||
* details. | ||
*/ | ||
if (url.pathname === '/remote.html' || url.pathname === '/') { | ||
event.respondWith(networkFirstFetch(event.request)); | ||
return; | ||
} | ||
|
||
// Use cache first strategy to serve regular static assets. | ||
return event.respondWith(cacheFirstFetch(event.request)); | ||
}); | ||
|
||
/** | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lovely, informative comments here.