How to cache signed files (Service worker) - javascript

Several front-end applications implement a signature template in the files to control the cache, and each change in the file that signature changes.
How do I get the Service Worker to handle these signatures and cache?
Signature Examples:
sw-d58e3582afa99040e27b92b13c8f2280.js
sw.js?_gc=20180101
I'm working on an application that is already ready, trying to implement service worker for certain features to become available offline.
Example, in this section I need to say what I will cache, however, the application changes the signatures of the file to control the cache. (today it's like this)
caches.open('my-cache').then(function(cache) {
return cache.addAll([
'/index.html',
'/styles.css',
'/main.js'
]);})
The application is always changing "styles.css" to "styles.css? V = 1527624807103_1" (timestamp) As far as I understand, "styles.css" is not the same as "styles.css? V = 1527624807103_1".

You should not version or add a signture parameter to the URL used for your actual, top-level service worker file. As explained in "The Service Worker Lifecycle":
It can land you with a problem like this:
index.html registers sw-v1.js as a service worker.
sw-v1.js caches and serves index.html so it works offline-first.
You update index.html so it registers your new and shiny sw-v2.js.
If you do the above, the user never gets sw-v2.js, because
sw-v1.js is serving the old version of index.html from its cache.
You've put yourself in a position where you need to update your
service worker in order to update your service worker.
As for the actual URLs that are included inside of your service worker file, which may legitimately contain versioning/hashes/signatures, your best bet is to use a tool that integrates with your build process and will generate the list of URLs based on the latest versions of each file that should be precached.
Workbox is one such tool, and there are others, like sw-precache (a precursor to Workbox), and offline-plugin.

Related

Precaching with Stale While Revalidate Strategy in Workbox

Question
Is it possible to precache a file using a different strategy? i.e. Stale While Revalidate?
Or, should I just load the script in the DOM and then add a route for it in the worker with the correct strategy?
Background
This is quite a weird case so I will try to explain it as best I can...
We have two repos; The PWA and The Games
Both are statically hosted on the same CDN
Due to the Games repo being separate, the PWA has no access to the versioning of the game js bundles
Therefore, the solution I have come up with is to generate an unversioned manifest (game-manifest.js) in the Games build
The PWA will then precache this file, loop through it's contents, and append each entry to the existing precache manifest
However, given the game-manifest.js has no revision and is not hashed, we need to apply either a Network First, or Stale While Revalidate strategy in order for the file to be updated when new versions become available
See the following code as a clearer example of what I am trying to do:
import { precacheAndRoute } from 'workbox-precaching';
// Load the game manifest
// THIS FILE NEEDS TO BE PRECACHED, but under the strategy
// of stale while revalidate, or network first.
importScripts('example.cdn.com/games/js/game-manifest.js');
// Something like...
self.__gameManifest.forEach(entry => {
self.__precacheManifest.push({
url: entry
});
});
// Load the assets to be precached
precacheAndRoute(self.__precacheManifest);
Generally speaking, it's not possible to swap in an alternative strategy when using workbox-precaching. It's always going to be cache-first, with the versioning info in the precache manifest controlling how updates take place.
There's a larger discussion of the issue at https://github.com/GoogleChrome/workbox/issues/1767
The recommended course of action is to explicitly set up runtime caching routes using the strategy that you'd prefer, and potentially "prime" the cache by adding entries to it in advance during the install step.

Handle "Loading chunk failed" errors with Lazy-loading/Code-splitting

We are developing a Vue.js application based on Vue CLI 3 with Vue Router and Webpack. The routes are lazy-loaded and the chunk file names contain a hash for cache busting. In general, everything is working fine.
However, there is a problem during the deployment. Steps to reproduce are the following.
User opens the application (let's assume route "/"), thus the main chunk file is loaded.
We change something in the application and deploy a new version.
Old chunk files are removed
New chunk files are being added (i.e. hashes in the chunk file names change)
User clicks a link to another route (e.g. "/foo")
An error occurs as the application tries to load a chunk file that has been renamed: Error: "Loading CSS chunk foo failed.
(/assets/css/foo.abc123.css)" (this might be CSS or JavaScript)
What is the best way to avoid errors like this?
One approach that should work is just to retain old chunk files and delete them at a later time. That, however, complicates the deployment of new versions as you need to keep track of old versions and always also deploy the old chunk files with the new version.
Another (naive) approach is to just reload as soon as such an error is detected (e.g. Vue Lazy Routes & loading chunk failed). It somewhat works, but it reloads the old route, not the new one. But at least it ensure that consecutive route changes work again.
Any other ideas? Maybe there is something in webpack that could fix this?
DoNOT cache the entry file(usually index.html).
We add:
expires 0;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate';
in our nginx server config.
Then, after you refreshed the client's code, you can use the vue-router's error hook to detect the error and do something properly.
As long as you have a versioned API, you can use the old app files (just leave them on the server and delete after a vew days).
You will get problems as soon as your API changes during deployments.
I assume, you deploy a new API each time you deploy new JS code.
Then you can:
Pass on the API version (simply use the git hash) to the application as header with every response (JS resources, CSS, API requests, 404 responses)
Store the API version in your main JS entry point (or make it accessible somehow, e.g. as generated constant)
On each server response, check if the Server version matches your main client version.
If it does not: Display a prominent warning to the user (like the cookie banners) that he should reload the page (=> allows the user to save chnages in hope the API did not change for that save button).
For async components, we display normal 'not found' messages if loading fails, together with a reload button that appears instead of the component. Reloading without user interaction will cause a lot of confusion.

Change React Webpack application file and api path

I'm working on an app that is going to be deploy on-prem (no cloud) and there are requirement to deploy the application to different server path. For example in one company it might be company1.com and on another it might be company2.com/app because they already have a server and want to deploy it on the same server under a different context path.
The problem is in Webpack we compile the app and create HTML+JS+CSS files. The HTML and JS files have the server context path (part after the domain name) hardcoded into the code. For example loading the JS files will be done with <script src="/hello.js" /> so if the app will be deploy to company2.com/app I need the script tag to be <script src="/app/hello.js" />
I'm looking for way to change the server context path dynamically preferably using environment variable.
For compare we use Spring on the server side and there we can define env-var server.contextPath which will change the context path in which the app works.
If it changes anything the app is deployed as docker image.
Any ideas how to implement such thing?
That's an interesting question, and there are some ways you can resolve that. I'll list some options below.
I'm looking for way to change the server context path dynamically preferribly using environment variable.
I will list some other options, but the first one is using env vars.
Using Environment Variables When Building
webpack has a plugin (DefinePlugin) that allows us to set environment variables to the javascript code.
Then, you will be able to get those environment variables using process.env. For exemple, if you have the following configuration:
plugins: [
new webpack.DefinePlugin({
fruit: JSON.stringify('orange'),
MY_VAR: JSON.stringify('value'),
API_URL: JSON.stringify('http://some-api')
})
]
Then, in your code you can get them using:
console.log(process.env.fruit); // "orange"
console.log(process.env.MY_VAR); // "value"
console.log(process.env.API_URL); // "http://some-api"
This said, you can do something like this:
Passing environment-dependent variables in webpack
Then, for example, you can just use process.env.API_URL in your code, instead of using it hardcoded.
IMPORTANT: this method will only work if you have can build your code before releasing it to production. (I think it's not a problem for you).
I think this is the best option, because your code will be "clean", it is more "customizable" and it will "simply work", regardless your environment.
Using a Map
You can have some application logic to decide your variables. For example, you may "look" to the URL (i.e. location.href) and decide the values to use based on this address.
let api;
if (domain === 'domain1.com') api = 'api1';
if (domain === 'domain2.com') api = 'api2';
Using an Extra HTTP Server
You can always point to a hardcoded path in your server, lets say /api. Then, in your code, you will point to /api. So, all your requests will go to /api.
You will need to have an HTTP Server (it can be a Nginx, a NodeJS, whatever you want) listening to /api and then have this "routing" logic there.
It will work fine, but you will need to control the deployment of this HTTP server. This may not be a good option for you, but it may suit your needs.
One advantage is that you'll be able to only change the code of this HTTP server when changing some routes, without need to deploy your front-end code again.
I think that sums it.
Hope it helps!

Does sw-precache activation of new service worker guarantees cache busting?

I am using sw-precache along with sw-toolbox to allow offline browsing of cached pages of an Angular app.
The app is served through a node express server.
One problem we ran into is that the index.html sometimes doesn't seem to be updated in the cache although other assets has been updated on activation of new service worker.
This leaves users with an outdated index.html that is trying to load no longer existing versioned asset in this case /scripts/a387fbeb.modules.js.
I am not entirely sure what's happening, because it seems that on different browsers where the index.html has been correctly updated have the same hash.
On one browser outdated (problematic) Index.html
(cached with 2cdd5371d1201f857054a716570c1564 hash) includes:
<script src="scripts/a387fbeb.modules.js"></script>
in its content. (this file no longer exists in the cache or on remote).
On another browser updated (good) index.html
(cached with the same 2cdd5371d1201f857054a716570c1564) includes:
<script src="scripts/cec2b711.modules.js"></script>
These two have the same cache, although the content that is returned to the browsers are different!
What should I make of this? Does this mean that sw-precache doesn't guarantee atomic cache busting when new SW activates? How can one protect from this?
If these help, this is the generated service-worker.js file from sw-precache.
Note: I realize I can use remoteFirst strategy (at least for index.html) to avoid this. But I'd still like to understand and figure out a way to use cacheFirst strategy to get the most out of performance.
Note 2: I saw in other related questions that one can change the name of the cache to force bust all the old cache. But this seems to beat the idea of sw-precache only busting updated content? Is this the way to go?
Note 3: Note that even if I hard reload the browser where the website is broken. The site would work because it would skip service worker cache but the cache would still be wrong - the service worker doesn't seem to activate - my guess because this specific SW has been activated already but failed at busting the cache correctly. Subsequent non-hard-refresh visits would still see the broken index.html.
(The answers here are specific to the sw-precache library. The details don't apply to service workers in general, but the concepts about cache maintenance may still apply to a wider audience.)
If the content of index.html is dynamically generated by a server and depends on other resources that are either inlined or referenced via <script> or <link> tags, then you need to specify those dependencies via the dynamicUrlToDependencies option. Here's an example from the app-shell-demo that ships as part of the library:
dynamicUrlToDependencies: {
'/shell': [
...glob.sync(`${BUILD_DIR}/rev/js/**/*.js`),
...glob.sync(`${BUILD_DIR}/rev/styles/all*.css`),
`${SRC_DIR}/views/index.handlebars`
]
}
(/shell is used there instead of /index.html, since that's the URL used for accessing the cached App Shell.)
This configuration tells sw-precache that any time any of the local files that match those patterns change, the cache entry for the dynamic page should be updated.
If your index.html isn't being generated dynamically by the server, but instead is updated during build time using something like this approach, then it's important to make sure that the step in your build process that runs sw-precache happens after all the other modifications and replacements have taken place. This means using something like run-sequence to ensure that the service worker generation isn't run in parallel with other tasks.
If the above information doesn't help you, feel free to file a bug with more details, including your site's URL.

Proper headers for pseudo static JavaScript file

I have a python 2.7 app on Google Appengine. One of the JS files is served via a python script, not a standard static handler. The app.yaml config is shown below:
- url: /js/foo.js
script: python.js.write_javascript.app
secure: optional
The request for foo.js is part of a code snippet clients, of our service, place on their website, so it can't really be updated. python.js.write_javascript.app basically just reads in a JS template file, substitutes in a few customer specific values and prints to the browser.
What I'm wondering is, how do we set the correct headers so this request is cached correctly. Without any custom headers, appengine's default is to tell the browser never to cache this. This is obviously undesirable because it creates unnecessary load on our app.
Ideally, I would like to have browsers make a new request only when the template has been updated. Another option would be to cache per session.
Thanks
Well
It looks like Google handles this automatically. I just print it, using the correct JavaScript headers but without any cache headers and Google's CDN caches it for me. I'm not sure what the default cache lifetime is but I saw no increase in instances or cost by implementing this.
It seems Google just takes care of it for me.

Categories

Resources