Skip to content
This repository was archived by the owner on Jan 19, 2025. It is now read-only.

Commit c38a820

Browse files
committed
New post 🚀
1 parent de1dd96 commit c38a820

File tree

1 file changed

+136
-5
lines changed

1 file changed

+136
-5
lines changed

_drafts/2020-08-10-webpack-workbox-service-worker-typescript.md

Lines changed: 136 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,35 @@ As I already said, Workbox is written in TypeScript. So let's start by creating
4444
```typescript
4545
import { skipWaiting, clientsClaim } from "workbox-core"
4646

47-
//...
47+
//...other code...
4848

4949
skipWaiting()
5050
clientsClaim()
5151

52-
//...
52+
//...other code...
5353
```
5454

5555
Now we are ready to setup it's time to understand and setup caches with Workbox. All the caches inside the framework are based on the concept of **routes** and **strategies**. Service worker can intercept network requests for a page and can respond to the browser with cached content, content from the network or content generated in the service worker. To define which request must be intercepted and served by the service worker, you must define its **routes**. The way the service worker handle the **routes** (for example cache only, network first etc.) are the **strategies**. Usually when you write your own service worker you define some files to precache during the service worker installation process and then for the routes you want to serve from it their related strategies.
56-
Let's start with the precache of some files. This is usually done manually by the developer in the `install` event, and usually the resource that are cached are the ones needed in order to have the PWA work offline. In my cases, and generally speaking for a blog/news website, this basically means save in the cache JavaScript and CSS files. Workbox give us the `precacheAndRoute` function to do this. It is possible to pass to this function a list of files to be cached and it will take care of creating an ad-hoc cache and same the files during the installation process. To build this website I'm using Webpack as JavaScript bundler. Workbox has a great support for it. In particular for the precache phase, there is the npm package `workbox-webpack-plugin` that contains a plugin called `InjectManifest`. This plugin is able to inject in the Service Worker code a variable named `__WB_MANIFEST` that contains a list of the entry points/generated files of the Webpack bundling process. So in my service worker I can precache the files I need by just writing `precacheAndRoute(self.__WB_MANIFEST)`. Anyway there's a problem: I want to cache some additional files during the installation process to manage errors related to content not available in the caches that it is not possible to load due to network errors. In this case it is possible to add the additional files with the standard way in the `install` event.
57-
58-
....webpack inject + offline stuff...
56+
Let's start with the precache of some files. This is usually done manually by the developer in the `install` event, and usually the resource that are cached are the ones needed in order to have the PWA work offline. In my cases, and generally speaking for a blog/news website, this basically means save in the cache JavaScript and CSS files. Workbox give us the `precacheAndRoute` function to do this. It is possible to pass to this function a list of files to be cached and it will take care of creating an ad-hoc cache and same the files during the installation process. To build this website I'm using Webpack as JavaScript bundler. Workbox has a great support for it. In particular for the precache phase, there is the npm package `workbox-webpack-plugin` that contains a plugin called `InjectManifest`. This plugin is able to inject in the Service Worker code a variable named `__WB_MANIFEST` that contains a list of the entry points/generated files of the Webpack bundling process. So in my service worker I can precache the files I need by just writing `precacheAndRoute(self.__WB_MANIFEST)`. Anyway there's a problem: I want to cache some additional files during the installation process to be used to manage errors related to content not available in the caches and that it is not possible to load due to network errors. It is possible to add these additional files with the standard way in the `install` event. In my case I decided to create an ad-hoc cache to save these files. Last but not least in the install event I decided also to clear all the caches. In this way on every install of a new service worker version I will initialize all the caches. All the caches that I'm using are identified by the constants `CACHE_<purpose>` (and we will see in a few moments how I'm using them :smirk:). Below you can find the code for the precache and install event.
5957

6058
```typescript
59+
import { precacheAndRoute } from 'workbox-precaching';
60+
61+
//...other code...
62+
63+
const CACHE_PREFIX = 'workbox-chicio-coding'
64+
const CACHE_OFFLINE_NAME = `${CACHE_PREFIX}-offline`
65+
const CACHE_SCRIPT_NAME = `${CACHE_PREFIX}-scripts`
66+
const CACHE_STYLES_NAME = `${CACHE_PREFIX}-styles`
67+
const CACHE_DOCUMENTS_NAME = `${CACHE_PREFIX}-documents`
68+
const CACHE_FONTS_NAME = `${CACHE_PREFIX}-fonts`
69+
const CACHE_IMAGES_NAME = `${CACHE_PREFIX}-images`
70+
71+
const OFFLINE_PAGE_URL = '/offline.html'
72+
const OFFLINE_PAGE_NO_NETWORK_IMAGE_URL = '/assets/images/no-wifi.png'
73+
74+
//...other code...
75+
6176
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
6277
// @ts-ignore: __WB_MANIFEST is a placeholder filled by workbox-webpack-plugin with the list of dependecies to be cached
6378
precacheAndRoute(self.__WB_MANIFEST)
@@ -79,4 +94,120 @@ self.addEventListener('install', (event: ExtendableEvent) => {
7994
])
8095
);
8196
})
97+
98+
//...other code...
99+
```
100+
101+
...routes and strategies...
102+
103+
```typescript
104+
import { registerRoute, setCatchHandler, Route } from 'workbox-routing';
105+
import { ExpirationPlugin } from 'workbox-expiration';
106+
import { CacheFirst } from 'workbox-strategies';
107+
108+
//...other code...
109+
110+
const stylesScriptsExpirationPlugin = new ExpirationPlugin({ maxEntries: 10, maxAgeSeconds: 15 * 24 * 60 * 60, purgeOnQuotaError: true })
111+
const fontsExpirationPlugin = new ExpirationPlugin({ maxEntries: 5, maxAgeSeconds: 180 * 24 * 60 * 60 })
112+
const imagesExpirationPlugin = new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 60 * 24 * 60 * 60 })
113+
const documentExpirationPlugin = new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 60 * 24 * 60 * 60, purgeOnQuotaError: true })
114+
115+
//...other code...
116+
117+
const registerCacheFirstRouteUsing = (
118+
destination: RequestDestination,
119+
cacheName: string,
120+
expirationPlugin: ExpirationPlugin
121+
): Route => registerRoute(
122+
({ request }) => request.destination === destination,
123+
new CacheFirst({
124+
cacheName: cacheName,
125+
plugins: [expirationPlugin],
126+
})
127+
)
128+
129+
registerCacheFirstRouteUsing('style', CACHE_STYLES_NAME, stylesScriptsExpirationPlugin)
130+
registerCacheFirstRouteUsing('script', CACHE_SCRIPT_NAME, stylesScriptsExpirationPlugin)
131+
registerCacheFirstRouteUsing('document', CACHE_DOCUMENTS_NAME, documentExpirationPlugin)
132+
registerCacheFirstRouteUsing('font', CACHE_FONTS_NAME, fontsExpirationPlugin)
133+
registerCacheFirstRouteUsing('image', CACHE_IMAGES_NAME, imagesExpirationPlugin)
134+
135+
//...other code...
136+
```
137+
138+
...set catch handler...
139+
140+
```typescript
141+
import { registerRoute, setCatchHandler, Route } from 'workbox-routing';
142+
143+
//...other code...
144+
145+
setCatchHandler((options: RouteHandlerCallbackOptions): Promise<Response> => {
146+
const isADocumentRequest = (options: RouteHandlerCallbackOptions): boolean =>
147+
!(typeof options.request === 'string') && options.request.destination == 'document';
148+
const isAOfflinePageImageRequest = (options: RouteHandlerCallbackOptions): boolean =>
149+
!(typeof options.request === 'string') &&
150+
options.request.destination == 'image' &&
151+
options.url?.pathname == OFFLINE_PAGE_NO_NETWORK_IMAGE_URL
152+
153+
if (isADocumentRequest(options)) {
154+
return caches.match(OFFLINE_PAGE_URL) as Promise<Response>;
155+
}
156+
157+
if (isAOfflinePageImageRequest(options)) {
158+
return caches.match(OFFLINE_PAGE_NO_NETWORK_IMAGE_URL) as Promise<Response>;
159+
}
160+
161+
return Promise.resolve(Response.error());
162+
})
163+
164+
//...other code...
165+
```
166+
167+
...message event to manage my personal pull to refresh implementation...
168+
169+
```typescript
170+
self.addEventListener('message', (event: ExtendableMessageEvent) => {
171+
const isARefresh = (event: ExtendableMessageEvent): boolean => event.data.message === 'refresh'
172+
const sendRefreshCompletedMessageToClient = (event: ExtendableMessageEvent): void => event.ports[0].postMessage({ refreshCompleted: true })
173+
174+
if (isARefresh(event)) {
175+
console.log(cacheNames)
176+
Promise.all([
177+
imagesExpirationPlugin.deleteCacheAndMetadata(),
178+
documentExpirationPlugin.deleteCacheAndMetadata()
179+
])
180+
.then(() => sendRefreshCompletedMessageToClient(event))
181+
.catch(() => sendRefreshCompletedMessageToClient(event))
182+
}
183+
})
184+
```
185+
186+
...strict types + tslint + google analytics...
187+
188+
```typescript
189+
import * as googleAnalytics from 'workbox-google-analytics';
190+
191+
//...other code...
192+
193+
// Fix self: https://stackoverflow.com/questions/56356655/structuring-a-typescript-project-with-workers
194+
declare const self: ServiceWorkerGlobalScope;
195+
export {};
196+
197+
//...other code...
198+
199+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
200+
// @ts-ignore: __WB_MANIFEST is a placeholder filled by workbox-webpack-plugin with the list of dependecies to be cached
201+
precacheAndRoute(self.__WB_MANIFEST)
202+
203+
//...other code...
204+
205+
googleAnalytics.initialize()
206+
207+
//...other code...
208+
82209
```
210+
211+
#### Conclusion
212+
213+
...conclusion

0 commit comments

Comments
 (0)