Back

MooseDog Studios

Here in Vermont, Convention Takes A Back Seat To Quality

MooseDog Studios Autumn Hero Picture

Dynamic Cache Name As A Static Value

Published: Feb 17, 2024
Updated: Apr 19, 2024

If you have ever engineered a Progressive Web App (PWA), you're well aware that the service-worker.js file is at the heart of this effort. It really does control the offline-capable aspects of your PWA.

The beating heart inside this beating heart is the Cache Storage functionality. The files you want available, even though there's no network connection. Logic tells us that when you update/re-build your site/app, this browser cache should be updated as well, and a versioned cacheName does just that.

On To The Show

To begin this discussion, you could ideally update the cacheName, which is declared right away in your service-worker.js file sent to the browser.

service-worker.js
let cacheName = "myAppCache-v1";

But updating the cacheName version string manually every time there's an update is a giant opportunity to forget all about it. How about a dynamic version name, something like this?

service-worker.js
let cacheName = "myAppCache-qkGOtAv";

Sure seems like a good practice, and with three lines of code, easy and painless:

service-worker.js
let result = Math.random().toString(36).substring(2,9);
result.toString();
let cacheName = `cacheName-${result}`;

Done, right? Oh no! For you see, the browser starts executing your service-worker.js file from the top every time it has service worker lifecycle stuff to do (install, activate, fetch), so it follows that a new cacheName, per the above code, is generated on every service worker type execution in the browser, not just every time you build your site.

The latter being your desired behavior seems to me.

Eleventy and NodeJS are quite capable of generating a dynamic cacheName, but sending to the browser a static cacheName, nonetheless re-versioned. Let's start by creating in the directory of your choosing/creation a file called cache-name.js, and filling it thusly:

cache-name.js
let newName = () => {
  // declare all characters
  const characters ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  let result = ' ';
  const charactersLength = characters.length;
  for ( let i = 0; i < 7; i++ ) {
      result += characters.charAt(Math.floor(Math.random() * charactersLength));
  };

  // just to be safe
  result.toString();

  // very curious, but the generated string has a leading whitespace
  // so....
  // get rid of it!
  let resultOne = result.trimStart();
  
  let cacheName = 'myAppCache-' + resultOne;
  return cacheName
};

// this thing is now importable from another file
module.exports = newName();

In the same directory, create a file called service-worker-base.js. This is where you define all the service worker behavior typically used. I'll just outline it here as the details are a dark and scary rabbit hole.

service-worker-base.js
.
.
self.addEventListener('install', event => {});
.
.
self.addEventListener('activate', event => {});
.
.
self.addEventListener('fetch', event => {});
.
.

Now we're set to bring this all together and create a service worker file that's usuable by the browser. Finally, in the same directory, create a NodeJS file called service-worker-create.js.

service-worker-create.js
const fs = require('fs');
// your location here
const cache = require('./cache-name.js');

function dealWithContent(callback) {

  // read the file contents
  fs.readFile('src/my/path/service-worker-base.js', 'utf8', (err, data) => { 
    if(err) { 
      throw err; 
    } else {
      // if all's well, send the file content with this callback function
      // to be put to use below
      callback(null, data);
    };
  });
};

dealWithContent((err, content) => {

  // a template literal to mash the two data sources into one document:
  // the dynamically created but now static versioned cacheName
  // and the service worker lifecycle instructions
  let newFile = `
    (() => {
    "use strict";

    let cacheName = ${cache};

    ${content};

    })();
    `;
  
  // write the new file to your root directory, or wherever you want
  // tell .eleventy.js where to find it, 
  // do an addPassthroughCopy to get it into your production directory
  fs.writeFile('./my-service-worker.js', newFile, (err) => {
    if (err) {
      console.log('error writing:', err);
    } else {
      console.log('success writing');
    }
  });
});

NodeJS and npm will do the heavy lifting for you when you build the updated version of your site.

package.json
"scripts": {
  "serviceworker:create": "node src/my/path/service-worker-create.js",
  "serviceworker:min": "terser my-service-worker.js -o my-service-worker-min.js",
  "build:start": "rm -rf dist && ELEVENTY_RUN_MODE=build npx @11ty/eleventy",
  "build": "run-s serviceworker:create serviceworker:min build:start"
}

And presto! A dynamically generated cachName value shipped to the browser as a static value in your service worker file. Send it!

So To Recap

TOP

SHARE