question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RFC: Default ServiceWorker to avoid cascading invalidation

See original GitHub issue

Background

Currently in v2, we include a content hash in filenames by default (except entries). Since #3414 this has also included all child bundles as well. This is necessary because the URLs of child bundles are in parents in order to load them, but it means that if a child bundle changes, all of its ancestors must be invalidated, negating any caching benefit. We discussed some possible solutions to this problem, and @philipwalton’s recent article also brought this back into our minds.

Service Workers

The best solution seems to be to use a Service Worker to handle the caching instead of the browser’s HTTP cache, and avoid content hashes in filenames entirely. This means that the filenames will not change between builds, so invalidating a child bundle does not invalidate its parents. In order to handle caching, a Service Worker with a manifest will be generated including hashes for all of the bundles. The Service Worker will request assets from the network, and cache them using the hash from the manifest as a cache key. Whenever the app is deployed, the manifest will be updated, and only the bundles that changed will need to be downloaded since they aren’t in the Service Worker’s cache.

Service workers have one major problem though: They break the browser reload button. If you serve cache first, the service worker will not be updated until AFTER the page has been reloaded and the user is already seeing stale content. By default, the new service worker won’t even be activated until the user closes and reopens all of their tabs for that website either. This can be mitigated by self.skipWaiting() but even then you still need 2 reloads.

The solution I came up with for this is to put the manifest in a separate tiny JSON file parcel-manifest.json that would be deployed alongside the app. The service worker would load this file as part of its installation step, and on top-level page navigation events. This way, the service worker would not change between builds, only the manifest would. This avoids the double reload problem because the service worker would get the refreshed manifest prior to reloading the page.

It would look something like this:

parcel-manifest.json

{
  "index.html": "48f7a7cb",
  "index.js": "6fbe6e5b"
}

service-worker.js (pseudocode)

self.addEventListener('install', event => {
  // fetch and cache manifest
});

self.addEventListener('fetch', event => {
  if (!manifest || event.navigation.mode === 'navigate') {
    // update manifest
  }

  let cacheKey = getCacheKey(manifest, event.request);
  if (cache.match(cacheKey)) {
    return event.respondWith(cached);
  }

  let res = fetch(event.request);
  cache.put(cacheKey, res);
  event.respondWith(res);
});

The downside here is one extra network call for the manifest on each navigation. However, I believe this is still overall better than the current situation with no service worker at all, and it works according to user expectations. Most of the time, you only need to pay the cost of one tiny network call to get the manifest (your entry HTML is now cacheable!), and if anything is invalidated, only the bundles that changed are downloaded instead of all of its ancestors as well. In addition, the app would work offline by default since we’d return cached content using a cached manifest, and we could do the same after a short timeout for lie-fi situations as well.

Fallback

For fallback on old browsers that don’t support service workers, we’d connect this feature to ES module output by default since they have very similar support matrices. If you use <script type="module"> you’d get service worker caching with no content-hashed filenames, while normal scripts would continue to be content-hashed as they are today.

Feedback

Please comment with your feedback! It would be greatly appreciated to hear lots of perspectives on this.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:19 (11 by maintainers)

github_iconTop GitHub Comments

1reaction
jamiebuildscommented, Oct 22, 2019

I’m just going to quickly throw out there that in Parcel 2 I believe this can all be worked out in plugin land. One of the main motivations of making so many things in Parcel exposed as plugins was to enable the community to experiment with ideas like this.

So I would propose this RFC be implemented as “experimental” plugins that we could build out and have audited by everyone interested. I’d be happy to put an app in production with a fairly-stable-but-still-experimental plugin and give feedback.

1reaction
jeffposnickcommented, Oct 21, 2019

FWIW, an example of a service worker that shares conceptual similarities with what Jake describes can be found as part of this AppCache Polyfill library.

One difference is that in that project, IndexedDB is used to share manifest info between client pages and the service worker, keyed on client ID, rather than postMessage().

Notably, this approach only works if you add code to both the window client and service worker.

(It’s fair to say that this implementation also makes compromises on the “preventing performance regressions” side of things, but fidelity with the AppCache specification was prioritized over performance.)

Read more comments on GitHub >

github_iconTop Results From Across the Web

Devon Govett on Twitter: "@jeffposnick Good point. Another option ...
I'd like to offer a default service worker for Parcel users, ... stop content hashing filenames and rely on SW for caching to...
Read more >
Cascading Cache Invalidation - Philip Walton
To use Import Maps to prevent cascading cache invalidation, you have to do three things: 1) configure your bundler to NOT include revision...
Read more >
US20140172944A1 - Invalidation systems, methods, and devices ...
A computer-implemented method includes receiving, at a service running on the hardware, invalidation information relating to one or more resources; ...
Read more >
Strategies for Service Worker Caching for Progressive Web ...
You need to wait out the cache expiry time (usually ~24h if no http cache header is set) to invalidate the service-worker.js file...
Read more >
HTML5 Security - OWASP Cheat Sheet Series
However, there are still some recommendations to keep in mind: ... in latest versions of all current browsers is RFC 6455 (supported by...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found