ts-service-worker 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,7 @@
1
- Automate the build of a complex, strongly-typed service worker.
1
+ Automate the build of a strongly-typed service worker that manages file caching. The idea is simple: Given a declarative **Plan** and options, this package outputs a functional browser-ready Javascript service worker file that manages all resource caching.
2
2
 
3
- # Goals
4
-
5
- - provide declarative support for the most common caching strategies
3
+ In particular:
4
+ - provides declarative support for the most common caching strategies
6
5
  - supports easy integration of multiple caching strategies
7
6
  - produces a `service-worker.js` that requires no additional transcompilation
8
7
  - avoids nesting and promise chains
@@ -11,17 +10,14 @@ Automate the build of a complex, strongly-typed service worker.
11
10
  - provides built-in debug logging
12
11
  - provides clear and simple cache purging rules
13
12
 
14
- Copyright (c) 2023-2024 Andrew J. Peterson, dba NDP Software All Rights Reserved.
13
+ Copyright (c) 2023-2025 Andrew J. Peterson, dba NDP Software All Rights Reserved.
15
14
 
16
15
  #### NOTICE
17
- This is in testing... It works on two very different projects. I'd love people to try it out and provide feedback.
18
-
19
- ## Overview
20
- The idea is simple. You provide TS-Service-Worker with a **Plan** (and options), and it outputs a functional Javascript file that can be sent to the browser. The **Plan** is a series of caching strategies to be applied, along with the paths they should be applied to.
16
+ This is in testing... It works on two production projects. I'd love people to try it out and provide feedback.
21
17
 
22
18
  ## Examples
23
19
 
24
- The very simplest service worker might provide an offline backup of the whole site:
20
+ The very simplest service worker might provide an offline backup of the whole site. This uses one strategy:
25
21
  ```ts
26
22
  import {requestHandler} from "ts-service-worker";
27
23
 
@@ -30,10 +26,11 @@ export const plan: Plan = [
30
26
  ]
31
27
  app.get('/service-worker.js', requestHandler(plan))
32
28
  ```
33
- This plan says, "intercept all the requests, and serve them from the network if available; otherwise, serve from the cache." This is just one of the example caching strategies, and demonstrates the declarative configuration-- and how a simple caching strategy like this is insufficient-- even though almost every single example implementation uses strategies like this. (shrug)
29
+ This plan says, "intercept all the requests, and serve them from the network if available; otherwise, serve from the cache."
30
+ Although this is just one example, it demonstrates the declarative configuration. In real life (and despite service worker documentation!), a simple caching strategy like this is seldom-- if ever-- sufficient.
34
31
 
35
32
  Some of the deficiencies of this strategy:
36
- - it only caches paths that have been visited
33
+ - it only caches paths that have been visited already
37
34
  - provides no way to update cached files
38
35
  - caches everything fetched, even unimportant resources
39
36
 
@@ -41,12 +38,15 @@ A real caching plan requires more complexity and nuance. Here is part of a sligh
41
38
  ```ts
42
39
  export const plan: Plan =
43
40
  [
44
- {strategy: 'networkOnly', paths: ['/open-search.xml']},
45
- {strategy: 'staleWhileRevalidate', path: ['/my-data.json']},
46
- {strategy: 'staticOfflineBackup', path: [/.*\.html/], backup: '/networkDown.html'}
41
+ {paths: ['/open-search.xml'], strategy: 'networkOnly'},
42
+ {paths: ['/my-data.json'], strategy: 'staleWhileRevalidate'},
43
+ {paths: [/.*\.html/], strategy: 'staticOfflineBackup',
44
+ backup: '/networkDown.html' }
47
45
  ]
48
46
  ```
49
- Here, we see multiple strategies working in consort. Each request is evaluated (sequentially) against the paths, and the first matching strategy is applied. If no strategy matches, the resource is fetched as normal. Therefore, in this example:
47
+ Here, we see how multiple strategies work in consort. Each request is evaluated (sequentially) against the paths, and the first matching strategy is applied. If no strategy matches, the resource is fetched as normal.
48
+
49
+ Therefore, in this example:
50
50
  - The path `open-search.xml` will never be cached,
51
51
  - `/my-data.json` will be served from the cache and re-fetched in the background, available for a later access.
52
52
  - Finally, the file `/networkDown.html` will be cached, and served as a replacement for other HTML pages when fetching fails.
@@ -56,9 +56,10 @@ Another example: a common strategy is to pre-cache some resources when the servi
56
56
  {
57
57
  strategy: 'cache-on-install',
58
58
  files: {
59
- dir: 'src/assets/images', // Root of search
60
- glob: '*.png', // match
61
- prefix: '/img'} // pre-pended to matched relative path
59
+ dir: 'src/assets/images', // Root of search
60
+ glob: '*.png', // match
61
+ prefix: '/img' // pre-pended to matched relative path
62
+ }
62
63
  }
63
64
  ```
64
65
  These are just a few examples of a caching plan. Each application is different and requires careful consideration and crafting. **TS-Service-Worker** makes developing a custom caching service worker easy and error-free.
@@ -71,8 +72,7 @@ There are several ways to use this:
71
72
 
72
73
  ### Within Express
73
74
 
74
- This package exports a request handler called, `requestHandle`. So using directly within Express (or equivalent) is convenient and provides good Typescript type hints.
75
- This request handler builder is a function that accepts a **plan** and **options** and returns a function that can be used as a route handler. Here's an example of how to use it in an Express app:
75
+ This package exports a request handler called requestHandler. So using it directly within Express (or equivalent) is convenient and provides good Typescript type hints. This request handler builder is a function that accepts a Plan and options and returns a function that can be used as a route handler. Here's an example of how to use it in an Express app:
76
76
 
77
77
  ```ts
78
78
  import { requestHandler } from 'ts-service-worker'
@@ -82,7 +82,7 @@ app.get('/service-worker.js', requestHandler([
82
82
  // ...
83
83
  ]))
84
84
  ```
85
- Note: you may want to manage your own caching of this, although the generation of the service worker is fast.
85
+ Note: you may want to manage your own caching of the service worker file itself on the server.
86
86
 
87
87
  ### As a script
88
88
 
@@ -139,27 +139,26 @@ The CLI will generate the Javascript file from the specifications in a JSON file
139
139
 
140
140
  1. Create a `sw-plan.json` file with `plan` and `options`. See below for available properties.
141
141
  2. Run `> npx ts-service-worker sw-plan.json >dist/service-worker.js`
142
- 3. Serve the resultant file (`dist/service-worker.js`) as your service worker, and reference it in your HTML head.
143
- 4. Editing the JSON file and regenerate as needed.
142
+ 3. Serve the resultant file (`dist/service-worker.js`) as your service worker, and reference it in your HTML `<head>` section.
143
+ 4. Edit the JSON file and regenerate as needed.
144
144
 
145
145
  ### Other
146
- There are other ways to incorporate this into your build cycle. The library exposes a method `generateServiceWorker()` which accepts a plan and options and generates the necessary Javascript (text). Let me know how you do it!
146
+ There are other ways to incorporate this into your build cycle. The library exposes a function `generateServiceWorker()` which accepts a plan and options and generates the necessary Javascript (text). Let me know how you do it!
147
147
 
148
148
  Note that the output of this tool needs _no_ post-processing, so DON'T DO IT. If the code output isn't compatible with a browser,
149
149
  let me know and I'll fix it right away.
150
150
 
151
- ### Options
151
+ ## Definitions
152
152
 
153
- The following may be provided as `options` for any of the usage methods, and they affect the generation of the Javascript code:
154
-
155
- * `version` – semantic version of the worker. Major or minor version bump means to reset the cache, but otherwise this is for you.
156
- * `skipWaiting` – executes the normal skip waiting logic, and allows your service worker to come into play without all the browser windows being closed.
157
- * `debug` – outputs log messages as files are fetched. This is too noisy for production, but may be useful to figure out what is going on with your service worker.
153
+ - A **Strategy** is a ruleset to fetch and cache resources based on matching URLs.
154
+ - A **Plan** is a set of strategies used together to manage the caching of resources for a web app.
155
+ - A **Path expression** is one or more strings or regular expressions that describe a set of resources.
156
+ - **Options** are non-strategy settings for a Plan, governing how the service worker itself is managed.
158
157
 
159
158
  ## Paths
160
159
 
161
160
  Paths expressions, used for most strategies, will match URLs requested from your website. The path property can be:
162
- - **a string URL path**. This is interpreted as the meaning the full path to the resource.
161
+ - **a string URL path**. This is interpreted as the meaning the full path to the resource. This is usually a path from the root of the domain, but could be a full URL if desired.
163
162
  - **a RegExp**, matching the full URL. This is useful for matching a set of URLs, such as all the images in a directory.
164
163
  - **an array** of strings or RegExps, following the rules above
165
164
  - **the imported symbol `OriginAndBelow`**, which matches everything under the service worker's domain.
@@ -169,47 +168,45 @@ Paths expressions, used for most strategies, will match URLs requested from your
169
168
 
170
169
  These are the currently implemented strategies.
171
170
 
172
- ### cacheFirst
173
-
174
- This strategy is ideal for CSS, images, fonts, JS, templates… basically anything you'd consider static to that "version" of your site. Per [https://web.dev/learn/pwa/serving/#cache-first](https://web.dev/learn/pwa/serving/#cache-first):
175
- >> Using this strategy, the service worker looks for the matching request in the cache and returns the corresponding Response if it's cached. Otherwise it retrieves the response from the network (~~optionally,~~ updating the cache for future calls). If there is neither a cache response nor a network response, the request will error. Since serving assets without going to the network tends to be faster, this strategy prioritizes performance over freshness.
171
+ ### `cacheFirst`
176
172
 
177
- There is no way to remove files from the cache individually. For this reason, use only for immutable resources (certain images and fingerprinted assets). If these do need to be updated, you will need to [rebuild the cache (see below)](#clearing-the-cache).
173
+ This strategy is ideal for CSS, images, fonts, JS, templates… basically anything you'd consider static to that "version" of your site.
174
+ "Using this strategy, the service worker looks for the matching request in the cache and returns the corresponding Response if it's cached. Otherwise it retrieves the response from the network (updating the cache for future calls). If there is neither a cache response nor a network response, the request will error." ([https://web.dev/learn/pwa/serving/#cache-first](https://web.dev/learn/pwa/serving/#cache-first))
178
175
 
179
- ### networkFirst
180
- This is most useful to provide the user with a backup in case the network isn't available. Per [https://web.dev/learn/pwa/serving/#network-first](https://web.dev/learn/pwa/serving/#network-first):
176
+ And the gist of it:
177
+ > Since serving assets without going to the network tends to be faster, this strategy prioritizes performance over freshness.
181
178
 
182
- > This strategy is the mirror of the Cache First strategy; it checks if the request can be fulfilled from the network and, if it can't, tries to retrieve it from the cache. If there is neither a network response nor a cache response, the request will error. Getting the response from the network is usually slower than getting it from the cache, this strategy prioritizes updated content instead of performance.
179
+ There is no way to remove files from the browser cache individually. For this reason, only use this cache for _immutable resources_ (certain images and fingerprinted assets). If these do need to be updated, you will need to [rebuild the cache (see below)](#clearing-the-cache).
183
180
 
184
- ### staleWhileRevalidate
181
+ ### `networkFirst`
182
+ This provides the user with a backup in case the network isn't available. It prioritizes updated content instead of performance. It won't speed up your site, but will allow it to keep working if the network is down, of flakey.
185
183
 
186
- This is useful for resources that could be slow to fetch and whose freshness is not critical. Per [https://web.dev/learn/pwa/serving/#stale-while-revalidate](https://web.dev/learn/pwa/serving/#stale-while-revalidate):
184
+ "This strategy ... checks if the request can be fulfilled from the network and, if it can't, tries to retrieve it from the cache. If there is neither a network response nor a cache response, the request will error. Getting the response from the network is usually slower than getting it from the cache." ([https://web.dev/learn/pwa/serving/#network-first](https://web.dev/learn/pwa/serving/#network-first))
187
185
 
188
- > The stale while revalidate strategy returns a cached response immediately, then checks the network for an update, replacing the cached response if one is found. This strategy always makes a network request, because even if a cached resource is found, it will try to update what was in the cache with what was received from the network, to use the updated version in the next request. This strategy, therefore, provides a way for you to benefit from the quick serving of the cache first strategy and update the cache in the background.
189
186
 
190
- > With this strategy, the assets are updated in the background. This means your users won't get the new version of the assets until the next time that path is requested. At that time the strategy will again check if a new version exists and the cycle repeats.
187
+ ### `staleWhileRevalidate`
191
188
 
192
- ### networkOnly
189
+ This is useful for resources that could be slow to fetch and whose freshness is not critical.
190
+ "The stale while revalidate strategy returns a cached response immediately, then checks the network for an update, replacing the cached response if one is found. This strategy always makes a network request, because even if a cached resource is found, it will try to update what was in the cache with what was received from the network, to use the updated version in the next request. This strategy, therefore, provides a way for you to benefit from the quick serving of the cache first strategy and update the cache in the background.
193
191
 
194
- Per [https://web.dev/learn/pwa/serving/#network-only](https://web.dev/learn/pwa/serving/#network-only):
192
+ "With this strategy, the assets are updated in the background. This means your users won't get the new version of the assets until the next time that path is requested. At that time the strategy will again check if a new version exists and the cycle repeats." ([https://web.dev/learn/pwa/serving/#stale-while-revalidate](https://web.dev/learn/pwa/serving/#stale-while-revalidate))
195
193
 
196
- > The network only strategy is similar to how browsers behave without a service worker or the Cache Storage API. Requests will only return a resource if it can be fetched from the network. This is often useful for resources like online-only API requests.
194
+ ### `networkOnly`
195
+ "The network only strategy is similar to how browsers behave without a service worker or the Cache Storage API. Requests will only return a resource if it can be fetched from the network. This is often useful for resources like online-only API requests." ([https://web.dev/learn/pwa/serving/#network-only](https://web.dev/learn/pwa/serving/#network-only))
197
196
 
198
- ### staticOfflineBackup
197
+ ### `staticOfflineBackup`
199
198
 
200
199
  Use this strategy to provide a backup for a resource to be used only when a site is offline. This may be a "sorry" page, or could provide some reasonable alternative. This is commonly used for placeholder images or other content.
201
200
 
202
- The backup resource is loaded _on service worker install_. If it is stale, you will need to invalidate the whole cache.
203
-
204
- Note: this currently doesn't support inline SVGs that you see in some examples, but requires using a separate file.
201
+ The backup resource is loaded _when the service worker is installed_. If it is stale, you will need to invalidate the whole cache by bumping the minor version.
205
202
 
206
203
  Note: If this backup URL is also a regular resource on the site, you can configure its behavior separately from `staticOfflineBackup`. It does **not** serve the "backup" URL from cache by default. If it's a normal app in the URL and you want it cached, also include a strategy like `networkFirst` in combination.
207
204
 
208
- ### cache-on-install
205
+ ### `cache-on-install`
209
206
 
210
- Cache specific paths when the service worker installs. Because it needs to know specific resources, _path matching wildcards and regular expression will not work._ For example, you can't pre-cache all files that end in `.png`-- there's no way for the service worker to know what they are! You must use FULL paths.
207
+ Cache specific resources when the service worker installs.
211
208
 
212
- But! This strategy has an extra feature that other strategies do not: it can **scan directories for resources.** In some cases this, such as when you have a whole directory of files to pre-cache, this will be the easiest to maintain: use glob patterns to identify these as shown below. Here's one example:
209
+ Because it needs to know the specific paths, _path matching wildcards and regular expression will not work._ For example, from within a running service worker, you can't find all files that end in `.png`. Therefore, you must use FULL resource paths. To facilitate this, this strategy can **scan directories of your local disk for resources.** When you have a whole directory of files to pre-cache, this will be the easy to maintain: use glob patterns to identify these as shown below. Here's one example:
213
210
 
214
211
  ```ts
215
212
  plan: Plan = [
@@ -217,45 +214,51 @@ plan: Plan = [
217
214
  strategy: 'cache on install',
218
215
  files: {
219
216
  glob: '**/*.png',
220
- dir: 'src/images', // where to start searching
221
- prefix: '/assets' // prefix applied to any matches
217
+ dir: 'src/images', // where to start searching on your disk
218
+ prefix: '/assets' // prefix applied to any matches in URLs
222
219
  },
223
220
  }
224
221
  ]
225
222
  ```
226
223
  The globs are evaluated _when the `service-worker.js` file is created_, so updating resources will require rebuilding the service worker file.
227
224
 
228
- ## Cache and Versioning of Service Workers
225
+ ## Options
226
+
227
+ The following may be provided as `options`, and they affect the generation of the Javascript code:
229
228
 
230
- When developing service workers, it can take a while to learn service worker behavior, and understanding when it re-installs and activates service workers and rebuilds caches. This is worth understanding, but **TS-Service-Worker** aims to provide the appropriate controls.
229
+ * `version` – semantic version of the worker. Major or minor version change implies (and forces) a reset of the cache on the browser. It has no other meaning.
230
+ * `skipWaiting` – executes the normal skip waiting logic, and allows your service worker to come into play without all the browser windows being closed.
231
+ * `debug` – outputs log messages as files are fetched. This is too noisy for production, but may be useful to figure out what is going on with your service worker.
231
232
 
232
- #### Clearing the Cache
233
+ #### `Version` and Clearing the Cache
233
234
  There are two components that relevant here:
234
235
 
235
236
  - the service worker
236
237
  - the cached files (or "responses")
237
238
 
238
- When building your service worker, provide a `version` string in the options, following semvar conventions. If you change this version at all, the service worker itself will be re-installed (because it has changed). This re-installation in itself, however, will _not_ affect the cached responses.
239
+ When building your service worker, provide a `version` string in the options, following semvar conventions.
239
240
 
240
- But, if the _major or minor version_ changes, the cache (of responses) will be deleted and a complete new cache created. _With this library, the way to reset the cache in users' browsers is to bump the major or minor version number._ (To test locally, instead, use the browser's developer tools.)
241
+ There are two rules that apply to this version:
241
242
 
242
- That being said, you're not in complete control. Browsers will manage the actual response cache, removing items if it becomes too large, or perhaps stale. From my testing, though, I have not seen this happen.
243
+ 1. If you change this version at all, the service worker itself will be re-installed (because it has changed). This re-installation in itself, however, will _not_ affect the browser cache of responses.
244
+
245
+ 2. If the _major or minor version_ changes, the cache (of responses) will be deleted and a complete new cache created. _With this library, the way to reset the cache in users' browsers is to bump the major or minor version number._ (To test locally you can use the browser's developer tools.)
243
246
 
244
- Some of the strategies will replace older assets with newer ones when found; some won't. This should be documented above or obvious.
247
+ That being said, you're not in complete control. Browsers will manage the actual response cache, removing items if it becomes too large, or perhaps stale. From my testing, though, I have not seen this happen.
245
248
 
246
249
  #### Skip Waiting
247
250
 
248
- Service workers themselves will re-install when they have changed-- based on the browsers' rules but typically a checksum of the `service-worker.js` code itself. This can be confusing at first, though, as new service workers doesn't come into play until it is activated. The default behavior for service workers is to wait until all the windows associated with the service worker to close, and only then activate the new service worker.
251
+ Service workers themselves will re-install when they have changed-- based on the browsers' rules but typically a checksum of the `service-worker.js` code itself. This can be confusing at first, though, as new service workers doesn't come into play until it is activated. The default behavior for service workers is to wait until all the windows associated with the service worker are closed, and only then, activate the new service worker.
249
252
 
250
- If this isn't what you want, you can accelerate this by passing `skipWaiting: true` in the options. This will activate the service worker as soon as it is downloaded-- but still will not be available as the page first loads. Usually this means it will be ready on the next reload.
253
+ If this isn't what you want, you can accelerate this by passing `skipWaiting: true` in the options. This will activate the service worker as soon as it is downloaded-- but it still will not be available as the page first loads. Usually this means it will be ready on the next reload.
251
254
 
252
255
  ## Why TS-Service-Worker?
253
256
 
254
257
  Service workers are a powerful tool for developers to improve the user experience, providing for fine-grained caching, offline usage and progressive web apps (PWAs). It replaced the coarse-grained _AppCache_, which proved quite hard to use (and was retired).
255
258
 
256
- Unfortunately, the service worker API is also hard to use as well, as it is quite low-level. It requires a series of nested promise chains, and quickly grows complicated. In the documentation contains good examples describing single specific strategies. While these are readable, they are minimally useful because real-world usage requires integrating several of these strategies at the same time. This can be error-prone and hard to follow. And as anyone who has developed service workers knows, they are tedious to test because of the complex lifecycle.
259
+ Unfortunately, the service worker API is also hard to use, as it is quite low-level. It requires a series of nested promise chains, and in any real world situation, quickly grows complicated. In the documentation contains good examples describing single specific strategies. While these are readable, they are minimally useful because real-world usage requires integrating several of these strategies at the same time. This can be error-prone and hard to follow. And as anyone who has developed service workers knows, they are tedious to test because of the complex lifecycle. Examples provide no guidance, or examples, where multiple strategies are applied.
257
260
 
258
- And since service workers run in the browser, they must be written in the browser's version Javascript. This likely involves transcompilation, which will requiring additional complexity in the build process to produce a separate artifact. It can be quite challenging in a Typescript and JS-bundling project (most projects). Typically the service-worker will require a different build path than the rest of your code because it must be distributed as a separate resource.
261
+ Another big hurdle for some developers is that since service workers run in the browser, they must be written in a browser-friendly version Javascript (not Typescript). This likely involves transcompilation, which will requiring additional complexity in the build process to produce an additional artifact. It can be quite challenging in a Typescript and JS-bundling project (most projects). The service-worker will require a different build path than the rest of your code because it must be distributed as a separate resource.
259
262
 
260
263
  There are [other libraries](#other-libraries) designed to help, which may be useful to you. **TS-Service-Worker** aims to solve specific challenges.
261
264
 
@@ -275,7 +278,7 @@ Published using `npm publish`.
275
278
  - [ ] Other matching mechanisms, like mime-type.
276
279
  - [ ] https://web.dev/learn/pwa/serving/#cache-only
277
280
  - [ ] filter/only strategy: to process only certain types, eg. `{ strategy: 'filter', paths: ' /https?:.*/ }`. This is weird semantically, so I'm still thinking.
278
- - [ ] https://web.dev/learn/pwa/workbox/#offline-fallback
281
+ - [x] https://web.dev/learn/pwa/workbox/#offline-fallback
279
282
  - [ ] https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#recovering_failed_requests
280
283
  - [ ] offline SVG: https://github.com/veiss-com/sw-tools#offline
281
284
  - [ ] https://github.com/veiss-com/sw-tools#limiting-the-cache-size
@@ -300,6 +303,6 @@ These are the basics of how service workers function:
300
303
 
301
304
  ## License
302
305
 
303
- Copyright (c) 2023-2024 Andrew J. Peterson, dba NDP Software All Rights Reserved.
306
+ Copyright (c) 2023-2025 Andrew J. Peterson, dba NDP Software All Rights Reserved.
304
307
 
305
308
  Available for licensing at reasonable rate. Please contact [NDP Software](mailto:andy@ndpsoftware.com?subject=ts-service-worker).
package/dist/cli.mjs CHANGED
@@ -6638,7 +6638,7 @@ function fileFromCache(name) {
6638
6638
  function toCache(event, response) {
6639
6639
  if (!response.ok) {
6640
6640
  log(\`skipping cache of failed request \${event.request.url} with status \${response.status} \${response.statusText}\`);
6641
- return response;
6641
+ return Promise.resolve(response);
6642
6642
  }
6643
6643
  return caches.open(CACHE_NAME)
6644
6644
  .then(function (cache) {
package/dist/index.js CHANGED
@@ -6650,7 +6650,7 @@ function fileFromCache(name) {
6650
6650
  function toCache(event, response) {
6651
6651
  if (!response.ok) {
6652
6652
  log(\`skipping cache of failed request \${event.request.url} with status \${response.status} \${response.statusText}\`);
6653
- return response;
6653
+ return Promise.resolve(response);
6654
6654
  }
6655
6655
  return caches.open(CACHE_NAME)
6656
6656
  .then(function (cache) {