Service Workers

Go Offline or Go Home

👷

Hi! I'm Steve Gherkin

Pickle with 4 years of experience in condiments.

Work at Brinelify.

Expert in juice network solutions.

Have you ever used
service workers?

• Yes, at work.
• Yes, on a personal project.
• No, but I heard about them.
• No, and I don’t know what they are.

How many websites will stay reactive while users are disconnected?

State of JavaScript 2018: Which new browser APIs are gaining traction?

🔥🔥🔥 Hot stuff! 🔥🔥🔥

Can I Use: Service workers are supported by pretty much all browsers

The buzz word: PWA

What makes a website a Progressive Web App?

The Google checklist

1. App served over HTTPS (required for service workers anyway).

2. Responsive design.

3. Accessible offline.

4. App manifest.

5. Time to interactive <10 seconds on first visit in 3G.

6. Cross-browser compatibility.

7. Show something when clicking on a link (loading indicator or skeleton).

8. Unique URLs for each page.

The benefits of service workers

• Offer an offline experience to your users.

• Increase performances by caching anything you want in the browser.

• Pay less by decreasing the load on your servers.

• ​Send notifications remotely to the browser.

How does a worker work?

It just takes a few lines

// index.js
navigator.serviceWorker.register('serviceWorker.js')
    .then(registration => console.log('Service worker registered.'))
    .catch(error => console.error('Service worker could not register.'));
// serviceWorker.js
const addUrlsToCache = async (urls) => {
    const cache = await caches.open('my-cache')
    return await cache.addAll(urls);
}

const handleFetchRequest = async (event) => {
    const result = await caches.match(event.request)
    if (result) {
        return result;
    }

    return fetch(event.request);
}

self.addEventListener('install', event => {
    return event.waitUntil(addUrlsToCache(['/', '/style.css', '/index.js']))
});

self.addEventListener('fetch', event => {
    event.respondWith(handleFetchRequest(event));
});

Google to the rescue (again)

Workbox with Wepack

// webpack.config.js

const { GenerateSW } = require('workbox-webpack-plugin');

module.exports = () => ({
    plugins: [
        new GenerateSW({
            swDest: 'workboxServiceWorker.js',
            maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
            clientsClaim: true,
        })
    ]
});

• Simple configuration.

• Customizable.

• Generates a service worker at compilation.

Better than a mobile app

Gotchas

• The notion of scope in service workers can be confusing.

• Since service workers live on the client, you're not fully in control when to update the script.

• The nature of the beast makes debugging more difficult.

One service worker to rule them all

However, from personal experience, you're better off with a single service worker in control of your whole app.

// index.js
navigator.serviceWorker.register('/assetsServiceWorker.js', { scope: '/assets' })

You can restrict which requests a service worker intercepts (such as requests under the /assets scope, for example).

Service worker lifecycle

Start:        myServiceWorker.register()

Install:      Service worker creates pre-cache.

Wait:         Skipped if there is not an active service
                   worker yet.
                   Prepares to take over old service worker.

Activate:  Serves cache or fetches data.
                   Additionally, creates cache dynamically.

Stop:         myServiceWorker.unregister()

Updating your worker:
A delicate matter

Your service worker will update as expected if your app is not open on your user's browser.

Otherwise, your updated service worker will not activate. As a consequence, your users might get stuck with an old service worker forever!

You can circumvent this behavior by calling self.skipWaiting() in the worker script.

Updating your worker

The brutal way: Desync app from service worker

  • If you call skipWaiting() without refreshing the app, it might try to access deleted cache and all hell might break loose.

The gentle way: A new beginning

  • Prompt user to reload the app after calling skipWaiting().

  • If they agree, it will let the new worker take over.

  • If they don't, the new worker will take control when they close the tab anyway.

Prompting for updates

// index.js
async updateServiceWorker(registration) {
    // trigger update check manually
    const updatedRegistration = await registration.update();
    updatedRegistration.addEventListener('updatefound', () => {
        // the state of the installing SW has changed
        updatedRegistration.installing.addEventListener('statechange', (event) => {
            // new SW is ready for activation
            if (event.target.state === 'installed') {
                promptUserToRefreshApp(updatedRegistration);
            }
        });
    });

    if (updatedRegistration.waiting) {
        promptUserToRefreshApp(updatedRegistration);
    }
}

promptUserToRefreshApp(registration) {
    if (window.confirm('A new version of the app is available. Reload the app?')) {
        activateUpdatedServiceWorker(registration);
        window.location.reload()
    }
}

activateUpdatedServiceWorker = (registration) => {
    registration.waiting.postMessage({ type: 'SKIP_WAITING' });
}

Debugging with
Chrome DevTools

Debugging with
Chrome DevTools

• Keep control of your service workers while developing them.

• Familiarize yourself with the lifecycle of your service workers.

• See which version of a service worker is running.

• Test offline functionalities.

• Nuke a bad service worker.

Resources

ServiceWorker Cookbook: https://serviceworke.rs

Slides: github.com/eugenewebdevs/archive/tree/master/2019-06-27-Service-Workers

State of JavaScript 2018: 2018.stateofjs.com

Can I Use Service Workers: caniuse.com/#search=service%20workers

Is Service Worker Ready? jakearchibald.github.io/isserviceworkerready

The ServiceWorker is coming, look busy: youtube.com/watch?v=SmZ9XcTpMS4

ServiceWorkers Outbreak: youtube.com/watch?v=CPP9ew4Co0M

Keep in touch!

yvesgurcan

Made with Slides.com