# A Cloudinary plugin for Workbox.js

Source: https://tpiros.dev/blog/a-cloudinary-plugin-for-workbox

Third and final article in the Network Information API / Workbox.js series. This one covers a [plugin created for Workbox.js by Cloudinary](https://www.npmjs.com/package/cloudinary-workbox-plugin) 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](https://www.npmjs.com/package/cloudinary-workbox-plugin) by running `npm i cloudinary-workbox-plugin`.

> This article has been updated following the release of [Workbox 4](https://github.com/GoogleChrome/workbox/releases/tag/v4.0.0).

## 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](https://fullstack-developer.academy/adaptive-image-caching-based-on-network-speed-with-workbox-js/).

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:

```javascript
// 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](https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts).

Because we need that import, we have to build the entire service worker programmatically. Workbox.js ships a package called [workbox-build](https://developers.google.com/web/tools/workbox/modules/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:

```javascript
// 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](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin).

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:

```javascript
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](https://github.com/tpiros/cloudinary-workbox-plugin-example).

# 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.
