-
Notifications
You must be signed in to change notification settings - Fork 22.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make the PWA code more modern and readable #1207
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,7 +44,7 @@ <h3 id="Registering_the_Service_Worker">Registering the Service Worker</h3> | |
<p><strong>NOTE</strong> : We're using the <a href="http://es6-features.org/">es6</a> <strong>arrow functions</strong> syntax in the Service Worker Implementation</p> | ||
|
||
<pre class="brush: js">if('serviceWorker' in navigator) { | ||
navigator.serviceWorker.register('./pwa-examples/js13kpwa/sw.js'); | ||
navigator.serviceWorker.register('./pwa-examples/js13kpwa/sw.js'); | ||
};</pre> | ||
|
||
<p>If the service worker API is supported in the browser, it is registered against the site using the {{domxref("ServiceWorkerContainer.register()")}} method. Its contents reside in the sw.js file, and can be executed after the registration is successful. It's the only piece of Service Worker code that sits inside the app.js file; everything else that is Service Worker-specific is written in the sw.js file itself.</p> | ||
|
@@ -58,15 +58,15 @@ <h4 id="Installation">Installation</h4> | |
<p>The API allows us to add event listeners for key events we are interested in — the first one is the <code>install</code> event:</p> | ||
|
||
<pre class="brush: js">self.addEventListener('install', (e) => { | ||
console.log('[Service Worker] Install'); | ||
console.log('[Service Worker] Install'); | ||
});</pre> | ||
|
||
<p>In the <code>install</code> listener, we can initialize the cache and add files to it for offline use. Our js13kPWA app does exactly that.</p> | ||
|
||
<p>First, a variable for storing the cache name is created, and the app shell files are listed in one array.</p> | ||
|
||
<pre class="brush: js">var cacheName = 'js13kPWA-v1'; | ||
var appShellFiles = [ | ||
<pre class="brush: js">const cacheName = 'js13kPWA-v1'; | ||
const appShellFiles = [ | ||
'/pwa-examples/js13kpwa/', | ||
'/pwa-examples/js13kpwa/index.html', | ||
'/pwa-examples/js13kpwa/app.js', | ||
|
@@ -89,22 +89,21 @@ <h4 id="Installation">Installation</h4> | |
|
||
<p>Next, the links to images to be loaded along with the content from the data/games.js file are generated in the second array. After that, both arrays are merged using the {{jsxref("Array.prototype.concat()")}} function.</p> | ||
|
||
<pre class="brush: js">var gamesImages = []; | ||
for(var i=0; i<games.length; i++) { | ||
gamesImages.push('data/img/'+games[i].slug+'.jpg'); | ||
<pre class="brush: js">const gamesImages = []; | ||
for (let i = 0; i < games.length; i++) { | ||
gamesImages.push(`data/img/${games[i].slug}.jpg`); | ||
} | ||
var contentToCache = appShellFiles.concat(gamesImages);</pre> | ||
const contentToCache = appShellFiles.concat(gamesImages);</pre> | ||
|
||
<p>Then we can manage the <code>install</code> event itself:</p> | ||
|
||
<pre class="brush: js">self.addEventListener('install', (e) => { | ||
console.log('[Service Worker] Install'); | ||
e.waitUntil( | ||
caches.open(cacheName).then((cache) => { | ||
console.log('[Service Worker] Caching all: app shell and content'); | ||
return cache.addAll(contentToCache); | ||
}) | ||
); | ||
e.waitUntil((async () => { | ||
const cache = await caches.open(cacheName); | ||
console.log('[Service Worker] Caching all: app shell and content'); | ||
await cache.addAll(contentToCache); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That was There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes but that's precisely what what confusing about the previous syntax, we returned the promised to await it, but we don't do anything with the resolved value With this refactoring, the install event awaits for the caching of assets, but we don't return anything we won't use, which is clearer imho Btw addAll returns undefined so this is a strictly ISO refactoring |
||
})()); | ||
});</pre> | ||
|
||
<p>There are two things that need an explanation here: what {{domxref("ExtendableEvent.waitUntil")}} does, and what the {{domxref("Cache","caches")}} object is.</p> | ||
|
@@ -126,26 +125,24 @@ <h3 id="Responding_to_fetches">Responding to fetches</h3> | |
<p>We also have a <code>fetch</code> event at our disposal, which fires every time an HTTP request is fired off from our app. This is very useful, as it allows us to intercept requests and respond to them with custom responses. Here is a simple usage example:</p> | ||
|
||
<pre class="brush: js">self.addEventListener('fetch', (e) => { | ||
console.log('[Service Worker] Fetched resource '+e.request.url); | ||
console.log(`[Service Worker] Fetched resource ${e.request.url}`); | ||
});</pre> | ||
|
||
<p>The response can be anything we want: the requested file, its cached copy, or a piece of JavaScript code that will do something specific — the possibilities are endless.</p> | ||
|
||
<p>In our example app, we serve content from the cache instead of the network as long as the resource is actually in the cache. We do this whether the app is online or offline. If the file is not in the cache, the app adds it there first before then serving it:</p> | ||
|
||
<pre class="brush: js">self.addEventListener('fetch', (e) => { | ||
e.respondWith( | ||
caches.match(e.request).then((r) => { | ||
console.log('[Service Worker] Fetching resource: '+e.request.url); | ||
return r || fetch(e.request).then((response) => { | ||
return caches.open(cacheName).then((cache) => { | ||
console.log('[Service Worker] Caching new resource: '+e.request.url); | ||
cache.put(e.request, response.clone()); | ||
return response; | ||
}); | ||
}); | ||
}) | ||
); | ||
e.respondWith((async () => { | ||
const r = await caches.match(e.request); | ||
console.log(`[Service Worker] Fetching resource: ${e.request.url}`); | ||
if (r) { return r; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🙌 |
||
const response = await fetch(e.request); | ||
const cache = await caches.open(cacheName); | ||
console.log(`[Service Worker] Caching new resource: ${e.request.url}`); | ||
cache.put(e.request, response.clone()); | ||
return response; | ||
})()); | ||
});</pre> | ||
|
||
<p>Here, we respond to the fetch event with a function that tries to find the resource in the cache and return the response if it's there. If not, we use another fetch request to fetch it from the network, then store the response in the cache so it will be available there next time it is requested.</p> | ||
|
@@ -167,11 +164,10 @@ <h2 id="Updates">Updates</h2> | |
// ... | ||
|
||
self.addEventListener('install', (e) => { | ||
e.waitUntil( | ||
caches.open('js13kPWA-v2').then((cache) => { | ||
return cache.addAll(contentToCache); | ||
}) | ||
); | ||
e.waitUntil((async () => { | ||
const cache = await caches.open(cacheName); | ||
Ryuno-Ki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
await cache.addAll(contentToCache); | ||
})()); | ||
});</pre> | ||
|
||
<p>A new service worker is installed in the background, and the previous one (v1) works correctly up until there are no pages using it — the new Service Worker is then activated and takes over management of the page from the old one.</p> | ||
|
@@ -181,15 +177,13 @@ <h2 id="Clearing_the_cache">Clearing the cache</h2> | |
<p>Remember the <code>activate</code> event we skipped? It can be used to clear out the old cache we don't need anymore:</p> | ||
|
||
<pre class="brush: js">self.addEventListener('activate', (e) => { | ||
e.waitUntil( | ||
caches.keys().then((keyList) => { | ||
return Promise.all(keyList.map((key) => { | ||
if(key !== cacheName) { | ||
return caches.delete(key); | ||
} | ||
})); | ||
}) | ||
); | ||
e.waitUntil((async () => { | ||
const keyList = await caches.keys(); | ||
await Promise.all(keyList.map((key) => { | ||
if (key === cacheName) { return; } | ||
await caches.delete(key); | ||
Ryuno-Ki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
})) | ||
})()); | ||
});</pre> | ||
|
||
<p>This ensures we have only the files we need in the cache, so we don't leave any garbage behind; the <a href="/en-US/docs/Web/API/IndexedDB_API/Browser_storage_limits_and_eviction_criteria">available cache space in the browser is limited</a>, so it is a good idea to clean up after ourselves.</p> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏