1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
|
---
title: API do Service Worker
slug: Web/API/Service_Worker_API
translation_of: Web/API/Service_Worker_API
---
<p>{{ServiceWorkerSidebar}} </p>
<p class="summary">Essencialmente, um <em>service worker</em> se comporta como um servidor <em>proxy</em> situado entre uma aplicação web, o navegador e a rede (quando esta estiver disponível). Eles servem, dentre outras coisas, para possibilitar a criação de experiências <em>offline</em> eficientes, interceptar requisições de rede – agindo adequadamente de acordo com o <em>status</em> atual da conexão – e atualizar os <em>assets</em> que residem no servidor. Service workers também permitem o acesso às APIs de <em>push notification</em> e <em>background sync</em>. </p>
<h2 id="Service_worker_-_Conceitos_e_uso">Service worker - Conceitos e uso</h2>
<p>Um <em>service worker</em> é um tipo especial de <em><a href="https://developer.mozilla.org/pt-BR/docs/Web/API/Worker">worker</a></em> baseado em eventos, o qual é registrado para um determinado <em>path</em> e origem. Na prática, ele é um arquivo JavaScript que pode controlar as páginas do site ao qual ele está associado, interceptando e modificando requisições e a navegação em si. Ele também faz <em>cache</em> dos recursos trafegados de maneira bastante granular, visando oferecer controle total sobre como a sua aplicação se comporta em determinadas situações (o exemplo mais óbvio, naturalmente, é quando não há conexão de rede disponível).</p>
<p>Assim como outros tipos de <em>worker</em>, um <em>service worker</em> é executado em um contexto isolado. Dessa forma, não tem acesso ao DOM e roda em uma thread completamente separada, sendo incapaz de realizar operações blocantes. Service workers foram projetados para ser totalmente assíncronos; como consequência disso, não permitem o acesso a APIs como <a href="https://developer.mozilla.org/pt-BR/docs/Web/API/XMLHttpRequest/Requisicoes_sincronas_e_assincronas#Synchronous_request">XHR síncrono</a> e <a href="https://developer.mozilla.org/pt-BR/docs/Web/API/Window/Window.localStorage">localStorage</a>. </p>
<p>Por questões de segurança, <em>service workers</em> funcionam apenas em sites servidos via HTTPS. A possibilidade de modificar requisições em um domínio desprotegido contra ataques do tipo <a href="https://developer.mozilla.org/en-US/docs/Glossary/MitM">man-in-the-middle</a> seria desastrosa. No Firefox, é vetado o acesso à API de <em>service workers</em> para sites abertos no <a href="https://support.mozilla.org/pt-BR/kb/navegacao-privativa-use-firefox-sem-historico">Modo de navegação privativa</a>.</p>
<div class="note">
<p><strong>Nota:</strong> Os service workers superaram tentativas anteriores de resolver problemas semelhantes, como o <a href="http://alistapart.com/article/application-cache-is-a-douchebag">AppCache</a>. Há uma explicação simples para eles terem sido bem-sucedidos: Service workers <strong>não</strong> tentam adivinhar o que você está tentando fazer e, muito menos, deixam de funcionar caso não tenham adivinhado corretamente. Pelo contrário, você tem o controle milimétrico de tudo. </p>
</div>
<div class="note">
<p><strong>Nota:</strong> Service workers fazem uso intenso de <a href="https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise">promessas</a>, uma vez que eles têm de esperar por respostas assíncronas para, após retornadas, poderem executar a ação apropriada (de sucesso ou erro). A arquitetura de promessas é ideal para esse tipo de cenário.</p>
</div>
<p> </p>
<h3 id="Registrando">Registrando</h3>
<p>O registro inicial de um <em>service worker</em> é feito através do método {{domxref("ServiceWorkerContainer.register()")}}. Se bem-sucedido, seu <em>service worker</em> será descarregado no cliente e então ocorrerá a tentativa de instalação/ativação para as URLs acessadas pelo usuário sob a origem registrada ou, caso deseje, apenas dentro de um subconjunto limitado por você.</p>
<h3 id="Download_instalação_e_ativação">Download, instalação e ativação</h3>
<p>Nesse estágio, seu service worker seguirá o seguinte ciclo de vida:</p>
<ol>
<li>Download</li>
<li>Instalação</li>
<li>Ativação</li>
</ol>
<p>Quando o usuário acessa pela primeira vez um site ou página controlado por um service worker, ele é descarregado imediatamente.</p>
<p>Após isso, serão feitos novos downloads em intervalos de aproximadamente 24 horas. O download <strong>pode</strong> ocorrer mais frequentemente, mas ele <strong>precisa</strong> ser feito a cada 24 horas para prevenir que scripts ruins sejam um problema por períodos muito extensos. </p>
<p>A tentativa de instalação é feita sempre que o arquivo descarregado é identificado como novo – seja por diferir dos <em>service workers</em> pré-existentes (é feita uma comparação binária nessa etapa) ou por ser o primeiro descoberto para a página ou site em questão.</p>
<p>Na primeira vez que um <em>service worker</em> é disponibilizado, é feita a tentativa de instalação. Se bem-sucedida, ele é ativado.</p>
<p>Se, por outro lado, já existe um <em>service worker</em> pré-instalado para uma página ou site e for disponibilizada uma nova versão, ela será descarregada, mas não imediatamente ativada. Isso é chamado de <strong><em>worker</em> em espera</strong>. Só será efetuada a ativação da versão atualizada quando não houver mais páginas utilizando a anterior. Após isso, ele passa a ser o <strong><em>worker</em> ativo</strong>. Caso necessário, é possível pular a etapa de espera com o método {{domxref("ServiceWorkerGlobalScope.skipWaiting()")}}. Em seguida, o novo worker ativo pode reivindicar as páginas existentes usando {{domxref("Clients.claim()")}}. </p>
<p>Você pode adicionar um <em>listener</em> para o evento {{domxref("InstallEvent")}}. Uma ação padrão, quando esse evento é disparado, é preparar o seu <em>service worker</em> para ser utilizado (por exemplo: criado um cache usando a API de storage nativa e armazenando nela os <em>assets</em> que você quer que permaneçam disponíveis caso a aplicação fique <em>offline</em>).</p>
<p>Há também o evento <code>activate</code>. O momento em que ele é disparado é geralmente o ideal para limpar caches antigos e outras coisas associadas com a versão anterior do seu <em>service worker</em>.</p>
<p>Seu service worker pode responder a requisições usando o {{domxref("FetchEvent")}}. Você pode manipular a resposta a essas requisições da maneira que quiser, através do método {{domxref("FetchEvent.respondWith")}}.</p>
<div class="note">
<p><strong>Nota: </strong>Como <code>oninstall<font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="background-color: #fff3d4; font-size: 18px;"> e </span></font></code><code>onactivate</code> podem demorar a serem concluídos, a especificação de <em>service workers</em> disponibiliza um método <a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil">waitUntil</a>. Ele recebe como parâmetro uma <a href="https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise">promessa</a>, notificando o navegador que há trabalho em andamento até que aquela promessa seja resolvida. O navegador, portanto, não deverá encerrar o service worker durante esse período de espera. </p>
</div>
<p>Para um tutorial completo, mostrando como construir seu primeiro exemplo do zero, leia <a href="https://developer.mozilla.org/pt-BR/docs/Web/API/Service_Worker_API/Using_Service_Workers">Usando Service Workers</a>.</p>
<h2 id="Outras_ideias_de_usos_possíveis">Outras ideias de usos possíveis</h2>
<ul>
<li>Sincronização de dados em <em>background</em></li>
<li>Responder a requisições de recursos a partir de outras origens</li>
<li>Receber de forma centralizada atualizações para dados de cálculo mais custoso, como geolocalização ou giroscópio, de forma a permitir que múltiplas páginas façam uso de um único conjunto de dados.</li>
<li>No lado do cliente, fazer compilação e gerenciamento de dependências para CoffeeScript, LESS, módulos CommonJS/AMD, entre outros. </li>
<li><em>Hooks</em> para serviços em <em>background</em>.</li>
<li>Templates customizados com base em certos padrões de URL.</li>
<li>Melhorias de performance como, por exemplo, fazer o pré-carregamento de recursos que o usuário provavelmente irá precisar a curto prazo, como as próximas fotos de um álbum.</li>
</ul>
<p>No futuro, service workers serão capazes de fazer várias outras coisas úteis para a plataforma web, deixando-a mais próxima de <em>apps</em> nativos em.</p>
<p>É interessante mencionar que outras especificações podem e irão passar a usar o contexto de service workers, por exemplo:</p>
<ul>
<li><a href="https://github.com/WICG/BackgroundSync">Sincronização em background</a>: Iniciar um service worker mesmo quando não há usuários no site, de forma que o cache possa ser atualizado;</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API">Reagir a mensagens push</a>: Iniciar um service worker para enviar uma mensagem aos usuários dizendo que há conteúdo novo disponível;</li>
<li>Reagir a uma data/hora específica;</li>
<li>Entrar em uma <a href="https://en.wikipedia.org/wiki/Geo-fence">geo-fence</a>.</li>
</ul>
<h2 id="Interfaces">Interfaces</h2>
<dl>
<dt>{{domxref("Cache")}}</dt>
<dd>Representa o armazenamento para os pares de objeto {{domxref("Request")}} e {{domxref("Response")}}, os quais são cacheados como parte do ciclo de vida do {{domxref("ServiceWorker")}}.</dd>
<dt>{{domxref("CacheStorage")}}</dt>
<dd>Representa o armazenamento para objetos {{domxref("Cache")}}. Ele disponibiliza um diretório-mestre com todos os caches nomeados que um {{domxref("ServiceWorker")}} pode acessar, mantendo o mapeamento de nomes para os objetos {{domxref("Cache")}} correspondentes.</dd>
<dt>{{domxref("Client")}}</dt>
<dd>Representa o escopo do cliente de um <em>service worker</em>. Um cliente pode ser um documento no contexto de um navegador ou um {{domxref("SharedWorker")}}, o qual é controlado por um <em>worker</em> ativo.</dd>
<dt>{{domxref("Clients")}}</dt>
<dd>Representa o container para uma lista de objetos {{domxref("Client")}}. É a principal forma de acessar os clientes na origem atual para o <em>service worker</em> ativo.</dd>
<dt>{{domxref("ExtendableEvent")}}</dt>
<dd>Estende a vida dos eventos <code>install</code> e <code>activate</code> disparados no {{domxref("ServiceWorkerGlobalScope")}}, como parte do ciclo de vida do <em>service worker</em>. Isso garante que qualquer evento funcional (como o {{domxref("FetchEvent")}}) não seja despachado para o {{domxref("ServiceWorker")}} até que ele conclua as ações em andamento, como por exemplo: atualizar esquemas de banco de dados, apagar caches defasados, etc.</dd>
<dt>{{domxref("ExtendableMessageEvent")}}</dt>
<dd>O objeto de evento do {{event("message_(ServiceWorker)","message")}}, o qual é disparado em um <em>service worker </em>(quando uma mensagem de canal é recebida no {{domxref("ServiceWorkerGlobalScope")}} a partir de outro contexto) — estende o tempo de vida desses eventos.</dd>
<dt>{{domxref("FetchEvent")}}</dt>
<dd>O parâmetro recebido no <em>handler</em> {{domxref("ServiceWorkerGlobalScope.onfetch")}}. O <code>FetchEvent</code> representa uma ação de descarregamento que é despachada no {{domxref("ServiceWorkerGlobalScope")}} de um {{domxref("ServiceWorker")}}. Ele contém informações sobre a requisição e a resposta resultante, além de disponibilizar o método {{domxref("FetchEvent.respondWith", "FetchEvent.respondWith()")}}, o qual nos permite devolver uma resposta customizada para a página que está sendo controlada.</dd>
<dt>{{domxref("InstallEvent")}}</dt>
<dd>O parâmetro recebido no <em>handler</em> {{domxref("ServiceWorkerGlobalScope.oninstall", "oninstall")}}. A interface <code>InstallEvent</code> representa uma ação de instalação que é despachada no {{domxref("ServiceWorkerGlobalScope")}} de um {{domxref("ServiceWorker")}}. Como deriva do {{domxref("ExtendableEvent")}}, ele assegura que eventos funcionais, como o {{domxref("FetchEvent")}}, não sejam despachados durante a instalação. </dd>
<dt>{{domxref("NavigationPreloadManager")}}</dt>
<dd>Disponibiliza métodos para gerenciar o pré-carregamento de recursos com um <em>service worker.</em> </dd>
<dt>{{domxref("Navigator.serviceWorker")}}</dt>
<dd>Retornar um objeto {{domxref("ServiceWorkerContainer")}}, o qual permite accesso ao registro, remoção, atualização e comunicação com os objetos {{domxref("ServiceWorker")}} para o <a href="https://html.spec.whatwg.org/multipage/browsers.html#concept-document-window">documento associado</a>.</dd>
<dt>{{domxref("NotificationEvent")}}</dt>
<dd>O parâmetro recebido no <em>handler</em> {{domxref("ServiceWorkerGlobalScope.onnotificationclick", "onnotificationclick")}}. A interface <code>NotificationEvent</code> representa um evento de <em>click</em> em uma notificação que é despachado no {{domxref("ServiceWorkerGlobalScope")}} de um {{domxref("ServiceWorker")}}.</dd>
<dt>{{domxref("ServiceWorker")}}</dt>
<dd>Representa um <em>service worker</em>. Diferentes contextos de navegação (ex: páginas, <em>workers</em>, etc.) podem ser associados com o mesmo objeto <code>ServiceWorker</code>.</dd>
<dt>{{domxref("ServiceWorkerContainer")}}</dt>
<dd>Disponibiliza um objeto representando o service worker como uma unidade global no ecossistema de rede, incluindo métodos para registrar, desregistrar e atualizar <em>service workers</em>, além de poder acessar o status de cada <em>worker</em> e seus registros.</dd>
<dt>{{domxref("ServiceWorkerGlobalScope")}}</dt>
<dd>Representa o contexto de execução global de um <em>service worker</em>.</dd>
<dt>{{domxref("ServiceWorkerMessageEvent")}} {{deprecated_inline}}</dt>
<dd>Representa uma mensagem enviada a um {{domxref("ServiceWorkerGlobalScope")}}. <strong>Vale ressaltar que essa interface está defasada em navegadores modernos. As mensagens de <em>service worker</em> agora usam a interface {{domxref("MessageEvent")}} para manter consistência com outras funcionalidades de messageria da web.</strong></dd>
<dt>{{domxref("ServiceWorkerRegistration")}}</dt>
<dd>Representa o registro de um <em>service worker</em>.</dd>
<dt>{{domxref("ServiceWorkerState")}} {{experimental_inline}}</dt>
<dd>Associado com o estado do <a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker" title="The ServiceWorker interface of the ServiceWorker API provides a reference to a service worker. Multiple browsing contexts (e.g. pages, workers, etc.) can be associated with the same service worker, each through a unique ServiceWorker object."><code>ServiceWorker</code></a>.</dd>
<dt>{{domxref("SyncEvent")}} {{non-standard_inline}}</dt>
<dd>
<p>A interface SyncEvent representa uma ação de sincronização que é despachada no {{domxref("ServiceWorkerGlobalScope")}} de um ServiceWorker. </p>
</dd>
<dt>{{domxref("SyncManager")}} {{non-standard_inline}}</dt>
<dd>Disponibiliza uma interface para registrar e listar registros de sincronização.</dd>
<dt>{{domxref("WindowClient")}}</dt>
<dd>Representa o escopo de um cliente de <em>service worker</em> que é um documento em um contexto de navegador, o qual é controlado por um <em>worker</em> ativo. Esse é um tipo especial de objeto {{domxref("Client")}}, como alguns métodos e propriedades adicionais disponíveis.</dd>
</dl>
<h2 id="Especificações">Especificações</h2>
<table>
<tbody>
<tr>
<th scope="col">Especificação</th>
<th scope="col">Status</th>
<th scope="col">Comentário</th>
</tr>
<tr>
<td>{{SpecName('Service Workers')}}</td>
<td>{{Spec2('Service Workers')}}</td>
<td>Initial definition.</td>
</tr>
</tbody>
</table>
<h2 id="Compatibilidade_de_Navegador">Compatibilidade de Navegador</h2>
<p>{{ CompatibilityTable() }}</p>
<table>
<tbody>
<tr>
<th>Recurso</th>
<th>Chrome</th>
<th>Edge</th>
<th>Firefox (Gecko)</th>
<th>Internet Explorer</th>
<th>Opera</th>
<th>Safari (WebKit)</th>
</tr>
<tr>
<td>Suporte básico</td>
<td>{{CompatChrome(40)}}</td>
<td>{{CompatNo}}<sup>[1]</sup></td>
<td>{{ CompatGeckoDesktop("44.0") }}<sup>[2]</sup></td>
<td>{{ CompatNo() }}</td>
<td>24</td>
<td>{{ CompatNo() }}<sup>[3]</sup></td>
</tr>
<tr>
<td>Eventos install/activate</td>
<td>{{ CompatChrome(40) }}</td>
<td>{{CompatNo}}<sup>[1]</sup></td>
<td>{{ CompatGeckoDesktop("44.0") }}<sup>[2]</sup></td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatVersionUnknown() }}</td>
<td>{{ CompatNo() }}</td>
</tr>
<tr>
<td>Evento fetch/request/<br>
<code>respondWith()</code></td>
<td>{{CompatChrome(40)}}</td>
<td>{{CompatNo}}<sup>[1]</sup></td>
<td>{{ CompatGeckoDesktop("44.0") }}<sup>[2]</sup></td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
</tr>
<tr>
<td>caches/cache</td>
<td>
<p>{{CompatChrome(42)}}</p>
</td>
<td>{{CompatNo}}<sup>[1]</sup></td>
<td>{{ CompatGeckoDesktop("39.0") }}<sup>[2]</sup></td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
</tr>
<tr>
<td>{{domxref("ServiceWorkerMessageEvent")}} defasado em favor {{domxref("MessageEvent")}}</td>
<td>
<p>{{CompatChrome(57)}}</p>
</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatGeckoDesktop("55.0") }}<sup>[2]</sup></td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
</tr>
<tr>
<td><code>NavigationPreloadManager</code></td>
<td>{{CompatChrome(59)}}</td>
<td>{{ CompatNo() }}</td>
<td> </td>
<td>{{ CompatNo() }}</td>
<td>{{CompatOpera(46)}}</td>
<td>{{ CompatNo() }}</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<th>Recursos</th>
<th>Android Webview</th>
<th>Chrome for Android</th>
<th>Edge</th>
<th>Firefox Mobile (Gecko)</th>
<th>IE Phone</th>
<th>Opera Mobile</th>
<th>Safari Mobile</th>
</tr>
<tr>
<td>Suporte básico</td>
<td>{{ CompatNo() }}</td>
<td>{{CompatChrome(40)}}</td>
<td>{{CompatNo}}<sup>[1]</sup></td>
<td>{{ CompatGeckoMobile("44.0") }}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatVersionUnknown() }}</td>
<td>{{ CompatNo() }}<sup>[3]</sup></td>
</tr>
<tr>
<td>Eventos install/activate</td>
<td>{{ CompatNo() }}</td>
<td>{{CompatChrome(40)}}</td>
<td>{{CompatNo}}<sup>[1]</sup></td>
<td>{{ CompatGeckoMobile("44.0") }}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatVersionUnknown() }}</td>
<td>{{ CompatNo() }}</td>
</tr>
<tr>
<td>Evento fetch/request/<br>
<code>respondWith()</code></td>
<td>{{ CompatNo() }}</td>
<td>{{CompatChrome(40)}}</td>
<td>{{CompatNo}}<sup>[1]</sup></td>
<td>{{ CompatGeckoMobile("44.0") }}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
</tr>
<tr>
<td>caches/cache</td>
<td>{{ CompatNo() }}</td>
<td>{{CompatChrome(40)}}</td>
<td>{{CompatNo}}<sup>[1]</sup></td>
<td>{{ CompatGeckoMobile("39.0") }}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
</tr>
<tr>
<td>{{domxref("ServiceWorkerMessageEvent")}} defasado em favor de {{domxref("MessageEvent")}}</td>
<td>{{ CompatNo() }}</td>
<td>
<p>{{CompatChrome(57)}}</p>
</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatGeckoMobile("55.0") }}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
</tr>
<tr>
<td><code>NavigationPreloadManager</code></td>
<td>{{ CompatNo() }}</td>
<td>{{CompatChrome(59)}}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
<td>{{ CompatNo() }}</td>
<td>{{CompatOperaMobile(46)}}</td>
<td>{{ CompatNo() }}</td>
</tr>
</tbody>
</table>
<p>[1] Esse recurso ainda não é suportado, apesar de <a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/status/serviceworker/">já estar em desenvolvimento</a>.</p>
<p>[2] Service workers (e <a href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API">Push</a>) foram desabilitados no Firefox, nas versões <a href="https://www.mozilla.org/en-US/firefox/organizations/">45 & 52 Extended Support Releases</a> (ESR.)</p>
<p>[3] Esse recurso ainda não é suportado, apesar de <a href="https://bugs.webkit.org/show_bug.cgi?id=174541">já estar em desenvolvimento</a>.</p>
<h2 id="Veja_também">Veja também</h2>
<ul>
<li><a href="https://serviceworke.rs/">ServiceWorker Cookbook</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker_API/Using_Service_Workers">Using Service Workers</a></li>
<li><a href="https://github.com/mdn/sw-test">Service workers basic code example</a></li>
<li><a href="https://jakearchibald.github.io/isserviceworkerready/">Is ServiceWorker ready?</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Guide/Performance/Using_web_workers">Using web workers</a></li>
<li><a href="https://www.fastly.com/blog/best-practices-for-using-the-vary-header">Best Practices for using the VARY header</a></li>
</ul>
|