Skip to content
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 12 commits into from
Oct 7, 2024

Conversation

adamziel
Copy link
Collaborator

@adamziel adamziel commented Oct 4, 2024

Motivation for the change, related issues

Related to #1821

Changes the webapp upgrade protocol proposed in #1822 to avoid forcibly refreshing the browser tabs with unsaved changes in them.

Technical implementation

Before this PR, the new service worker would clear the offline cache, claim all the active clients, and forcibly refresh them to ensure the latest Playground version is loaded everywhere.

This worked, but every webapp upgrade would destroy any work the user may have done in their temporary Playground. We've explored storing temporary Playgrounds in OPFS, but backtracked because 1) it created an uncanny amount of complexity, and 2) some browsers (e.g. Safari in private mode) don't support OPFS and must rely on a temporary in-memory site.

After this PR, the service worker clears the offline cache, claims all the active clients, but it doesn't forcibly refresh them. Instead, it uses the network-first strategy for the remote.html route and the / route. All the other files are still loaded using the cache-first strategy.

Every Playground that's already open, either temporary or stored, will remain functional. The heavy, asynchronously loaded resources such as PHP.wasm and WordPress.zip were already processed – there's no user flow that could lead to import()-ing a non-existing php.js file.

Every newly opened Playground will be loaded using a freshly downloaded remote.html file containing references to freshly deployed Playground assets. Thus

Other changes

This PR inlines the reusable service worker utilities from packages/php-wasm/web/src/lib/register-service-worker.ts into @wp-playground packages. It turns out, they weren't as reusable and keeping them separate was annoying. I'm now convinced the service worker bits are application specific and splitting them between multiple packages just isn't useful.

Testing instructions

Review the app deployment E2E tests check what we need to check, and them confirm they are green in the CI.

packages/playground/remote/service-worker.ts Outdated Show resolved Hide resolved
packages/playground/remote/service-worker.ts Outdated Show resolved Hide resolved
@adamziel adamziel marked this pull request as ready for review October 7, 2024 15:11
@adamziel adamziel changed the title Clean and safe service worker interactions via page reloads Refreshless website deployments – load remote.html using the network-first strategy Oct 7, 2024
@brandonpayton
Copy link
Member

After this PR, the service worker clears the offline cache, claims all the active clients, but it doesn't forcibly refresh them. Instead, it uses the network-first strategy for the remote.html route and the / route. All the other files are still loaded using the cache-first strategy.

Are there any dynamically requested resources that are dependencies of a given remote.html dependency tree?

If so, I wonder whether we could somehow track whether remote.html clients depend upon a specific cache and only remove an offline cache once no more clients depend upon it.

@adamziel
Copy link
Collaborator Author

adamziel commented Oct 7, 2024

Are there any dynamically requested resources that are dependencies of a given remote.html dependency tree?

PHP.js and zipped WordPress versions for sure, perhaps there's a few more.

If so, I wonder whether we could somehow track whether remote.html clients depend upon a specific cache and only remove an offline cache once no more clients depend upon it.

I would love a flavor of that where we only remove those cached entries that are no longer present in the new version. For example, if the php-4987fba.wasm file is present in both the old and the new version, we don't have to evict it from the cache.

@adamziel adamziel merged commit 7cbbd8c into trunk Oct 7, 2024
9 checks passed
@adamziel adamziel deleted the fix-service-worker-updates branch October 7, 2024 17:44
Copy link
Member

@brandonpayton brandonpayton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a couple of questions, but mostly I reviewed and had happy comments. Thank you!

* 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely, informative comments here.

@@ -105,7 +113,8 @@ import { wordPressRewriteRules } from '@wp-playground/wordpress';
import { reportServiceWorkerMetrics } from '@php-wasm/logger';

import {
cachedFetch,
cacheFirstFetch,
networkFirstFetch,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid naming. Clear and explicit. ✊ ❤️

* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

}
}

const registration = await sw.register(serviceWorkerUrl + '', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that concat just for converting to string? I figured the API would take URL objects but haven't checked.

The spec isn't super straightforward to read on this IMO, but the MS TypeScript type seems to suggest URL is a supported type for that param:
https://github.com/microsoft/TypeScript/blob/b845fd2434f255ba35b0bb143a65172c8985e467/src/lib/webworker.generated.d.ts#L5425

// Proxy the service worker messages to the web worker:
navigator.serviceWorker.addEventListener(
'message',
async function onMessage(event) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's cool how you flattened register-service-worker.ts into this module. I was never sure why it was separated, but it was never important enough to pursue.

maxDiffPixels,
});

await expect(website.page.getByLabel('Open Site Manager')).toBeVisible();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why move away from screenshots? I can think of reasons (e.g., test fragility) but am curious about yours.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

3 participants