Using Workbox + Webpack to precache with Service Worker

Workbox is a new project from Google that makes service worker use-cases such as caching, background sync and queueing of analytics whilst offline easier to code.

Note: Workbox looks like it's still in active development. API and docs can sometimes be out-of-date with recent code changes.

"Workbox is a rethink [on] our previous service worker libraries and tools, sw-toolbox and sw-precache, and is designed to be more modular, flexible, and extensible."

Quote from: https://developers.google.com/web/tools/workbox/

As stated above, Workbox is a rethink on their old service worker libraries and tools. Personally I used sw-precache before but I found the generated service worker code it created difficult to read. Workbox fixes this (in my opinion) as its modular and much easier to reason with. Find a list of all the packages in Workbox here.

Workbox + Webpack

Yes! Workbox comes with official Webpack integration! Previously sw-precache had an unofficial project called sw-precache-webpack-plugin, so its nice that there is support out-of-the-box now.

Basic Configuration

1) Add workbox-webpack-plugin.

yarn add --dev workbox-webpack-plugin  

2) Add plugin to your webpack config.

import WorkboxPlugin from 'workbox-webpack-plugin';  
// ...
plugins: [  
 // ...
 new WorkboxPlugin({
   globDirectory: './dist/',
   globPatterns: ['**/*.{html,js,css}'],
   swDest: './dist/service-worker.js'
 })
]

3) Register the service worker in your JS code.

// register service worker
if ('serviceWorker' in navigator) {  
  navigator.serviceWorker.register('/service-worker.js');
}

4) Run your webpack-dev-server or alternative and you should notice any HTML, JS or CSS files served from dist are now cached in the service worker on repeat visits!

File Revisions

If you inspect your generated service-worker.js file you'll notice it's quite small in size (in comparison to sw-precache). It will contain the file revisions and an import to workbox-sw where the actual work will happen.

Example output:

importScripts('workbox-sw.prod.v1.0.1.js');

/**
 * DO NOT EDIT THE FILE MANIFEST ENTRY
 * ...
 */
const fileManifest = [  
  {
    "url": "/main.css",
    "revision": "c751059c834d06d8a066535504616945"
  },
  {
    "url": "/main.js",
    "revision": "dbe85ddb2050f016120d2e018a677b40"
  }
];

const workboxSW = new self.WorkboxSW();  
workboxSW.precache(fileManifest);

The registered service worker will immediately return cached main.js and main.css files on repeat visits. However depending on the caching strategy used it will also check the revision hash for any changes and fetch and cache the new revisions in the background.

Advanced Configuration

More often that not, you'll need more control over what is cached, strategies used and custom code inside the server worker. You can do this by setting your own service worker and using the WorkboxSW object directly.

Here's part of a configuration I used for a server-side rendered React + Redux app. I did not output a HTML file to the dist folder so my cache was instead managed by routes instead.

1) Update webpack config to add swSrc which is a path to your own custom service worker. This now injects the manifest into your custom service worker and disables the generating of one.

import WorkboxPlugin from 'workbox-webpack-plugin';  
// ...
plugins: [  
 // ...
 new WorkboxPlugin({
   globDirectory: './dist/',
   globPatterns: ['**/*.{html,js,css}'],
   swSrc: './src/client/service-worker.js',
   swDest: './dist/service-worker.js'
 })
]

2) Add custom service worker file.

More example code from the official sites are available here and here.

importScripts('https://unpkg.com/workbox-sw@1.0.1');

// Create Workbox service worker instance
const workboxSW = new WorkboxSW({ clientsClaim: true });

// Placeholder array which is populated automatically by workboxBuild.injectManifest()
workboxSW.precache([]);

// List of strategies to be used
const STRATEGIES = {  
  networkFirst: workboxSW.strategies.networkFirst,
  staleWhileRevalidate: workboxSW.strategies.staleWhileRevalidate
};

// Register png files e.g. https://localhost:3000/images/1.png
workboxSW.router.registerRoute(/\.png$/, STRATEGIES.networkFirst());

// Register example path e.g. https://localhost:3000/example
workboxSW.router.registerRoute('/example', STRATEGIES.staleWhileRevalidate());

// Register express like route paths e.g. https://localhost:3000/list/one
workboxSW.router.registerRoute('/list/:itemId',  
  STRATEGIES.staleWhileRevalidate({
    cacheName: 'cache-with-expiration',
    cacheExpiration: {
      maxEntries: 20,
      maxAgeSeconds: 120
    }
  })
);

With this custom service worker you can start to control caching strategies and more at a granular level.

Setting clientsClaim to true here tells the service worker to take control when it has been activated. You can read more about clientsClaim here and here.

Copy workbox-sw from node-modules

If you don't want to use https://unpkg.com/workbox-sw@1.0.1 but instead use your local node-modules build file with your custom service worker you can do this.

1) Add copy-webpack-plugin and workbox-sw.

yarn add --dev copy-webpack-plugin  
yarn add workbox-sw  

2) Add the plugin to your webpack config.

plugins: [  
    // copy WorkboxSW production build file
    new CopyWebpackPlugin([
      { from: require.resolve('workbox-sw'), to: 'workbox-sw.prod.js' }
    ])
    // ...
]

3) Update your custom service worker file.

importScripts('workbox-sw.prod.js');  
// ... 

Conclusion

Workbox is great and looks like it will only get better! I'm not sure that its ready for production yet. At time of writing it's in active development and there are lots of GitHub issues. However I'll be keeping an close on it for my future Progressive Web Apps.

PWA all the things ❤️.

References