7 min read

The shiny new Service Workers provide powerful features such as a scriptable cache for offline support, background syncing, and push notifications in your browser. Just like Shared Workers, a Service Worker runs in the background, but it can even run when your website is not actually open. These new features can make your web apps feel more like native mobile apps.

Current Browser Support

As of writing this article, Service Workers is enabled in Chrome 41 by default. But this does not mean that all features described in the W3 Service Workers Draft are fully implemented yet. The implementation is in the very early stages and things may change. In this article, we will cover the basic caching features that are currently available in Chrome.

If you want to use Service Workers in Firefox 36, it is currently a flagged feature that you must enable manually. In order to do this, type “about:config” into your URL field and search for “service worker” in order to set the “dom.Service Workers.enabled” setting to true. After a restart of Firefox, the new API is available for use.

Let’s get started – Registering a Service Worker

index.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>ServiceWorker Demo</title>
	</head>
	<body>
		<script>
			if ('serviceWorker' in navigator) {
			navigator.serviceWorker.register('my-service-worker.js')
.then(function(registration) {
				console.log('yay! serviceWorker installed!', registration);
			}, function(error) {
				console.log('something went wrong!:', error);
			});
		}
		</script>
	</body>
</html>

To register a Service Worker, we call navigator.serviceWorker.register() with a path to our Service Worker file. Due to security reasons, it is important that your service worker file is located at the top-level relative to your website. Paths like ‘scripts/my-service-worker.js’ won’t work.

The register method returns a Promise, which is fulfilled when the installation process is successful. The promise can be rejected if you, e.g., have a syntax error in your Service Worker file.

Cool, so let’s review what a basic Service Worker that lives in the ‘my-service-worker.js’ file might look like.

A basic Service Worker

my-service-worker.js:

this.addEventListener('install', function(event) {
	console.log('install event!');
	// some logic here...
});

In our Service Worker file, we can register event listeners for several events that are triggered by the browser. In our case, we listen for the ‘install’ event, which is triggered when the browser sees the Service Worker the first time. Later on, we will add some code to the ‘install’ event listener to make our web app offline ready. For now, we add a simple ‘console.log’ message to be able to check that our event listener function was called.

Now when you open the index.html file in your browser (important: you need to be running a webserver to serve these two files), you should see a success log message in the Chrome developer tools console.

You might wonder why the ‘install event!’ log message from the service worker file is not showing up in the console. This is due to the fact that all Service Workers are running in a separate thread. Next, we will cover how to debug Service Workers.

In Chrome, you can open the URL “chrome://serviceworker-internals” to get a list of Service Workers registered in your browser:

service worker, offline webapps, browser API

When you visit this page, right after you’ve visited the index.html, you should see a worker with the installation status: ‘ACTIVATED’ and running status ‘RUNNING’. Then you will know that everything went fine.

After a while, the worker should be in the running status ‘STOPPED’. This is due to the fact that Chrome completely handles the lifetime of a Service Worker. You have no guarantee how long your service worker runs after the installation, for example.

After digging into the basics of installing Service Workers, we clearly have no advantages yet. So let’s take look at the described offline caching features of Service Workers next.

Make your Web apps Offline Ready

Let’s face it: The Cache Manifest standard to make your apps offline ready has some big disadvantages. It’s not possible to script the caching mechanism in any way. You have to let the browser handle the caching logic. With Service Workers, the browser gives you the moving parts and lets you handle the caching stuff the way you want. So let’s dive into the basic caching mechanisms that Service Workers provide.

Let’s get back to our index.html file and add an image named ‘my-image.png’ to the body that we want to have available when we are offline:

<body>
	<img src="my-image.png" alt="my image" />
	…
</body>

Now that we have an image in our index.html, let’s extend our existing service worker to cache our image ‘my-image.png’ and our index.html for offline usage:

// (1.)
importScripts('./cache-polyfill.js');

// this event listener gets triggered when the browser sees the ServiceWorker the first time
this.addEventListener('install', function(event) {
	console.log('install!');
	// (2.)
	event.waitUntil(
	    caches.open('my-cache')
	      .then(function(cache) {
	        console.log('cache opened');
		// (3.)
	        return cache.addAll([
	        	'/',
	        	'/my-image.png'
	        ]);
	      })
  	);
	
});

this.addEventListener('fetch', function(event) {
  // (4.)
  event.respondWith(
    caches.match(event.request).then(function(response) {
    	if (response) {
		// (5.)
    		console.log('found response in cache for:', event.request.url);
    		return response;
    	}

    	// (6.)
    	return fetch(event.request);
    })
  );
});
  1. We use a global function available in the Service Worker context called ‘importScripts’ that lets us load external scripts for using libraries and other stuff in our Service Worker’s logic. As of writing this article, not all caching API are implemented in the current version of Chrome. This is why we are loading a cache polyfill that adds the missing API that is needed for our application to work in the browser.
  2. In our install event listener, we use the waitUntil method from the provided event object to tell the browser with a promise when the installation process in our Service Worker is finished. The provided promise is the return value of the caches.open() method that opens the cache with name ‘my-cache’.
  3. When the cache has been opened successfully, we add the index.html and our image to cache. The browser pre-fetches all defined files and adds them to the cache. Only when all requests have been successfully executed is the installation step of the service worker finished and the Service Worker can be activated by the browser.
  4. The event listener for the event type ‘fetch’ is called when the browser wants to fetch a resource, e.g., an image. In this listener, you have full control of what you want to send as a response with the event.respondWith() method. In our case, we open up the cache used in the install event to see if we have a cached response for the given request.
  5. When we find a matching response for the given request, we return the cached response. With this mechanism, you are able to serve all cached files, even if you are offline or the webserver is down.
  6. We have no cached response for the given request and the browser will handle the fetching for the uncached file with the shiny new fetch API.

To see if the cache is working as intended, open the index.html file your browser and shut down your web server afterward. When you refresh your page, you should get the index.html and the my-image.png file out of the Service Worker cache:

service worker, offline webapps, browser API

With this few lines of Service Worker code, you have implemented a basic offline-ready web application that caches your index.html and your image file. As already mentioned, this is only the beginning of Service Workers and many more features like push notifications will be added this year.

About the Author

Sebastian Müller is Senior Software Engineer at adesso AG in Dortmund, Germany. He spends his time building Single Page Applications and is interested in JavaScript Architectures. He can be reached at @Sebamueller on Twitter and as SebastianM on Github.

LEAVE A REPLY

Please enter your comment!
Please enter your name here