Introduction
A Cloudinary plugin for Workbox

A Cloudinary plugin for Workbox

This is the third and final article in the Network Information API / Workbox.js series discussing how to use a plugin created for Workbox.js by Cloudinary that was recently released.

The first article in the series can be accessed here: Adaptive Image Loading Based on Network Speed
The second article can be found here: Adaptive Image Caching Based on Network Speed with Workbox.js

Where to access the plugin?

The plugin can be installed from npm by executing the npm i cloudinary-workbox-plugin command.

Note that this article has been updated in light of the release of Workbox 4.

Using the plugin

In an earlier article, we discussed how to leverage the Network Information API in conjunction with Workbox.js to create an application that not only functions offline (almost a PWA) but it also caches images adaptively based on the network speed.

That article showcased the true flexibility of Workbox.js, and we introduced a concept of expanding its functionality via a custom plugin.

First of all, let's go ahead and install the plugin by executing npm i cloudinary-workbox-plugin.

The installation will place the plugin itself under the node_modules folder, therefore we'll need to copy it over to a location where we can use it from in the service worker. Copying the plugin can be achieved by using popular plugins for gulp or webpack:

// gulp
const copyCloudinaryPlugin = () => gulp.src(['node_modules/cloudinary-workbox-plugin/dist/cloudinaryPlugin.js']).pipe(gulp.dest('build'));
gulp.task('copy-cloudinary-plugin', copyCloudinaryPlugin);

// webpack
const CopyPlugin = require('copy-webpack-plugin');
plugins: [
    new CleanWebpackPlugin(),
    new CopyPlugin([
      { from: 'node_modules/cloudinary-workbox-plugin/dist/cloudinaryPlugin.js', to: '' },
    ]),

To use the plugin, we'll need to make use of the importScripts() method which synchronously imports scripts into the service worker's scope.

To learn more about importScripts() please consult MDN.

Because we are required to use the import mentioned above we need to create the entire service worker programmatically. Luckily, Workbox.js comes with a package called workbox-build that exposes a generateSW() method. We can also leverage this method from a webpack specific package called workbox-webpack-plugin.

We can bundle the creation of the service worker into a gulp task, or use the aforementioned webpack plugin:

// gulp
const workboxBuild = require('workbox-build');
// ...
const serviceWorkerImportscript = async () => {
  return await workboxBuild
  .generateSW({
    swDest: 'build/sw.js',
    importScripts: ['./cloudinaryPlugin.js'],
    runtimeCaching: [{
      urlPattern: '/api/news',
      handler: 'StaleWhileRevalidate',
      options: {
        cacheName: 'api-cache',
      }
    }, {
      urlPattern: new RegExp('^https:\/\/res\.cloudinary\.com\/.*\/image\/upload\/'),
      handler: 'CacheFirst',
      options: {
        cacheName: 'cloudinary-images',
        plugins: [{
          requestWillFetch: async ({ request }) => cloudinaryPlugin.requestWillFetch(request)
        }]
      }
    }]
  });
};

// webpack
const workboxPlugin = require('workbox-webpack-plugin');
// ...
plugins: [
    new workboxPlugin.GenerateSW({
      swDest: 'sw.js',
      importScripts: ['./cloudinaryPlugin.js'],
      runtimeCaching: [{
        urlPattern: '/api/news',
        handler: 'StaleWhileRevalidate',
        options: {
          cacheName: 'api-cache',
        }
      }, {
        urlPattern: new RegExp('^https:\/\/res\.cloudinary\.com\/.*\/image\/upload\/'),
        handler: 'CacheFirst',
        options: {
          cacheName: 'cloudinary-images',
          plugins: [{
            requestWillFetch: async ({ request }) => cloudinaryPlugin.requestWillFetch(request)
          }]
        }
      }]
    })
  ]

Notice that in the webpack example we do not need to define the final path under swDest to include a folder since it'll take webpack's output.path option.

If you're curious about the difference between generateSW() and injectManifest() take a look at this article by Google.

Notice how we specify the importScripts property to locate the cloudinaryPlugin.js file which we have copied over earlier. This also means that the copy task must come before the service worker build task.

Using the generateSW() method from either gulp or webpack we'll end up having a generated Service Worker file that will have the following content:

importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.0.0/workbox-sw.js");

importScripts(
  "./cloudinaryPlugin.js",
  "precache-manifest.[hash].js"
);

// additional code created by the build process
workbox.routing.registerRoute(/^https:\/\/res.cloudinary.com\/.*\/image\/upload\//, new workbox.strategies.CacheFirst({ "cacheName":"cloudinary-images", plugins: [{ requestWillFetch: async ({ request }) => cloudinaryPlugin.requestWillFetch(request) }] }), 'GET');

With the above, we can see that for every GET request that matches our predefined pattern we'll apply the plugin and place images to the cloudinary-images cache.

Access the code

If you wish to see how the plugin is used, have a look at this repository on GitHub.

Conclusion

In this article, we have reviewed how to use a custom created Workbox.js plugin for adaptively loading images based on the Network Information API. I encourage you to check out the plugin, as well as to play around with both Workbox.js and Cloudinary.

Author

Tamas Piros

Developer Evangelist, experienced Technical Trainer and Google Developer Expert in Web Technologies. Active conference speaker, passionate about the latest & greatest advancements in web technologies.

View Comments
Next Post

ES6 — Set vs Array — What and when?

Previous Post

404 after refreshing the browser for Angular / Vue.js app