How to Build WorkBox ServiceWorker with Meteor?

I’m working on adding PWA features via WorkBox. The latest WorkBox has npm modules, loaded in the ServiceWorker.js file via import statements. But there’s a gotcha. From the WorkBox Getting Started guide:

Unfortunately, JavaScript modules don’t work inside service workers, so you’ll need a bundler that supports JavaScript modules to compile your service worker to a single file. Popular choices include webpack, Rollup, and Parcel. Please consult the documentation for your bundler of choice for setup and configuration.

Is there a best practices approach to getting Meteor to bundle a ServiceWorker.js file that contains import statements?

I moved ServiceWorker.js to imports/startup/client/.

Here’s my code that’s trying to load ServiceWorker.js. This code is located at imports/startup/client/serviceWorkerInit.js:

//NOTE: MUST use https or localhost to get serviceWorker to be in navigator!

Meteor.startup(() => {
        if (!('serviceWorker' in navigator)) {
            console.log('serviceWorker not in navigator');
            return;
        }
        navigator.serviceWorker
            .register('./ServiceWorker.js')
            .then(() => console.info('service worker registered.'))
            .catch(error => {
                console.log('Error registering serviceWorker: ', error)
            })
    }
);

I’m getting the error:

Error registering serviceWorker: DOMException: Failed to register a ServiceWorker for scope (‘http://localhost:3000/’) with script (‘http://localhost:3000/sw.js’): The script has an unsupported MIME type (‘text/html’).

What am I missing?

First thing that jumps out at me is the fact that the error references sw.js but your code references ./ServiceWorker.js

Secondly ./ServiceWorker.js is a relative path and will only be found if your app loads at the root. If entry happens at any other path it will not succeed.

That’s just a typo in my post. The file is named sw.js in my app, and I renamed it ServiceWorker.js in this post to make it easier to read.

I had it in /public, and it was being found there, but the import statements were throwing an error saying import statements could not be used outside of modules.

I really appreciate your input. Can I provide more info that may be relevant?

Absolutely. More information would be great.

Are you using chrome, or another browser? If your service worker imports code via JavaScript modules and your browser lacks support, this could be the issue. Support outside of workers is pretty good, but not within workers. If this happens to be the case you might want to use another bundler to transpile the service worker and place it in the public folder.

We were not able to figure this out (within the scope of Meteor) so we went with importing the workbox package from their cdn and not bundled with our app

Yes, I am using Chrome.

Interesting! This seems to confirm what @copleykj posted, that it may be necessary to transpile the service worker outside of Meteor and then place it in the public folder.

Can you give an example of what your service worker code looks like?

Yes. This code is copy-pasted from the WorkBox Getting Started page.

import { registerRoute } from 'workbox-routing';
import {
    NetworkFirst,
    StaleWhileRevalidate,
    CacheFirst,
} from 'workbox-strategies';

// Used for filtering matches based on status code, header, or both
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
// Used to limit entries in cache, remove entries after a certain period of time
import { ExpirationPlugin } from 'workbox-expiration';

// Cache page navigations (html) with a Network First strategy
registerRoute(
    // Check to see if the request is a navigation to a new page
    ({ request }) => request.mode === 'navigate',
    // Use a Network First caching strategy
    new NetworkFirst({
        // Put all cached files in a cache named 'pages'
        cacheName: 'pages',
        plugins: [
            // Ensure that only requests that result in a 200 status are cached
            new CacheableResponsePlugin({
                statuses: [200],
            }),
        ],
    }),
);

// Cache CSS, JS, and Web Worker requests with a Stale While Revalidate strategy
registerRoute(
    // Check to see if the request's destination is style for stylesheets, script for JavaScript, or worker for web worker
    ({ request }) =>
        request.destination === 'style' ||
        request.destination === 'script' ||
        request.destination === 'worker',
    // Use a Stale While Revalidate caching strategy
    new StaleWhileRevalidate({
        // Put all cached files in a cache named 'assets'
        cacheName: 'assets',
        plugins: [
            // Ensure that only requests that result in a 200 status are cached
            new CacheableResponsePlugin({
                statuses: [200],
            }),
        ],
    }),
);

// Cache images with a Cache First strategy
registerRoute(
    // Check to see if the request's destination is style for an image
    ({ request }) => request.destination === 'image',
    // Use a Cache First caching strategy
    new CacheFirst({
        // Put all cached files in a cache named 'images'
        cacheName: 'images',
        plugins: [
            // Ensure that only requests that result in a 200 status are cached
            new CacheableResponsePlugin({
                statuses: [200],
            }),
            // Don't cache more than 50 items, and expire them after 30 days
            new ExpirationPlugin({
                maxEntries: 50,
                maxAgeSeconds: 60 * 60 * 24 * 30, // 30 Days
            }),
        ],
    }),
);

Apparently I didn’t fully read the very first post in this thread… :man_facepalming:

Yes, you’ll definitely need to transpile your service worker code before it will run in the browser

OK, very good. At the moment I found a great library of service workers and I’m using that instead. They don’t require imports. I’ll post the link later when I get back to the computer.

Here’s a great library of service workers from Mozilla, with a lot of variations for different use cases. No imports are required. I dropped one into my Meteor app and it started working right away using my previously-configured manifest.json. The code’s available on GitHub.

Then you are not getting the advantages of using workbox. Then everytime there are new browser tech or changes in browser API under the hood, you have to take of it yourself to support the changes.

Importing the workbox package is as simple as using importScripts()

Could you provide sample code please?

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

const { setCacheNameDetails } = workbox.core;

It is indicated in the second section of the link I posted above (linking again)

Awesome. I hadn’t heard about ImportScripts() before. Thanks!

@rjdavid, I’m getting this in Chrome Dev tools:

May I ask what you added to your WorkBox sw to handle this?

Note: I tried adding some offline page code from the WorkBox docs, but it calls for workbox-navigation-preload, and that doesn’t seem to be available via WorkBox ImportScripts().

@rjdavid - when I refresh the page, the error message disappears. In Lighthouse, the error is shown, but if I uncheck “Clear Storage”, the error message is gone. So… I guess it’s working!