A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Offline Recipes for Service Workers (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="https://hacks.mozilla.org/2015/11/offline-service-workers/">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. Offline Recipes for Service Workers (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="https://hacks.mozilla.org/2015/11/offline-service-workers/">Source originale du contenu</a></h3>
  445. <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>
  446. <ul>
  447. <li>Files are served from cache even when the user is online.</li>
  448. <li>There’s no dynamism: the appcache file is simply a list of files to cache.</li>
  449. <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>
  450. <li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache#Gotchas" target="_blank">Other gotchas</a>.</li>
  451. </ul>
  452. <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>
  453. <h2>Introducing the Service Worker Cookbook</h2>
  454. <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>
  455. <ul>
  456. <li>Offline Recipes for Service Workers (today’s post)</li>
  457. <li>At Your Service for More Than Just appcache</li>
  458. <li>Web Push Updates to the Masses</li>
  459. </ul>
  460. <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>
  461. <h2>What do we mean by <em>offline</em>?</h2>
  462. <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>
  463. <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>
  464. <h3>The Service Worker</h3>
  465. <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>
  466. <pre><code class="language-javascript">&#13;
  467. var CACHE_NAME = 'dependencies-cache';&#13;
  468. &#13;
  469. // Files required to make this app work offline&#13;
  470. var REQUIRED_FILES = [&#13;
  471. 'random-1.png',&#13;
  472. 'random-2.png',&#13;
  473. 'random-3.png',&#13;
  474. 'random-4.png',&#13;
  475. 'random-5.png',&#13;
  476. 'random-6.png',&#13;
  477. 'style.css',&#13;
  478. 'index.html',&#13;
  479. '/', // Separate URL than index.html!&#13;
  480. 'index.js',&#13;
  481. 'app.js'&#13;
  482. ];&#13;
  483. </code></pre>
  484. <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>
  485. <pre><code class="language-javascript">&#13;
  486. self.addEventListener('install', function(event) {&#13;
  487. // Perform install step: loading each required file into cache&#13;
  488. event.waitUntil(&#13;
  489. caches.open(CACHE_NAME)&#13;
  490. .then(function(cache) {&#13;
  491. // Add all offline dependencies to the cache&#13;
  492. return cache.addAll(REQUIRED_FILES);&#13;
  493. })&#13;
  494. .then(function() {&#13;
  495. // At this point everything has been cached&#13;
  496. return self.skipWaiting();&#13;
  497. })&#13;
  498. );&#13;
  499. });&#13;
  500. </code></pre>
  501. <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>
  502. <pre><code class="language-javascript">&#13;
  503. self.addEventListener('fetch', function(event) {&#13;
  504. event.respondWith(&#13;
  505. caches.match(event.request)&#13;
  506. .then(function(response) {&#13;
  507. // Cache hit - return the response from the cached version&#13;
  508. if (response) {&#13;
  509. return response;&#13;
  510. }&#13;
  511. &#13;
  512. // Not in cache - return the result from the live server&#13;
  513. // `fetch` is essentially a "fallback"&#13;
  514. return fetch(event.request);&#13;
  515. }&#13;
  516. )&#13;
  517. );&#13;
  518. });&#13;
  519. </code></pre>
  520. <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>
  521. <pre><code class="language-javascript">&#13;
  522. self.addEventListener('activate', function(event) {&#13;
  523. // Calling claim() to force a "controllerchange" event on navigator.serviceWorker&#13;
  524. event.waitUntil(self.clients.claim());&#13;
  525. });&#13;
  526. </code></pre>
  527. <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>
  528. <h3>Service worker registration</h3>
  529. <p>With the simple service worker created, it’s time to register the service worker:</p>
  530. <pre><code class="language-javascript">&#13;
  531. // Register the ServiceWorker&#13;
  532. navigator.serviceWorker.register('service-worker.js', {&#13;
  533. scope: '.'&#13;
  534. }).then(function(registration) {&#13;
  535. // The service worker has been registered!&#13;
  536. });&#13;
  537. </code></pre>
  538. <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>
  539. <pre><code class="language-javascript">&#13;
  540. // Listen for claiming of our ServiceWorker&#13;
  541. navigator.serviceWorker.addEventListener('controllerchange', function(event) {&#13;
  542. // Listen for changes in the state of our ServiceWorker&#13;
  543. navigator.serviceWorker.controller.addEventListener('statechange', function() {&#13;
  544. // If the ServiceWorker becomes "activated", let the user know they can go offline!&#13;
  545. if (this.state === 'activated') {&#13;
  546. // Show the "You may now use offline" notification&#13;
  547. document.getElementById('offlineNotification').classList.remove('hidden');&#13;
  548. }&#13;
  549. });&#13;
  550. });&#13;
  551. </code></pre>
  552. <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>
  553. <pre><code class="language-javascript">&#13;
  554. // This file is required to make the "app" work offline&#13;
  555. document.querySelector('#randomButton').addEventListener('click', function() {&#13;
  556. var image = document.querySelector('#logoImage');&#13;
  557. var currentIndex = Number(image.src.match('random-([0-9])')[1]);&#13;
  558. var newIndex = getRandomNumber();&#13;
  559. &#13;
  560. // Ensure that we receive a different image than the current&#13;
  561. while (newIndex === currentIndex) {&#13;
  562. newIndex = getRandomNumber();&#13;
  563. }&#13;
  564. &#13;
  565. image.src = 'random-' + newIndex + '.png';&#13;
  566. &#13;
  567. function getRandomNumber() {&#13;
  568. return Math.floor(Math.random() * 6) + 1;&#13;
  569. }&#13;
  570. });&#13;
  571. </code></pre>
  572. <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>
  573. <p>This recipe covers probably the most simple of offline cases: caching required static files for offline use.</p>
  574. <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>
  575. <h3>The service worker</h3>
  576. <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>
  577. <pre><code class="language-javascript">&#13;
  578. self.addEventListener('install', function(event) {&#13;
  579. // Put `offline.html` page into cache&#13;
  580. var offlineRequest = new Request('offline.html');&#13;
  581. event.waitUntil(&#13;
  582. fetch(offlineRequest).then(function(response) {&#13;
  583. return caches.open('offline').then(function(cache) {&#13;
  584. return cache.put(offlineRequest, response);&#13;
  585. });&#13;
  586. })&#13;
  587. );&#13;
  588. });&#13;
  589. </code></pre>
  590. <p>If that requests fails the service worker won’t register since nothing has been put into cache.</p>
  591. <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>
  592. <pre><code class="language-javascript">&#13;
  593. self.addEventListener('fetch', function(event) {&#13;
  594. // Only fall back for HTML documents.&#13;
  595. var request = event.request;&#13;
  596. // &amp;&amp; request.headers.get('accept').includes('text/html')&#13;
  597. if (request.method === 'GET') {&#13;
  598. // `fetch()` will use the cache when possible, to this examples&#13;
  599. // depends on cache-busting URL parameter to avoid the cache.&#13;
  600. event.respondWith(&#13;
  601. fetch(request).catch(function(error) {&#13;
  602. // `fetch()` throws an exception when the server is unreachable but not&#13;
  603. // for valid HTTP responses, even `4xx` or `5xx` range.&#13;
  604. return caches.open('offline').then(function(cache) {&#13;
  605. return cache.match('offline.html');&#13;
  606. });&#13;
  607. })&#13;
  608. );&#13;
  609. }&#13;
  610. // Any other handlers come here. Without calls to `event.respondWith()` the&#13;
  611. // request will be handled without the ServiceWorker.&#13;
  612. });&#13;
  613. </code></pre>
  614. <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>
  615. <h3>Service Worker Registration</h3>
  616. <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>
  617. <pre><code class="language-javascript">&#13;
  618. if (navigator.serviceWorker.controller) {&#13;
  619. // A ServiceWorker controls the site on load and therefor can handle offline&#13;
  620. // fallbacks.&#13;
  621. console.log('DEBUG: serviceWorker.controller is truthy');&#13;
  622. debug(navigator.serviceWorker.controller.scriptURL + ' (onload)', 'controller');&#13;
  623. }&#13;
  624. &#13;
  625. else {&#13;
  626. // Register the ServiceWorker&#13;
  627. console.log('DEBUG: serviceWorker.controller is falsy');&#13;
  628. navigator.serviceWorker.register('service-worker.js', {&#13;
  629. scope: './'&#13;
  630. }).then(function(reg) {&#13;
  631. debug(reg.scope, 'register');&#13;
  632. });&#13;
  633. }&#13;
  634. </code></pre>
  635. <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>
  636. <pre><code class="language-javascript">&#13;
  637. // The refresh link needs a cache-busting URL parameter&#13;
  638. document.querySelector('#refresh').search = Date.now();&#13;
  639. </code></pre>
  640. <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>
  641. <h2>Go offline!</h2>
  642. <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>
  643. <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>
  644. <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>
  645. </article>
  646. </section>
  647. <nav id="jumpto">
  648. <p>
  649. <a href="/david/blog/">Accueil du blog</a> |
  650. <a href="https://hacks.mozilla.org/2015/11/offline-service-workers/">Source originale</a> |
  651. <a href="/david/stream/2019/">Accueil du flux</a>
  652. </p>
  653. </nav>
  654. <footer>
  655. <div>
  656. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  657. <p>
  658. Bonjour/Hi!
  659. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  660. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  661. </p>
  662. <p>
  663. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  664. </p>
  665. <p>
  666. Voici quelques articles choisis :
  667. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  668. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  669. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  670. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  671. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  672. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  673. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  674. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  675. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  676. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  677. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  678. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  679. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  680. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  681. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  682. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  683. </p>
  684. <p>
  685. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  686. </p>
  687. <p>
  688. Je ne traque pas ta navigation mais mon
  689. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  690. conserve des logs d’accès.
  691. </p>
  692. </div>
  693. </footer>
  694. <script type="text/javascript">
  695. ;(_ => {
  696. const jumper = document.getElementById('jumper')
  697. jumper.addEventListener('click', e => {
  698. e.preventDefault()
  699. const anchor = e.target.getAttribute('href')
  700. const targetEl = document.getElementById(anchor.substring(1))
  701. targetEl.scrollIntoView({behavior: 'smooth'})
  702. })
  703. })()
  704. </script>