A place to cache linked articles (think custom and personal wayback machine)
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

index.md 14KB

il y a 4 ans
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. title: Offline Recipes for Service Workers
  2. url: https://hacks.mozilla.org/2015/11/offline-service-workers/
  3. hash_url: 855debdfdc494d53ccf8e1939ea85362
  4. <p>“Offline” is a big topic these days, especially as many web apps look to also function as mobile apps.  The original offline helper API, the Application Cache API (also known as “appcache”), has a host of problems, many of which can be found in Jake Archibald’s <a href="http://alistapart.com/article/application-cache-is-a-douchebag" target="_blank">Application Cache is a Douchebag</a> post.  Problems with appcache include:</p>
  5. <ul>
  6. <li>Files are served from cache even when the user is online.</li>
  7. <li>There’s no dynamism: the appcache file is simply a list of files to cache.</li>
  8. <li>One is able to <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache#Gotchas" target="_blank">cache the .appcache file itself</a> and that leads to update problems.</li>
  9. <li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache#Gotchas" target="_blank">Other gotchas</a>.</li>
  10. </ul>
  11. <p>Today there’s a new API available to developers to ensure their web apps work properly:  <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API" target="_blank">the Service Worker API</a>.  The Service Worker API allows developers to manage what does and doesn’t go into cache for offline use with JavaScript.</p>
  12. <h2>Introducing the Service Worker Cookbook</h2>
  13. <p>To introduce you to the Service Worker API we’ll be using examples from Mozilla’s new  <a href="https://serviceworke.rs" target="_blank">Service Worker Cookbook</a>!  The Cookbook is a collection of working, practical examples of service workers in use in modern web apps.  We’ll be introducing service workers within this three-part series:</p>
  14. <ul>
  15. <li>Offline Recipes for Service Workers (today’s post)</li>
  16. <li>At Your Service for More Than Just appcache</li>
  17. <li>Web Push Updates to the Masses</li>
  18. </ul>
  19. <p>Of course this API has advantages other than enabling offline capabilities, such as performance for one, but I’d like to start by introducing basic service worker strategies for offline.</p>
  20. <h2>What do we mean by <em>offline</em>?</h2>
  21. <p>Offline doesn’t just mean the user doesn’t have an internet connection — it can also mean that the user is on a flaky network connection.  Essentially “offline” means that the user doesn’t have a reliable connection, and we’ve all been there before!</p>
  22. <p>The Offline Status recipe illustrates how to use a service worker to cache a known asset list and then notify the user that they may now go offline and use the app. The app itself is quite simple: show a random image when a button is clicked.  Let’s have a look at the components involved in making this happen.</p>
  23. <h3>The Service Worker</h3>
  24. <p>We’ll start by looking at the <code>service-worker.js</code> file to see what we’re caching. We’ll be caching the random images to display, as well as the display page and critical JavaScript resources, in a cache named <code>dependencies-cache</code>:</p>
  25. <pre><code class="language-javascript">&#13;
  26. var CACHE_NAME = 'dependencies-cache';&#13;
  27. &#13;
  28. // Files required to make this app work offline&#13;
  29. var REQUIRED_FILES = [&#13;
  30. 'random-1.png',&#13;
  31. 'random-2.png',&#13;
  32. 'random-3.png',&#13;
  33. 'random-4.png',&#13;
  34. 'random-5.png',&#13;
  35. 'random-6.png',&#13;
  36. 'style.css',&#13;
  37. 'index.html',&#13;
  38. '/', // Separate URL than index.html!&#13;
  39. 'index.js',&#13;
  40. 'app.js'&#13;
  41. ];&#13;
  42. </code></pre>
  43. <p>The service worker’s <code>install</code> event will open the cache and use <code>addAll</code> to direct the service worker to cache our specified files:</p>
  44. <pre><code class="language-javascript">&#13;
  45. self.addEventListener('install', function(event) {&#13;
  46. // Perform install step: loading each required file into cache&#13;
  47. event.waitUntil(&#13;
  48. caches.open(CACHE_NAME)&#13;
  49. .then(function(cache) {&#13;
  50. // Add all offline dependencies to the cache&#13;
  51. return cache.addAll(REQUIRED_FILES);&#13;
  52. })&#13;
  53. .then(function() {&#13;
  54. // At this point everything has been cached&#13;
  55. return self.skipWaiting();&#13;
  56. })&#13;
  57. );&#13;
  58. });&#13;
  59. </code></pre>
  60. <p>The <code>fetch</code> event of a service worker is fired for every single request the page makes.  The <code>fetch</code> event also allows you to serve alternate content than was actually requested.  For the purposes of offline content, however, our <code>fetch</code> listener will be very simple:  if the file is cached, return it from cache; if not, retrieve the file from server:</p>
  61. <pre><code class="language-javascript">&#13;
  62. self.addEventListener('fetch', function(event) {&#13;
  63. event.respondWith(&#13;
  64. caches.match(event.request)&#13;
  65. .then(function(response) {&#13;
  66. // Cache hit - return the response from the cached version&#13;
  67. if (response) {&#13;
  68. return response;&#13;
  69. }&#13;
  70. &#13;
  71. // Not in cache - return the result from the live server&#13;
  72. // `fetch` is essentially a "fallback"&#13;
  73. return fetch(event.request);&#13;
  74. }&#13;
  75. )&#13;
  76. );&#13;
  77. });&#13;
  78. </code></pre>
  79. <p>The last part of this <code>service-worker.js</code> file is the <code>activate</code> event listener where we immediately claim the service worker so that the user doesn’t need to refresh the page to activate the service worker. The <code>activate event</code> fires when a previous version of a service worker (if any) has been replaced and the updated service worker takes control of the scope.</p>
  80. <pre><code class="language-javascript">&#13;
  81. self.addEventListener('activate', function(event) {&#13;
  82. // Calling claim() to force a "controllerchange" event on navigator.serviceWorker&#13;
  83. event.waitUntil(self.clients.claim());&#13;
  84. });&#13;
  85. </code></pre>
  86. <p>Essentially we don’t want to require the user to refresh the page for the service worker to begin — we want the service worker to activate upon initial page load.</p>
  87. <h3>Service worker registration</h3>
  88. <p>With the simple service worker created, it’s time to register the service worker:</p>
  89. <pre><code class="language-javascript">&#13;
  90. // Register the ServiceWorker&#13;
  91. navigator.serviceWorker.register('service-worker.js', {&#13;
  92. scope: '.'&#13;
  93. }).then(function(registration) {&#13;
  94. // The service worker has been registered!&#13;
  95. });&#13;
  96. </code></pre>
  97. <p>Remember that the goal of the recipe is to notify the user when required files have been cached.  To do that we’ll need to listen to the service worker’s <code>state</code>. When the <code>state</code> has become <code>activated</code>, we know that essential files have been cached, our app is ready to go offline, and we can notify our user:</p>
  98. <pre><code class="language-javascript">&#13;
  99. // Listen for claiming of our ServiceWorker&#13;
  100. navigator.serviceWorker.addEventListener('controllerchange', function(event) {&#13;
  101. // Listen for changes in the state of our ServiceWorker&#13;
  102. navigator.serviceWorker.controller.addEventListener('statechange', function() {&#13;
  103. // If the ServiceWorker becomes "activated", let the user know they can go offline!&#13;
  104. if (this.state === 'activated') {&#13;
  105. // Show the "You may now use offline" notification&#13;
  106. document.getElementById('offlineNotification').classList.remove('hidden');&#13;
  107. }&#13;
  108. });&#13;
  109. });&#13;
  110. </code></pre>
  111. <p>Testing the registration and verifying that the app works offline simply requires using the recipe! This recipe provides a button to load a random image by changing the image’s <code>src</code> attribute:</p>
  112. <pre><code class="language-javascript">&#13;
  113. // This file is required to make the "app" work offline&#13;
  114. document.querySelector('#randomButton').addEventListener('click', function() {&#13;
  115. var image = document.querySelector('#logoImage');&#13;
  116. var currentIndex = Number(image.src.match('random-([0-9])')[1]);&#13;
  117. var newIndex = getRandomNumber();&#13;
  118. &#13;
  119. // Ensure that we receive a different image than the current&#13;
  120. while (newIndex === currentIndex) {&#13;
  121. newIndex = getRandomNumber();&#13;
  122. }&#13;
  123. &#13;
  124. image.src = 'random-' + newIndex + '.png';&#13;
  125. &#13;
  126. function getRandomNumber() {&#13;
  127. return Math.floor(Math.random() * 6) + 1;&#13;
  128. }&#13;
  129. });&#13;
  130. </code></pre>
  131. <p>Changing the image’s <code>src</code> would trigger a network request for that image, but since we have the image cached by the service worker, there’s no need to make the network request.</p>
  132. <p>This recipe covers probably the most simple of offline cases: caching required static files for offline use.</p>
  133. <p>This recipe follows another simple use case: fetch a page via AJAX but respond with another cached HTML resource (<code>offline.html</code>) if the request fails.</p>
  134. <h3>The service worker</h3>
  135. <p>The <code>install</code> step of the service worker fetches the <code>offline.html</code> file and places it into a cache called <code>offline</code>:</p>
  136. <pre><code class="language-javascript">&#13;
  137. self.addEventListener('install', function(event) {&#13;
  138. // Put `offline.html` page into cache&#13;
  139. var offlineRequest = new Request('offline.html');&#13;
  140. event.waitUntil(&#13;
  141. fetch(offlineRequest).then(function(response) {&#13;
  142. return caches.open('offline').then(function(cache) {&#13;
  143. return cache.put(offlineRequest, response);&#13;
  144. });&#13;
  145. })&#13;
  146. );&#13;
  147. });&#13;
  148. </code></pre>
  149. <p>If that requests fails the service worker won’t register since nothing has been put into cache.</p>
  150. <p>The <code>fetch</code> listener listens for a request for the page and, upon failure, responds with the <code>offline.html</code> file we cached during the event registration:</p>
  151. <pre><code class="language-javascript">&#13;
  152. self.addEventListener('fetch', function(event) {&#13;
  153. // Only fall back for HTML documents.&#13;
  154. var request = event.request;&#13;
  155. // &amp;&amp; request.headers.get('accept').includes('text/html')&#13;
  156. if (request.method === 'GET') {&#13;
  157. // `fetch()` will use the cache when possible, to this examples&#13;
  158. // depends on cache-busting URL parameter to avoid the cache.&#13;
  159. event.respondWith(&#13;
  160. fetch(request).catch(function(error) {&#13;
  161. // `fetch()` throws an exception when the server is unreachable but not&#13;
  162. // for valid HTTP responses, even `4xx` or `5xx` range.&#13;
  163. return caches.open('offline').then(function(cache) {&#13;
  164. return cache.match('offline.html');&#13;
  165. });&#13;
  166. })&#13;
  167. );&#13;
  168. }&#13;
  169. // Any other handlers come here. Without calls to `event.respondWith()` the&#13;
  170. // request will be handled without the ServiceWorker.&#13;
  171. });&#13;
  172. </code></pre>
  173. <p>Notice we use <code>catch</code> to detect if the request has failed and that therefore we should respond with <code>offline.html</code> content.</p>
  174. <h3>Service Worker Registration</h3>
  175. <p>A service worker needs to be registered only once. This example shows how to bypass registration if it’s already been done by checking the presence of the <code>navigator.serviceWorker.controller</code> property; if the <code>controller</code> property doesn’t exist, we move on to registering the service worker.</p>
  176. <pre><code class="language-javascript">&#13;
  177. if (navigator.serviceWorker.controller) {&#13;
  178. // A ServiceWorker controls the site on load and therefor can handle offline&#13;
  179. // fallbacks.&#13;
  180. console.log('DEBUG: serviceWorker.controller is truthy');&#13;
  181. debug(navigator.serviceWorker.controller.scriptURL + ' (onload)', 'controller');&#13;
  182. }&#13;
  183. &#13;
  184. else {&#13;
  185. // Register the ServiceWorker&#13;
  186. console.log('DEBUG: serviceWorker.controller is falsy');&#13;
  187. navigator.serviceWorker.register('service-worker.js', {&#13;
  188. scope: './'&#13;
  189. }).then(function(reg) {&#13;
  190. debug(reg.scope, 'register');&#13;
  191. });&#13;
  192. }&#13;
  193. </code></pre>
  194. <p>With the service worker confirmed as registered, you can test the recipe (and trigger the new page request) by clicking the “refresh” link: (which then triggers a page refresh with a cache-busting parameter):</p>
  195. <pre><code class="language-javascript">&#13;
  196. // The refresh link needs a cache-busting URL parameter&#13;
  197. document.querySelector('#refresh').search = Date.now();&#13;
  198. </code></pre>
  199. <p>Providing the user an offline message instead of allowing the browser to show its own (sometimes ugly) message is an excellent way of keeping a dialog with the user about why the app isn’t available while they’re offline!</p>
  200. <h2>Go offline!</h2>
  201. <p>Service workers have moved offline experience and control into a powerful new space.  Today you can use the Service Worker API in Chrome and <a href="https://www.mozilla.org/en-US/firefox/developer/" target="_blank">Firefox Developer Edition</a>.  Many websites are using service workers today as you can see for yourself by going to <code>about:serviceworkers</code> in Firefox Developer Edition;  you’ll see a listing of installed service workers from websites you’ve visited!</p>
  202. <p><a href="https://2r4s9p1yi1fa2jd7j43zph8r-wpengine.netdna-ssl.com/files/2015/11/AboutServiceWorkers.png"><img src="https://2r4s9p1yi1fa2jd7j43zph8r-wpengine.netdna-ssl.com/files/2015/11/AboutServiceWorkers.png" alt="about:serviceworkers"/></a></p>
  203. <p>The <a href="https://serviceworke.rs" target="_blank">Service Worker Cookbook</a> is full of excellent, practical recipes and we continue to add more. Keep an eye out for the next post in this series, <em>At Your Service for More than Just appcache</em>, where you’ll learn about using the Service Worker API for more than just offline purposes.</p>