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.

index.html 39KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025
  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>Introduction to Service Worker (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="http://www.html5rocks.com/en/tutorials/service-worker/introduction/">
  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. Introduction to Service Worker (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="http://www.html5rocks.com/en/tutorials/service-worker/introduction/">Source originale du contenu</a></h3>
  445. <p>Rich offline experiences, periodic background syncs, push notifications—
  446. functionality that would normally require a native application—are coming to
  447. the web. Service workers provide the technical foundation that all these
  448. features will rely on.</p>
  449. <h2 id="toc-what">What is a Service Worker?</h2>
  450. <p>A service worker is a script that is run by your browser in the background,
  451. separate from a web page, opening the door to features which don't need a web
  452. page or user interaction. Today, they already include features like
  453. <a href="http://updates.html5rocks.com/2015/03/push-notificatons-on-the-open-web">
  454. push notifications</a> and in the future it will include other things like,
  455. background sync, or geofencing. The core feature discussed in this tutorial is
  456. the ability to intercept and handle network requests, including programmatically
  457. managing a cache of responses.</p>
  458. <p>The reason this is such an exciting API is that it allows you to support offline
  459. experiences, giving developers complete control over what exactly that
  460. experience is.</p>
  461. <p>Before service worker there was one other API that would give users an offline
  462. experience on the web called <a href="/tutorials/appcache/beginner/">App
  463. Cache</a>. The major
  464. issue with App Cache is the <a href="http://alistapart.com/article/application-cache-is-a-douchebag">number
  465. of gotcha's that exist</a> as well
  466. as the design working particularly well for single page web apps, but not for
  467. multi-page sites. Service workers have been designed to avoid these common pain
  468. points.</p>
  469. <p>Things to note about a service worker:</p>
  470. <ul>
  471. <li>It's a <a href="/tutorials/workers/basics/">JavaScript
  472. Worker</a>, so it can't
  473. access the DOM directly. Instead, a service worker can communicate with the
  474. pages it controls by responding to messages sent via the
  475. <a href="https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage"><code>postMessage</code></a>
  476. interface, and those pages can manipulate the DOM if needed.</li>
  477. <li>Service worker is a programmable network proxy, allowing you to control
  478. how network requests from your page are handled.</li>
  479. <li>It will be terminated when not in use, and restarted when it's next needed, so
  480. you cannot rely on global state within a service worker's <code>onfetch</code> and
  481. <code>onmessage</code> handlers. If there is information that you need to persist and
  482. reuse across restarts, service workers do have access to the
  483. <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a>
  484. API.</li>
  485. <li>Service workers make extensive use of promises, so if you're new to promises,
  486. then you should stop reading this and check out <a href="/tutorials/es6/promises/">Jake Archibald's
  487. article</a>.</li>
  488. </ul>
  489. <h2 id="lifecycle">Service Worker Lifecycle</h2>
  490. <p>A service worker has a lifecycle which is completely separate from your web
  491. page.</p>
  492. <p>To install a service worker for your site, you need to <strong>register</strong> it, which
  493. you do in your page's JavaScript. Registering a service worker will cause the
  494. browser to start the service worker install step in the background.</p>
  495. <p>Typically during the install step, you'll want to cache some static assets. If
  496. all the files are cached successfully, then the service worker becomes
  497. installed. If any of the files fail to download and cache, then the install step
  498. will fail and the service worker won't activate (i.e. won't be installed). If
  499. that happens, don't worry, it'll try again next time. But that means if it
  500. <em>does</em> install, you <strong>know</strong> you've got those static assets in the cache.</p>
  501. <p>When we're installed, the activation step will follow and this is a great
  502. opportunity for handling any management of old caches, which we'll cover during
  503. the <a href="#toc-how">service worker update section</a>.</p>
  504. <p>After the activation step, the service worker will control all pages that fall
  505. under its scope, though the page that registered the service worker for the
  506. first time won't be controlled until it's loaded again. Once a service worker is
  507. in control, it will be in one of two states: either the service worker will be
  508. terminated to save memory, or it will handle <code>fetch</code> and <code>message</code> events which
  509. occur when a network request or message is made from your page.</p>
  510. <p>Below is an overly simplified version of the service worker lifecycle on it's
  511. first installation.</p>
  512. <p><img src="http://www.html5rocks.com/en/tutorials/service-worker/introduction/images/sw-lifecycle.png"/></p>
  513. <h2 id="toc-before">Before We Start</h2>
  514. <p>Grab the caches polyfill from this repository
  515. <a href="https://github.com/coonsta/cache-polyfill">https://github.com/coonsta/cache-polyfill</a>.</p>
  516. <p>This polyfill will add support for <code>Cache.addAll</code> which Chrome M43's implementation of
  517. the <a href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#cache-interface">Cache
  518. API</a> doesn't currently support.</p>
  519. <p>Grab <strong><code>dist/serviceworker-cache-polyfill.js</code></strong> to put somewhere in your site
  520. and use it in a service worker with the <strong><code>importScripts</code></strong> method. Any script
  521. which is imported will automatically be cached by the service worker.</p>
  522. <pre class="prettyprint"><code>importScripts('serviceworker-cache-polyfill.js');</code></pre>
  523. <h3>HTTPS is Needed</h3>
  524. <p>During development you'll be able to use service worker through <code>localhost</code>, but
  525. to deploy it on a site you'll need to have HTTPS setup on your server.</p>
  526. <p>Using service worker you can hijack connections, fabricate, and filter
  527. responses. Powerful stuff. While you would use these powers for good, a
  528. man-in-the-middle might not. To avoid this, you can only register for service
  529. workers on pages served over HTTPS, so we know the service worker the browser
  530. receives hasn't been tampered with during its journey through the network.</p>
  531. <p><a href="https://pages.github.com/">Github
  532. Pages</a> are served over HTTPS, so they're a great place to host demos.</p>
  533. <p>If you want to add HTTPS to your server then you'll need to get a TLS
  534. certificate and set it up for your server. This varies depending on your setup,
  535. so check your server's documentation and be sure to check out <a href="https://mozilla.github.io/server-side-tls/ssl-config-generator/">Mozilla's SSL config generator</a> for best practices.</p>
  536. <h3>Using Service Worker</h3>
  537. <p>Now that we've got the Polyfill and covered HTTPS, let's look at what we do in
  538. each step.</p>
  539. <h3>How to Register and Install a Service Worker</h3>
  540. <p>To install a service worker you need to kick start the process by <strong>registering</strong>
  541. a service worker in your page. This tells the browser where your service
  542. worker JavaScript file lives.</p>
  543. <pre class="prettyprint"><code>if ('serviceWorker' in navigator) {
  544. navigator.serviceWorker.register('/sw.js').then(function(registration) {
  545. // Registration was successful
  546. console.log('ServiceWorker registration successful with scope: ', registration.scope);
  547. }).catch(function(err) {
  548. // registration failed :(
  549. console.log('ServiceWorker registration failed: ', err);
  550. });
  551. }</code></pre>
  552. <p>This code checks to see if the service worker API is available, and if it is,
  553. the service worker at <code>/sw.js</code> is registered.</p>
  554. <p>You can call register every time a page loads without concern; the browser will
  555. figure out if the service worker is already registered or not and handle it
  556. accordingly.</p>
  557. <p>One subtlety with the register method is the location of the service worker
  558. file. You'll notice in this case that the service worker file is at the root of
  559. the domain. This means that the service worker's scope will be the entire
  560. origin. In other words, this service worker will receive <code>fetch</code> events for
  561. everything on this domain. If we register the service worker file at
  562. <code>/example/sw.js</code>, then the service worker would only see <code>fetch</code> events for
  563. pages whose URL starts with <code>/example/</code> (i.e. <code>/example/page1/</code>,
  564. <code>/example/page2/</code>).</p>
  565. <p>Now you can check that a service worker is enabled by going to
  566. <code>chrome://inspect/#service-workers</code> and looking for your site.</p>
  567. <p><img src="http://www.html5rocks.com/en/tutorials/service-worker/introduction/images/sw-chrome-inspect.png"/></p>
  568. <p>When service worker was first being implemented you could also view your service
  569. worker details through <code>chrome://serviceworker-internals</code>. This may still be
  570. useful, if for nothing more than learning about the life cycle of service
  571. workers, but don't be surprised if it gets replaced by
  572. <code>chrome://inspect/#service-workers</code> at a later date.</p>
  573. <p>You may find it useful to test your service worker in an Incognito window so
  574. that you can close and reopen knowing that the previous service worker won't
  575. affect the new window. Any registrations and caches created from within an
  576. Incognito window will be cleared out once that window is closed.</p>
  577. <h3>Service Worker Install Step</h3>
  578. <p>After a controlled page kicks off the registration process, let's shift to the
  579. point of view of the service worker script, which is given the opportunity to
  580. handle the <code>install</code> event.</p>
  581. <p>For the most basic example, you need to define a callback for the <code>install</code> event
  582. and decide which files you want to cache.</p>
  583. <pre class="prettyprint"><code>// The files we want to cache
  584. var urlsToCache = [
  585. '/',
  586. '/styles/main.css',
  587. '/script/main.js'
  588. ];
  589. // Set the callback for the install step
  590. self.addEventListener('install', function(event) {
  591. // Perform install steps
  592. });</code></pre>
  593. <p>Inside of our install callback, we need to take the following steps:</p>
  594. <ol>
  595. <li>Open a cache</li>
  596. <li>Cache our files</li>
  597. <li>Confirm whether all the required assets are cached or not</li>
  598. </ol>
  599. <pre class="prettyprint"><code>var CACHE_NAME = 'my-site-cache-v1';
  600. var urlsToCache = [
  601. '/',
  602. '/styles/main.css',
  603. '/script/main.js'
  604. ];
  605. self.addEventListener('install', function(event) {
  606. // Perform install steps
  607. event.waitUntil(
  608. caches.open(CACHE_NAME)
  609. .then(function(cache) {
  610. console.log('Opened cache');
  611. return cache.addAll(urlsToCache);
  612. })
  613. );
  614. });</code></pre>
  615. <p>Here you can see we call <code>caches.open</code> with our desired cache name, after this we
  616. call <code>cache.addAll</code> and pass in our array of files. This is a chain of promises
  617. (<code>caches.open</code> and <code>cache.addAll</code>). <code>event.waitUntil</code> takes a promise and uses it
  618. to know how long installation takes, and whether it succeeded.</p>
  619. <p>If all the files are successfully cached, then the service worker
  620. will be installed. If <strong>any</strong> of the files fail to download, then the install
  621. step will fail. This allows you to rely on having all the assets that you
  622. defined, but does mean you need to be careful with the list of files you decide
  623. to cache in the install step. Defining a long list of files will increase the
  624. chance that one file may fail to cache, leading to your service worker not
  625. getting installed.</p>
  626. <p>This is just one example, you can perform other tasks in the <code>install</code> event or
  627. avoid setting an <code>install</code> event listener altogether.</p>
  628. <h3>How to Cache and Return Requests</h3>
  629. <p>Now that you've installed a service worker, you probably want to return one of
  630. your cached responses right?</p>
  631. <p>After a service worker is installed and the user navigates to a different page
  632. or refreshes, the service worker will begin to receive <code>fetch</code> events, an example
  633. of which is below.</p>
  634. <pre class="prettyprint"><code>self.addEventListener('fetch', function(event) {
  635. event.respondWith(
  636. caches.match(event.request)
  637. .then(function(response) {
  638. // Cache hit - return response
  639. if (response) {
  640. return response;
  641. }
  642. return fetch(event.request);
  643. }
  644. )
  645. );
  646. });</code></pre>
  647. <p>Here we've defined our <code>fetch</code> event and within the <code>event.respondWith</code>, we pass
  648. in a promise from <code>caches.match</code>. <code>caches.match</code> will look at the request
  649. and find any cached results from any of the caches your service worker created.</p>
  650. <p>If we have a matching response, we return the cached value, otherwise we return
  651. the result of a call to <code>fetch</code>, which will make a network request and return the
  652. data if anything can be retrieved from the network. This is a simple example and
  653. uses any cached assets we cached during the install step.</p>
  654. <p>If we wanted to cache new requests cumulatively, we can do so by handling the
  655. response of the fetch request and then adding it to the cache, like below.</p>
  656. <pre class="prettyprint"><code>self.addEventListener('fetch', function(event) {
  657. event.respondWith(
  658. caches.match(event.request)
  659. .then(function(response) {
  660. // Cache hit - return response
  661. if (response) {
  662. return response;
  663. }
  664. // IMPORTANT: Clone the request. A request is a stream and
  665. // can only be consumed once. Since we are consuming this
  666. // once by cache and once by the browser for fetch, we need
  667. // to clone the response
  668. var fetchRequest = event.request.clone();
  669. return fetch(fetchRequest).then(
  670. function(response) {
  671. // Check if we received a valid response
  672. if(!response || response.status !== 200 || response.type !== 'basic') {
  673. return response;
  674. }
  675. // IMPORTANT: Clone the response. A response is a stream
  676. // and because we want the browser to consume the response
  677. // as well as the cache consuming the response, we need
  678. // to clone it so we have 2 stream.
  679. var responseToCache = response.clone();
  680. caches.open(CACHE_NAME)
  681. .then(function(cache) {
  682. cache.put(event.request, responseToCache);
  683. });
  684. return response;
  685. }
  686. );
  687. })
  688. );
  689. });</code></pre>
  690. <p>What we are doing is this:</p>
  691. <ol>
  692. <li>Add a callback to <code>.then</code> on the fetch request</li>
  693. <li>Once we get a response, we perform the following checks:</li>
  694. <ol>
  695. <li>Ensure the response is valid.</li>
  696. <li>Check the status is <code>200</code> on the response.</li>
  697. <li>Make sure the response type is <strong>basic</strong>, which indicates that it's a
  698. request from our origin. This means that requests to third party assets
  699. aren't cached as well.</li>
  700. </ol>
  701. <li>If we pass the checks, we
  702. <a href="https://fetch.spec.whatwg.org/#dom-response-clone"><code>clone</code></a>
  703. the response. The reason for this is that because the response is a
  704. <a href="https://streams.spec.whatwg.org/">Stream</a>,
  705. the body can only be consumed once. Since we want to return the
  706. response for the browser to use, as well as pass it to the cache to use, we
  707. need to clone it so we can send one to the browser and one to the cache.</li>
  708. </ol>
  709. <h2 id="toc-how">How to Update a Service Worker</h2>
  710. <p>There will be a point in time where your service worker will need updating. When that time comes, you'll need to follow these steps:</p>
  711. <ol>
  712. <li>Update your service worker JavaScript file.</li>
  713. <ol>
  714. <li>When the user navigates to your site, the browser tries to redownload the
  715. script file that defined the service worker in the background. If there
  716. is even a byte's difference in the service worker file compared to what
  717. it currently has, it considers it 'new'.</li>
  718. </ol>
  719. <li>Your new service worker will be started and the <code>install</code> event will be fired.</li>
  720. <li>At this point the old service worker is still controlling the current pages
  721. so the new service worker will enter a "waiting" state.</li>
  722. <li>When the currently open pages of your site are closed, the old service worker
  723. will be killed and the new service worker will take control.</li>
  724. <li>Once your new service worker takes control, its <code>activate</code> event will be fired.</li>
  725. </ol>
  726. <p>One common task that will occur in the activate callback is cache management.
  727. The reason you'll want to do this in the activate callback is because if you
  728. were to wipe out any old caches in the install step, any old service worker,
  729. which keeps control of all the current pages, will suddenly stop being able to
  730. serve files from that cache.</p>
  731. <p>Let's say we have one cache called 'my-site-cache-v1', and we find that we want
  732. to split this out into one cache for pages and one cache for blog posts. This
  733. means in the install step we'd create two caches, 'pages-cache-v1' and
  734. 'blog-posts-cache-v1' and in the activate step we'd want to delete our older
  735. 'my-site-cache-v1'.</p>
  736. <p>The following code would do this by looping through all of the caches in the
  737. service worker and deleting any caches which aren't defined in the cache
  738. whitelist.</p>
  739. <pre class="prettyprint"><code>self.addEventListener('activate', function(event) {
  740. var cacheWhitelist = ['pages-cache-v1', 'blog-posts-cache-v1'];
  741. event.waitUntil(
  742. caches.keys().then(function(cacheNames) {
  743. return Promise.all(
  744. cacheNames.map(function(cacheName) {
  745. if (cacheWhitelist.indexOf(cacheName) === -1) {
  746. return caches.delete(cacheName);
  747. }
  748. })
  749. );
  750. })
  751. );
  752. });</code></pre>
  753. <h2 id="toc-rough">Rough Edges &amp; Gotchas</h2>
  754. <p>This stuff is really new. Here's a collection of issues that get in the way.
  755. Hopefully this section can be deleted soon, but for now these are worth being
  756. mindful of.</p>
  757. <h3>If Installation Fails, We're Not so Good at Telling You About It</h3>
  758. <p>If a worker registers, but then doesn't appear in
  759. <code>chrome://inspect/#service-workers</code> or <code>chrome://serviceworker-internals</code>, it's
  760. likely its failed to install due to an error being thrown, or a rejected promise
  761. being passed to <code>event.waitUntil</code>.</p>
  762. <p>To work around this, go to <code>chrome://serviceworker-internals</code> and check "Opens the
  763. DevTools window for service worker on start for debugging", and put a debugger;
  764. statement at the start of your <code>install</code> event. This, along with "<a href="https://developer.chrome.com/devtools/docs/javascript-debugging#pause-on-uncaught-exceptions">
  765. Pause on uncaught exceptions</a>", should reveal the issue.</p>
  766. <h3>The Defaults of fetch()</h3>
  767. <h4>No Credentials by Default</h4>
  768. <p>When you use <code>fetch</code>, by default, requests won't contain credentials such as
  769. cookies. If you want credentials, instead call:</p>
  770. <pre class="prettyprint"><code>fetch(url, {
  771. credentials: 'include'
  772. })</code></pre>
  773. <p>This behaviour is on purpose, and is arguably better than XHR's more complex
  774. default of sending credentials if the URL is same-origin, but omiting them
  775. otherwise. Fetch's behaviour is more like other CORS requests, such as <code>&lt;img crossorigin&gt;</code>, which never sends cookies unless you opt-in with <code>&lt;img crossorigin="use-credentials"&gt;</code>.</p>
  776. <h4>Non-CORS Fail by Default</h4>
  777. <p>By default, fetching a resource from a third party URL will fail if it doesn't
  778. support CORS. You can add a non-CORS option to the <code>Request</code> to overcome this,
  779. although this will cause an <a href="https://fetch.spec.whatwg.org/#concept-filtered-response-opaque">'opaque'
  780. response</a>,
  781. which means you won't be able to tell if the response was successful or not.</p>
  782. <pre class="prettyprint"><code>cache.addAll(urlsToPrefetch.map(function(urlToPrefetch) {
  783. return new Request(urlToPrefetch, { mode: 'no-cors' });
  784. })).then(function() {
  785. console.log('All resources have been fetched and cached.');
  786. });</code></pre>
  787. <h3>Handling Responsive Images</h3>
  788. <p>The <code>srcset</code> attribute or the <code>&lt;picture&gt;</code> element
  789. will select the most appropriate image asset at run time and make a network request.</p>
  790. <p>For service worker, if you wanted to cache an image during the install step, you
  791. have a few options:</p>
  792. <ol>
  793. <li>Install all the images <code>&lt;picture&gt;</code> element and <code>srcset</code> attribute will request</li>
  794. <li>Install a single low-res version of the image</li>
  795. <li>Install a single high-res version of the image</li>
  796. </ol>
  797. <p>Realistically you should be picking option 2 or 3 since downloading all of the
  798. images would be a waste of memory.</p>
  799. <p>Let's assume you go for the low res version at install time and you want to try
  800. and retrieve higher res images from the network when the page is loaded, but if
  801. the high res images fail, fallback to the low res version. This is fine and
  802. dandy to do but there is one problem.</p>
  803. <p>If we have the following two images:</p>
  804. <table>
  805. <tr>
  806. <td>Screen Density</td>
  807. <td>Width</td>
  808. <td>Height</td>
  809. </tr>
  810. <tr>
  811. <td>1x</td>
  812. <td>400</td>
  813. <td>400</td>
  814. </tr>
  815. <tr>
  816. <td>2x</td>
  817. <td>800</td>
  818. <td>800</td>
  819. </tr>
  820. </table>
  821. <p>In a srcset image, we'd have some markup like this:</p>
  822. <pre class="prettyprint"><code>&lt;img src="image-src.png" srcset="image-src.png 1x, image-2x.png 2x" /&gt;</code></pre>
  823. <p>If we are on a 2x display, then the browser will opt to download <code>image-2x.png</code>,
  824. if we are offline you could <code>.catch</code> this request and return <code>image-src.png</code>
  825. instead if it's cached, however the browser will expect an image which
  826. takes into account the extra pixels on a 2x screen, so the image will appear as
  827. 200x200 CSS pixels instead of 400x400 CSS pixels. The only way around this is to
  828. set a fixed height and width on the image.</p>
  829. <pre class="prettyprint"><code>&lt;img src="image-src.png" srcset="image-src.png 1x, image-2x.png 2x"
  830. style="width:400px; height: 400px;" /&gt;</code></pre>
  831. <p><img src="http://www.html5rocks.com/en/tutorials/service-worker/introduction/images/sw-responsive-img-example.png"/></p>
  832. <p>For <code>&lt;picture&gt;</code> elements being used for art direction, this becomes considerably
  833. more difficult and will depend heavily on how your images are created and used,
  834. but you may be able to use a similar approach to srcset.</p>
  835. <h2 id="toc-learn">Learn More</h2>
  836. <p>There is a list of documentation on service worker being maintained at
  837. <a href="https://jakearchibald.github.io/isserviceworkerready/resources.html">https://jakearchibald.github.io/isserviceworkerready/resources.html</a>
  838. that you may find useful.</p>
  839. <h2 id="toc-help">Get Help</h2>
  840. <p>If you get stuck then please post your questions on Stackoverflow and use the
  841. <a href="http://stackoverflow.com/questions/tagged/service-worker">'service-worker'</a>
  842. tag so that we can keep a track of issues and try and help as much as possible.</p>
  843. </article>
  844. </section>
  845. <nav id="jumpto">
  846. <p>
  847. <a href="/david/blog/">Accueil du blog</a> |
  848. <a href="http://www.html5rocks.com/en/tutorials/service-worker/introduction/">Source originale</a> |
  849. <a href="/david/stream/2019/">Accueil du flux</a>
  850. </p>
  851. </nav>
  852. <footer>
  853. <div>
  854. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  855. <p>
  856. Bonjour/Hi!
  857. 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>
  858. 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>).
  859. </p>
  860. <p>
  861. 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>.
  862. </p>
  863. <p>
  864. Voici quelques articles choisis :
  865. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  866. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  867. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  868. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  869. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  870. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  871. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  872. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  873. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  874. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  875. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  876. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  877. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  878. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  879. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  880. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  881. </p>
  882. <p>
  883. 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>.
  884. </p>
  885. <p>
  886. Je ne traque pas ta navigation mais mon
  887. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  888. conserve des logs d’accès.
  889. </p>
  890. </div>
  891. </footer>
  892. <script type="text/javascript">
  893. ;(_ => {
  894. const jumper = document.getElementById('jumper')
  895. jumper.addEventListener('click', e => {
  896. e.preventDefault()
  897. const anchor = e.target.getAttribute('href')
  898. const targetEl = document.getElementById(anchor.substring(1))
  899. targetEl.scrollIntoView({behavior: 'smooth'})
  900. })
  901. })()
  902. </script>