First look at service worker
A service worker is a script that your browser runs in the background, separate from a web page, opening the door to features that don't need a web page or user interaction.
More precisely, service worker is used for boosting the user experience of a web application, which allows you to programmatically managing a cache of responses and support offline experiences, hence giving developers complete control over web from client side.
You might reference google web (opens new window) for official details.
In this article, I'm going to re-state what I think it is important for service worker and how to playaround with it in a demo web.
# Concept
There are few state of service worker which I think matters in the demo below:
installing
- with event
install
- this happens when the browser detect there's a byte difference between the registered service worker javascript file
- with event
activated
- with event
activate
- this happens after a new service worker installed. However, if the old page is still active, which means the old service worker still in use, the service worker will waiting for
activate
until old page is closed (old service worker is terminated) and hence triggeractivate
event become activated.
- with event
fetch
- with event
fetch
- executed for the resources fetch
- with event
# Setup
# Debugging tool
So far, chrome is supporting a native service worker debugging tool, which you might find it in chrome://inspect/#service-workers
. Firefox don't have native tool for it, you might need search for a plugin to try out.
Only installed service worker can be viewed from this tool, hence make sure you have at least one service worker installed to your browser before visit that page. Example:
# Service worker demo
Demo file structure
$ tree
.
├── app.js
├── file_cache_on_install
│ ├── img.jpg
│ └── install.json
├── file_runtime_cache
│ ├── img.jpg
│ └── runtime.json
├── index.html
└── sw.js
2 directories, 7 files
2
3
4
5
6
7
8
9
10
11
12
13
index.html
, the entry point of the web app
<html>
<head>
<title>Web workers</title>
<style>
img {
width: 25%;
}
* {
box-sizing: border-box;
}
/* Create two equal columns that floats next to each other */
.column {
float: left;
width: 50%;
padding: 10px;
height: 300px; /* Should be removed. Only for demonstration */
}
/* Clear floats after the columns */
.row:after {
content: '';
display: table;
clear: both;
}
</style>
<script src="app.js">
// app.js is mainly used for register service worker to this html
</script>
<script type="text/javascript">
// this is used for load json file and display to html for display
function loadFileToId(filePath, idName) {
fetch(filePath)
.then(function(response) {
return response.json();
})
.then(function(mydata) {
var div = document.getElementById(idName);
for (var i = 0; i < mydata.length; i++) {
div.innerHTML =
div.innerHTML +
"<p class='inner' id=" +
i +
'>' +
mydata[i].name +
'</p>';
}
});
}
function load() {
loadFileToId('./file_cache_on_install/install.json', 'on_install_data');
loadFileToId('./file_runtime_cache/runtime.json', 'on_runtime_data');
}
</script>
</head>
<body onload="load()">
<div class="row">
<div class="column" style="background-color:#aaa;">
<h2>JSON on install</h2>
<div id="on_install_data"></div>
</div>
<div class="column" style="background-color:#bbb;">
<h2>JSON on runtime</h2>
<div id="on_runtime_data"></div>
</div>
</div>
<img src="file_runtime_cache/img.jpg" />
<img src="file_cache_on_install/img.jpg" />
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
above file is linked to app.js
as a main script, which register a service worker:
console.log("What's in navigator:", navigator);
console.log("'serviceWorker' in navigator", 'serviceWorker' in navigator);
if ('serviceWorker' in navigator) {
const serviceWorkerJsFilePath = './sw.js';
// simply register a service worker
window.addEventListener('load', function() {
navigator.serviceWorker.register(serviceWorkerJsFilePath).then(
function(registration) {
// Registration was successful
console.log(
`ServiceWorker registration successful from ${serviceWorkerJsFilePath} with scope: ${registration.scope}`
);
},
function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
}
);
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
while the service worker is defined in sw.js
:
// decide what you wanna cache as initial steps to register the service worker
// once the service worker js is updated, install will be triggered
this.addEventListener('install', onInstall);
// a trigger which will get executed after service worker install
// if current page still open, even new service worker installed, it wont enter activate state
// instead, it will enter waiting state
// once old page is closed, new service worker enter activate and hence trigger the handler below
this.addEventListener('activate', onActivate);
// all the resource fetch action go through here
this.addEventListener('fetch', onFetch);
var urlsToCache = [
'file_cache_on_install/install.json',
'file_cache_on_install/img.jpg'
];
var INSTALL_CACHE_NAME = 'file_cache_on_install';
var RUNTIME_CACHE_NAME = 'file_cache_on_runtime';
function onInstall(event) {
console.log('INSTALL service worker...');
event.waitUntil(
caches.open(INSTALL_CACHE_NAME).then(function(cache) {
console.log(
'INSTALL -> URLs to cache during install service worker',
urlsToCache
);
// magic cache handler, which cache the url response to predefined urls group
return cache.addAll(urlsToCache);
})
);
}
function onActivate(event) {
console.log('ACTIVATE service worker...');
// let s delete all the cache which is not in the on install cache
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames
.filter(function(key) {
console.log(
`ACTIVATE -> key:${key}, (key !== INSTALL_CACHE_NAME):${key !==
INSTALL_CACHE_NAME}`
);
return key !== INSTALL_CACHE_NAME;
})
.map(function(cacheName) {
console.log('ACTIVATE -> delete cache:', cacheName);
return caches.delete(cacheName);
})
);
})
);
}
function onFetch(event) {
console.log('FETCH service worker...');
// process including on install cache resources and the rest resources
// 1. fetch resource from on install cache store, if cache not here, continue
// 2. fetch resource from network and cache back to another cache store
// 3. if second steps failed, fetch the resources from cache (offline support)
// NOTE: no handling if cannot get data from both cache & network
event.respondWith(fetchFromInstallCacheOrFallback(event));
}
// fetch resource from on install cache store, if we have cache return and exits
function fetchFromInstallCacheOrFallback(event) {
return caches.open(INSTALL_CACHE_NAME).then(cache => {
return cache.match(event.request).then(response => {
console.log('event.request', event.request, 'response', response);
if (response) {
return response;
}
// return if we have network
return fetchFromNetworkOrFallback(event);
});
});
}
// fetch resource from network
// if resource response correctly, cache response to cache store, return and exist
function fetchFromNetworkOrFallback(event) {
return fetch(event.request)
.then(response => {
// Check if we received a valid response
console.log('FETCH -> Response from network:', response);
if (!response || response.status !== 200) {
return response;
}
// better make a clone of response
var responseToCache = response.clone();
caches.open(RUNTIME_CACHE_NAME).then(function(cache) {
console.log(
`FETCH -> Caching ${event.request.url} to ${RUNTIME_CACHE_NAME}, for response ${response}`
);
cache.put(event.request, responseToCache);
});
return response;
})
.catch(() => {
return fetchFromRuntimeCache(event);
});
}
// fetch resource from cache store, return proper response of exists
function fetchFromRuntimeCache(event) {
return caches.open(RUNTIME_CACHE_NAME).then(cache => {
return cache.match(event.request).then(function(response) {
// Cache hit - return response
console.log(
`FETCH -> Cached request from ${event.request.url}, for response ${response}`
);
return response;
});
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
beyond, there are some testing file, file_cache_on_install/install.json
:
[
{ "name": "On install" },
{ "name": "Arun" },
{ "name": "Sunil" },
{ "name": "Rahul" },
{ "name": "Anita" }
]
2
3
4
5
6
7
file_runtime_cache/runtime.json
:
[
{ "name": "On runtime" },
{ "name": "Sunil" },
{ "name": "Rahul" },
{ "name": "Anita" }
]
2
3
4
5
6
if you serve it out locally
$ python -m SimpleHTTPServer 5000
you might get view:
# Demo rundown
# Normal setup and check cache
serve the web application and go to localhost:5000
$ python -m SimpleHTTPServer 5000
you can view the app properly with resources fetch, the cache are listed as:
# Offline mode with resource fetch from cache store
If you properly fetched the web page. Then, enable offline mode and refresh your browser, you should still be able to see the web
TIP
- if you delete the cache from the cache store and refresh again, you will not be able to visit the web, entire cache store will be gone as well.
- if you turn the offline mode off, refresh again, you will be able to fetch entire web page and have them cached
# Always fetch from network for runtime cache
Once you load the web page, make a change to:
file_runtime_cache/runtime.json
(e.g. one more item with name)
[
{ "name": "On runtime" },
{ "name": "Marvin" },
{ "name": "Sunil" },
{ "name": "Rahul" },
{ "name": "Anita" }
]
2
3
4
5
6
7
Reload the page, you will see the new item pop up to the web page
# Install only happen for new service worker
Once you load the web page, make a change to:
file_cache_on_install/install.json
(e.g. one more item with name)
Reload the page, there's no change happen to the web page.
Make a change to the sw.js
and reload the page, e.g. add a new line to the end of the file
TIP
old service worker still processing while new service worker is waiting for activating
from the console log, you can see
install
event is triggered, hence thefile_cache_on_install/install.json
finally updated to the cache storeat this point, the display is still same content as before for
JSON on install
fieldreload the page once again, you can proper fetch the resources from install cache, hence the UI updated accordingly
open a new page and look at the service worker activity and close the old page, you can see:
- old service worker is terminated
- new service worker is activated (although it is stopped)
# Conclusion
With Service worker, engineer will be more flexible and over control on client's browser to tuning the user experience in an offline web mode or with poor network connectivity
Worth a look in the future:
- Service worker with PWA