Skip to main content

A Cloudinary plugin for Workbox.js

3 min read

Older Article

This article was published 7 years ago. Some information may be outdated or no longer applicable.

Third and final article in the Network Information API / Workbox.js series. This one covers a plugin created for Workbox.js by Cloudinary that was recently released.

The first article in the series: Adaptive Image Loading Based on Network Speed

The second article: Adaptive Image Caching Based on Network Speed with Workbox.js

Where to access the plugin?

Grab it from npm by running npm i cloudinary-workbox-plugin.

This article has been updated following the release of Workbox 4.

Using the plugin

In an earlier article, we walked through how to wire up the Network Information API with Workbox.js to build an application that functions offline and caches images adaptively based on network speed.

That article showed off the real flexibility of Workbox.js, and we introduced a concept of extending its functionality via a custom plugin.

First, install the plugin: npm i cloudinary-workbox-plugin.

The installation drops the plugin under node_modules, so we need to copy it somewhere the service worker can reach. Popular gulp or webpack plugins handle this:

// 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 need importScripts(), which synchronously pulls scripts into the service worker’s scope.

For more on importScripts(), see MDN.

Because we need that import, we have to build the entire service worker programmatically. Workbox.js ships a package called workbox-build that exposes a generateSW() method. There’s also a webpack-specific package called workbox-webpack-plugin.

We can bundle the service worker creation into a gulp task, or wire it through the 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),
            },
          ],
        },
      },
    ],
  }),
];

In the webpack example, we don’t need to specify a folder path in swDest since it picks up webpack’s output.path option.

Curious about the difference between generateSW() and injectManifest()? Check this article by Google.

Notice the importScripts property pointing to the cloudinaryPlugin.js file we copied over earlier. The copy task must run before the service worker build task.

Using generateSW() from either gulp or webpack produces a Service Worker file with the following shape:

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'
);

Every GET request matching our predefined pattern triggers the plugin and slots images into the cloudinary-images cache.

Access the code

To see the plugin in action, have a look at this repository on GitHub.

Conclusion

We’ve walked through how to wire up a custom Workbox.js plugin that adaptively loads images based on the Network Information API. Worth checking out if you’re working with both Workbox.js and Cloudinary.