ts-service-worker 1.1.2 → 1.3.0
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 +123 -53
- package/dist/.keep +0 -0
- package/dist/cli.mjs +27 -7
- package/dist/index.js +27 -7
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/types/.keep +0 -0
- package/types/generate.d.mts +2 -0
package/README.md
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
Automate the build of a complex, strongly-typed service worker.
|
|
2
|
+
|
|
3
|
+
# Goals
|
|
2
4
|
|
|
3
5
|
- provide declarative support for the most common caching strategies
|
|
4
6
|
- supports easy integration of multiple caching strategies
|
|
5
|
-
- produces a `service-worker.js` that requires no additional
|
|
7
|
+
- produces a `service-worker.js` that requires no additional transcompilation
|
|
6
8
|
- avoids nesting and promise chains
|
|
7
|
-
- is written in
|
|
9
|
+
- is written in fully-typed Typescript
|
|
8
10
|
- can scan your disk for resources to cache
|
|
9
11
|
- provides built-in debug logging
|
|
10
12
|
- provides clear and simple cache purging rules
|
|
11
13
|
|
|
12
|
-
Copyright (c) 2023 Andrew J. Peterson, dba NDP Software
|
|
14
|
+
Copyright (c) 2023-2024 Andrew J. Peterson, dba NDP Software All Rights Reserved.
|
|
13
15
|
|
|
14
16
|
#### NOTICE
|
|
15
17
|
This is in testing... It works on two very different projects. I'd love people to try it out and provide feedback.
|
|
@@ -24,50 +26,53 @@ The very simplest service worker might provide an offline backup of the whole si
|
|
|
24
26
|
import {requestHandler} from "ts-service-worker";
|
|
25
27
|
|
|
26
28
|
export const plan: Plan = [
|
|
27
|
-
{paths: /.*/, strategy: "networkFirst"}
|
|
29
|
+
{paths: /.*/, strategy: "networkFirst"}
|
|
28
30
|
]
|
|
29
31
|
app.get('/service-worker.js', requestHandler(plan))
|
|
30
32
|
```
|
|
31
|
-
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 is
|
|
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)
|
|
32
34
|
|
|
33
|
-
Some of the
|
|
35
|
+
Some of the deficiencies of this strategy:
|
|
34
36
|
- it only caches paths that have been visited
|
|
35
37
|
- provides no way to update cached files
|
|
36
38
|
- caches everything fetched, even unimportant resources
|
|
37
39
|
|
|
38
|
-
A real caching plan requires more nuance. Here is part of a slightly more realistic plan:
|
|
40
|
+
A real caching plan requires more complexity and nuance. Here is part of a slightly more realistic plan:
|
|
39
41
|
```ts
|
|
40
42
|
export const plan: Plan =
|
|
41
43
|
[
|
|
42
44
|
{strategy: 'networkOnly', paths: ['/open-search.xml']},
|
|
43
|
-
{strategy: 'staleWhileRevalidate', ['/my-data.json']},
|
|
44
|
-
{strategy: 'staticOfflineBackup', [/.*\.html/], backup: '/networkDown.html'}
|
|
45
|
+
{strategy: 'staleWhileRevalidate', path: ['/my-data.json']},
|
|
46
|
+
{strategy: 'staticOfflineBackup', path: [/.*\.html/], backup: '/networkDown.html'}
|
|
45
47
|
]
|
|
46
48
|
```
|
|
47
|
-
Here, we see multiple strategies in
|
|
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:
|
|
48
50
|
- The path `open-search.xml` will never be cached,
|
|
49
51
|
- `/my-data.json` will be served from the cache and re-fetched in the background, available for a later access.
|
|
50
52
|
- Finally, the file `/networkDown.html` will be cached, and served as a replacement for other HTML pages when fetching fails.
|
|
51
53
|
|
|
52
|
-
Another common strategy is to pre-cache some resources
|
|
54
|
+
Another example: a common strategy is to pre-cache some resources when the service worker initializes. The paths of these resources can be explicitly listed, or dynamically determined from glob matches of files on disk:
|
|
53
55
|
```ts
|
|
54
56
|
{
|
|
55
57
|
strategy: 'cache-on-install',
|
|
56
58
|
files: {
|
|
57
59
|
dir: 'src/assets/images', // Root of search
|
|
58
60
|
glob: '*.png', // match
|
|
59
|
-
prefix: '/img'} // pre-
|
|
61
|
+
prefix: '/img'} // pre-pended to matched relative path
|
|
60
62
|
}
|
|
61
63
|
```
|
|
62
|
-
These are just
|
|
64
|
+
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.
|
|
65
|
+
|
|
66
|
+
## Getting Started and Usage
|
|
63
67
|
|
|
64
|
-
|
|
68
|
+
Install with npm, yarn or your weapon of choice. There are no prerequisites, but Typescript is key to reaping the benefits.
|
|
65
69
|
|
|
66
|
-
|
|
70
|
+
There are several ways to use this:
|
|
67
71
|
|
|
68
72
|
### Within Express
|
|
69
73
|
|
|
70
|
-
|
|
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:
|
|
71
76
|
|
|
72
77
|
```ts
|
|
73
78
|
import { requestHandler } from 'ts-service-worker'
|
|
@@ -77,6 +82,56 @@ app.get('/service-worker.js', requestHandler([
|
|
|
77
82
|
// ...
|
|
78
83
|
]))
|
|
79
84
|
```
|
|
85
|
+
Note: you may want to manage your own caching of this, although the generation of the service worker is fast.
|
|
86
|
+
|
|
87
|
+
### As a script
|
|
88
|
+
|
|
89
|
+
You can use the library to make a script in your build process. Although it will require you integrate this
|
|
90
|
+
in your build process, this is nice because it's declarative, and can provide full typing.
|
|
91
|
+
|
|
92
|
+
To do this, create a file like `service-worker-build.ts` and use the `generateServiceWorker()` function. Here's an example:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import type { Plan } from 'ts-service-worker'
|
|
96
|
+
import { generateServiceWorker } from 'ts-service-worker'
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
const plan: Plan = [
|
|
100
|
+
{
|
|
101
|
+
"strategy": "cache-on-install",
|
|
102
|
+
"paths": [
|
|
103
|
+
"images/splash.png",
|
|
104
|
+
//..
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"strategy": "networkFirst",
|
|
109
|
+
"paths": [
|
|
110
|
+
"/src/site.css",
|
|
111
|
+
"/src/index.css"
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"strategy": 'staticOfflineBackup',
|
|
116
|
+
'paths': ["/", "/index.html"],
|
|
117
|
+
"backup": "/index.html"
|
|
118
|
+
},
|
|
119
|
+
// etc.
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
const options = {
|
|
123
|
+
"version": "5.0.0",
|
|
124
|
+
"skipWaiting": true,
|
|
125
|
+
"debug": true
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log(generateServiceWorker(plan, options))
|
|
129
|
+
```
|
|
130
|
+
The integration may be a simple as a line in your `package.json` file,
|
|
131
|
+
|
|
132
|
+
"build:sw": "ts-node service-worker-build.ts > serviceWorker.js"
|
|
133
|
+
|
|
134
|
+
or it may be more complex.
|
|
80
135
|
|
|
81
136
|
### CLI
|
|
82
137
|
|
|
@@ -90,24 +145,25 @@ The CLI will generate the Javascript file from the specifications in a JSON file
|
|
|
90
145
|
### Other
|
|
91
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!
|
|
92
147
|
|
|
93
|
-
Note that the output of this tool needs _no_ post-processing, so
|
|
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
|
+
let me know and I'll fix it right away.
|
|
94
150
|
|
|
95
151
|
### Options
|
|
96
152
|
|
|
97
|
-
The following may be provided as options for any of the usage methods, and they affect the generation of the Javascript code:
|
|
153
|
+
The following may be provided as `options` for any of the usage methods, and they affect the generation of the Javascript code:
|
|
98
154
|
|
|
99
|
-
* `version` – semantic version of the worker. Major version bump means to reset the cache.
|
|
155
|
+
* `version` – semantic version of the worker. Major or minor version bump means to reset the cache, but otherwise this is for you.
|
|
100
156
|
* `skipWaiting` – executes the normal skip waiting logic, and allows your service worker to come into play without all the browser windows being closed.
|
|
101
|
-
* `debug` – outputs log messages as files are fetched. This is too
|
|
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.
|
|
102
158
|
|
|
103
159
|
## Paths
|
|
104
160
|
|
|
105
161
|
Paths expressions, used for most strategies, will match URLs requested from your website. The path property can be:
|
|
106
|
-
- a string URL path
|
|
107
|
-
- a RegExp
|
|
108
|
-
- an array of strings or RegExps, following the rules above
|
|
109
|
-
- the symbol `OriginAndBelow
|
|
110
|
-
- the symbol `ScopeAndBelow
|
|
162
|
+
- **a string URL path**. This is interpreted as the meaning the full path to the resource.
|
|
163
|
+
- **a RegExp**, matching the full URL. This is useful for matching a set of URLs, such as all the images in a directory.
|
|
164
|
+
- **an array** of strings or RegExps, following the rules above
|
|
165
|
+
- **the imported symbol `OriginAndBelow`**, which matches everything under the service worker's domain.
|
|
166
|
+
- **the imported symbol `ScopeAndBelow`**, which matches everything under the service worker's scope, which is the URL of the service worker code (overrideable).
|
|
111
167
|
|
|
112
168
|
## Strategies
|
|
113
169
|
|
|
@@ -115,19 +171,19 @@ These are the currently implemented strategies.
|
|
|
115
171
|
|
|
116
172
|
### cacheFirst
|
|
117
173
|
|
|
118
|
-
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]:
|
|
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):
|
|
119
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.
|
|
120
176
|
|
|
121
|
-
There is no way to remove files from the cache. 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).
|
|
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).
|
|
122
178
|
|
|
123
179
|
### networkFirst
|
|
124
|
-
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]:
|
|
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):
|
|
125
181
|
|
|
126
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.
|
|
127
183
|
|
|
128
184
|
### staleWhileRevalidate
|
|
129
185
|
|
|
130
|
-
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]:
|
|
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):
|
|
131
187
|
|
|
132
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.
|
|
133
189
|
|
|
@@ -135,79 +191,91 @@ This is useful for resources that could be slow to fetch and whose freshness is
|
|
|
135
191
|
|
|
136
192
|
### networkOnly
|
|
137
193
|
|
|
138
|
-
Per [https://web.dev/learn/pwa/serving/#network-only]:
|
|
194
|
+
Per [https://web.dev/learn/pwa/serving/#network-only](https://web.dev/learn/pwa/serving/#network-only):
|
|
139
195
|
|
|
140
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.
|
|
141
197
|
|
|
142
198
|
### staticOfflineBackup
|
|
143
199
|
|
|
144
|
-
Use this strategy to provide a backup for a resource
|
|
200
|
+
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.
|
|
145
201
|
|
|
146
|
-
The backup resource is loaded
|
|
202
|
+
The backup resource is loaded _on service worker install_. If it is stale, you will need to invalidate the whole cache.
|
|
147
203
|
|
|
148
204
|
Note: this currently doesn't support inline SVGs that you see in some examples, but requires using a separate file.
|
|
149
205
|
|
|
150
|
-
Note: this does **not** serve the "backup" URL from cache by default. If it's a normal app in the URL and you want it cached,
|
|
206
|
+
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.
|
|
151
207
|
|
|
152
208
|
### cache-on-install
|
|
153
209
|
|
|
154
|
-
Cache specific paths when the service worker installs. Because it needs to know specific resources,
|
|
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.
|
|
155
211
|
|
|
156
|
-
But
|
|
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:
|
|
157
213
|
|
|
158
214
|
```ts
|
|
159
215
|
plan: Plan = [
|
|
160
216
|
{
|
|
217
|
+
strategy: 'cache on install',
|
|
161
218
|
files: {
|
|
162
219
|
glob: '**/*.png',
|
|
163
220
|
dir: 'src/images', // where to start searching
|
|
164
221
|
prefix: '/assets' // prefix applied to any matches
|
|
165
222
|
},
|
|
166
|
-
strategy: 'cache on install'
|
|
167
223
|
}
|
|
168
224
|
]
|
|
169
225
|
```
|
|
170
|
-
The globs are evaluated
|
|
226
|
+
The globs are evaluated _when the `service-worker.js` file is created_, so updating resources will require rebuilding the service worker file.
|
|
171
227
|
|
|
172
228
|
## Cache and Versioning of Service Workers
|
|
173
229
|
|
|
174
|
-
When developing service workers, it can take a while to learn service worker behavior, understanding when it re-installs service workers and rebuilds caches. This is worth understanding, but **TS-Service-Worker**
|
|
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.
|
|
175
231
|
|
|
176
232
|
#### Clearing the Cache
|
|
177
233
|
There are two components that relevant here:
|
|
178
234
|
|
|
179
235
|
- the service worker
|
|
180
|
-
- the
|
|
236
|
+
- the cached files (or "responses")
|
|
181
237
|
|
|
182
|
-
When building your service worker, provide a `version` string in the options,
|
|
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.
|
|
183
239
|
|
|
184
|
-
|
|
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
|
+
|
|
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.
|
|
185
243
|
|
|
186
244
|
Some of the strategies will replace older assets with newer ones when found; some won't. This should be documented above or obvious.
|
|
187
245
|
|
|
188
246
|
#### Skip Waiting
|
|
189
247
|
|
|
190
|
-
Service workers themselves will re-install when they have changed-- based on the browsers' rules. This can be confusing at first, though, as new service workers
|
|
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.
|
|
191
249
|
|
|
192
|
-
If this isn't what you want, you can
|
|
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.
|
|
193
251
|
|
|
194
252
|
## Why TS-Service-Worker?
|
|
195
253
|
|
|
196
|
-
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.
|
|
254
|
+
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).
|
|
197
255
|
|
|
198
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.
|
|
199
257
|
|
|
200
|
-
And since service workers run in the browser, they must be written in the browser's version Javascript. This likely involves
|
|
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.
|
|
201
259
|
|
|
202
260
|
There are [other libraries](#other-libraries) designed to help, which may be useful to you. **TS-Service-Worker** aims to solve specific challenges.
|
|
203
261
|
|
|
204
|
-
|
|
262
|
+
|
|
263
|
+
## Development
|
|
264
|
+
|
|
265
|
+
All developer actions are listed in the `scripts` section of `package.json`. The most important are:
|
|
266
|
+
- `example1` and `example2`: see some sample Plans and output
|
|
267
|
+
- `test`: run the automated tests
|
|
268
|
+
- `build`: build the library (everything)
|
|
269
|
+
|
|
270
|
+
Published using `npm publish`.
|
|
271
|
+
|
|
272
|
+
### TODO
|
|
205
273
|
1. Other matching mechanisms, like mime-type.
|
|
206
274
|
2. https://web.dev/learn/pwa/serving/#cache-only
|
|
207
275
|
3. filter/only strategy: to process only certain types, eg. `{ strategy: 'filter', paths: ' /https?:.*/ }`. This is weird semantically, so I'm still thinking.
|
|
208
|
-
4. allowOnly: /
|
|
209
276
|
5. https://web.dev/learn/pwa/workbox/#offline-fallback
|
|
210
|
-
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#recovering_failed_requests
|
|
277
|
+
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#recovering_failed_requests
|
|
278
|
+
6. offline SVG: https://github.com/veiss-com/sw-tools#offline
|
|
211
279
|
6. https://github.com/veiss-com/sw-tools#limiting-the-cache-size
|
|
212
280
|
7. expiring cache / expirations on cached items
|
|
213
281
|
|
|
@@ -218,9 +286,11 @@ These are the basics of how service workers function:
|
|
|
218
286
|
- Interesting reference showing the complexities: https://adactio.com/serviceworker.js and https://adactio.medium.com/cache-limiting-in-service-workers-d6741361ca19
|
|
219
287
|
- The complexities of testing service workers: [https://medium.com/dev-channel/testing-service-workers-318d7b016b19]
|
|
220
288
|
- Service worker mocking library: [https://github.com/zackargyle/service-workers/tree/master/packages/service-worker-mock]
|
|
221
|
-
- See the service workers generated with this tool:
|
|
289
|
+
- See the service workers generated with this tool:
|
|
290
|
+
- <a href="https://amp-what.com/service-worker.js" nofollow noindex>https://amp-what.com/service-worker.js</a>
|
|
291
|
+
- <a href="https://www.gotomyhead.site/serviceWorker.js" nofollow noindex>https://www.gotomyhead.site/serviceWorker.js</a>
|
|
222
292
|
|
|
223
|
-
|
|
293
|
+
### Other Libraries (The comp-e-ti-tion)
|
|
224
294
|
- https://web.dev/learn/pwa/workbox/ -- popular and very cool tool. This will work for many projects, but I was looking for something where I had a little more control.
|
|
225
295
|
- https://vite-pwa-org.netlify.app/frameworks/sveltekit.html
|
|
226
296
|
- https://github.com/veiss-com/sw-tools#offline -- very specific features
|
|
@@ -228,6 +298,6 @@ These are the basics of how service workers function:
|
|
|
228
298
|
|
|
229
299
|
## License
|
|
230
300
|
|
|
231
|
-
Copyright (c) 2023 Andrew J. Peterson, dba NDP Software
|
|
301
|
+
Copyright (c) 2023-2024 Andrew J. Peterson, dba NDP Software All Rights Reserved.
|
|
232
302
|
|
|
233
|
-
Available for licensing at reasonable rate. Please contact NDP Software.
|
|
303
|
+
Available for licensing at reasonable rate. Please contact [NDP Software](mailto:andy@ndpsoftware.com?subject=ts-service-worker).
|
package/dist/.keep
ADDED
|
File without changes
|
package/dist/cli.mjs
CHANGED
|
@@ -6529,8 +6529,9 @@ var TEMPLATE = `
|
|
|
6529
6529
|
"use strict";
|
|
6530
6530
|
const ORIGIN_MATCHER = new RegExp(\`^\${regexEscape(self.origin)}.*\`, "i");
|
|
6531
6531
|
const SCOPE_MATCHER = new RegExp(\`^\${regexEscape(self.registration.scope)}.*\`, "i");
|
|
6532
|
+
|
|
6532
6533
|
// VARIABLES
|
|
6533
|
-
|
|
6534
|
+
|
|
6534
6535
|
self.addEventListener('install', function (event) {
|
|
6535
6536
|
event.waitUntil(caches.open(CACHE_NAME).then(function (cache) {
|
|
6536
6537
|
return cache.addAll(PRELOAD_PATHS)
|
|
@@ -6551,7 +6552,9 @@ self.addEventListener('activate', function (event) {
|
|
|
6551
6552
|
}))));
|
|
6552
6553
|
});
|
|
6553
6554
|
self.addEventListener('fetch', function (event) {
|
|
6554
|
-
|
|
6555
|
+
|
|
6556
|
+
const url = event.request.url.replace(/#.*$/, '');
|
|
6557
|
+
|
|
6555
6558
|
// Cache http and https only, skip unsupported chrome-extension:// and file://...
|
|
6556
6559
|
if (!url.startsWith('http'))
|
|
6557
6560
|
return;
|
|
@@ -6568,7 +6571,7 @@ self.addEventListener('fetch', function (event) {
|
|
|
6568
6571
|
function matches(paths) {
|
|
6569
6572
|
return paths &&
|
|
6570
6573
|
paths.some(path => path instanceof RegExp ?
|
|
6571
|
-
path.exec(url) : url
|
|
6574
|
+
path.exec(url) : url === path);
|
|
6572
6575
|
}
|
|
6573
6576
|
});
|
|
6574
6577
|
// Caching strategies
|
|
@@ -6620,7 +6623,11 @@ function fetchAndCache(event) {
|
|
|
6620
6623
|
log(\` - unable to cache \${event.request.url} \${e}\`);
|
|
6621
6624
|
return response;
|
|
6622
6625
|
})
|
|
6623
|
-
})
|
|
6626
|
+
})
|
|
6627
|
+
.catch(e => {
|
|
6628
|
+
log(\` - unable to fetch \${event.request.url} \${e}\`);
|
|
6629
|
+
throw e
|
|
6630
|
+
});
|
|
6624
6631
|
}
|
|
6625
6632
|
function fromCache(event) {
|
|
6626
6633
|
return caches.open(CACHE_NAME).then(cache => cache.match(event.request));
|
|
@@ -6630,7 +6637,7 @@ function fileFromCache(name) {
|
|
|
6630
6637
|
}
|
|
6631
6638
|
function toCache(event, response) {
|
|
6632
6639
|
if (!response.ok) {
|
|
6633
|
-
log(\`request
|
|
6640
|
+
log(\`skipping cache of failed request \${event.request.url} with status \${response.status} \${response.statusText}\`);
|
|
6634
6641
|
return response;
|
|
6635
6642
|
}
|
|
6636
6643
|
return caches.open(CACHE_NAME)
|
|
@@ -6649,6 +6656,12 @@ function log(s) {
|
|
|
6649
6656
|
function regexEscape(s) {
|
|
6650
6657
|
return s.replace(/([/.])/g, '\\\\$&');
|
|
6651
6658
|
}
|
|
6659
|
+
|
|
6660
|
+
function withOrigin(path) {
|
|
6661
|
+
if (path.match(/https?:\\/\\//)) return path
|
|
6662
|
+
if (path.startsWith('/')) return self.origin + path
|
|
6663
|
+
return self.origin + '/' + path
|
|
6664
|
+
}
|
|
6652
6665
|
`;
|
|
6653
6666
|
|
|
6654
6667
|
// src/utils.mts
|
|
@@ -6685,13 +6698,20 @@ function generateServiceWorker(inputSpec, optionsIn = {}) {
|
|
|
6685
6698
|
...optionsIn
|
|
6686
6699
|
};
|
|
6687
6700
|
const { preloadPaths, routable } = organizeStrategies(inputSpec);
|
|
6688
|
-
const varsBlock = generateFlag("DEBUG", options.debug) + generateFlag("SKIP_WAITING", options.skipWaiting) + generateVersion(options.version) + generatePreloadCode(preloadPaths) + generateRoutes(routable);
|
|
6701
|
+
const varsBlock = generateFlag("DEBUG", options.debug) + generateFlag("SKIP_WAITING", options.skipWaiting) + generateVersion(options.version) + generateCacheName(options.version) + generatePreloadCode(preloadPaths) + generateRoutes(routable);
|
|
6689
6702
|
return evalTemplate(varsBlock);
|
|
6690
6703
|
}
|
|
6691
6704
|
function generateVersion(version) {
|
|
6692
6705
|
return `const VERSION = '${version}';
|
|
6693
6706
|
`;
|
|
6694
6707
|
}
|
|
6708
|
+
function cacheName(version) {
|
|
6709
|
+
const parts = version.split(".");
|
|
6710
|
+
return (parts[0] || "0") + "." + (parts[1] || "0");
|
|
6711
|
+
}
|
|
6712
|
+
function generateCacheName(version) {
|
|
6713
|
+
return `const CACHE_NAME = '${cacheName(version)}';`;
|
|
6714
|
+
}
|
|
6695
6715
|
function generateFlag(name, bool) {
|
|
6696
6716
|
return `const ${name} = ${bool};
|
|
6697
6717
|
`;
|
|
@@ -6710,7 +6730,7 @@ function evalTemplate(variables) {
|
|
|
6710
6730
|
}
|
|
6711
6731
|
function pathsToJS(paths) {
|
|
6712
6732
|
const asArray = Array.isArray(paths) ? paths : [paths];
|
|
6713
|
-
return `[${asArray.map(pathToJS).join(",")}]`;
|
|
6733
|
+
return `[${asArray.map(pathToJS).join(",")}].map(withOrigin)`;
|
|
6714
6734
|
}
|
|
6715
6735
|
function pathToJS(path3) {
|
|
6716
6736
|
switch (typeof path3) {
|
package/dist/index.js
CHANGED
|
@@ -6541,8 +6541,9 @@ var TEMPLATE = `
|
|
|
6541
6541
|
"use strict";
|
|
6542
6542
|
const ORIGIN_MATCHER = new RegExp(\`^\${regexEscape(self.origin)}.*\`, "i");
|
|
6543
6543
|
const SCOPE_MATCHER = new RegExp(\`^\${regexEscape(self.registration.scope)}.*\`, "i");
|
|
6544
|
+
|
|
6544
6545
|
// VARIABLES
|
|
6545
|
-
|
|
6546
|
+
|
|
6546
6547
|
self.addEventListener('install', function (event) {
|
|
6547
6548
|
event.waitUntil(caches.open(CACHE_NAME).then(function (cache) {
|
|
6548
6549
|
return cache.addAll(PRELOAD_PATHS)
|
|
@@ -6563,7 +6564,9 @@ self.addEventListener('activate', function (event) {
|
|
|
6563
6564
|
}))));
|
|
6564
6565
|
});
|
|
6565
6566
|
self.addEventListener('fetch', function (event) {
|
|
6566
|
-
|
|
6567
|
+
|
|
6568
|
+
const url = event.request.url.replace(/#.*$/, '');
|
|
6569
|
+
|
|
6567
6570
|
// Cache http and https only, skip unsupported chrome-extension:// and file://...
|
|
6568
6571
|
if (!url.startsWith('http'))
|
|
6569
6572
|
return;
|
|
@@ -6580,7 +6583,7 @@ self.addEventListener('fetch', function (event) {
|
|
|
6580
6583
|
function matches(paths) {
|
|
6581
6584
|
return paths &&
|
|
6582
6585
|
paths.some(path => path instanceof RegExp ?
|
|
6583
|
-
path.exec(url) : url
|
|
6586
|
+
path.exec(url) : url === path);
|
|
6584
6587
|
}
|
|
6585
6588
|
});
|
|
6586
6589
|
// Caching strategies
|
|
@@ -6632,7 +6635,11 @@ function fetchAndCache(event) {
|
|
|
6632
6635
|
log(\` - unable to cache \${event.request.url} \${e}\`);
|
|
6633
6636
|
return response;
|
|
6634
6637
|
})
|
|
6635
|
-
})
|
|
6638
|
+
})
|
|
6639
|
+
.catch(e => {
|
|
6640
|
+
log(\` - unable to fetch \${event.request.url} \${e}\`);
|
|
6641
|
+
throw e
|
|
6642
|
+
});
|
|
6636
6643
|
}
|
|
6637
6644
|
function fromCache(event) {
|
|
6638
6645
|
return caches.open(CACHE_NAME).then(cache => cache.match(event.request));
|
|
@@ -6642,7 +6649,7 @@ function fileFromCache(name) {
|
|
|
6642
6649
|
}
|
|
6643
6650
|
function toCache(event, response) {
|
|
6644
6651
|
if (!response.ok) {
|
|
6645
|
-
log(\`request
|
|
6652
|
+
log(\`skipping cache of failed request \${event.request.url} with status \${response.status} \${response.statusText}\`);
|
|
6646
6653
|
return response;
|
|
6647
6654
|
}
|
|
6648
6655
|
return caches.open(CACHE_NAME)
|
|
@@ -6661,6 +6668,12 @@ function log(s) {
|
|
|
6661
6668
|
function regexEscape(s) {
|
|
6662
6669
|
return s.replace(/([/.])/g, '\\\\$&');
|
|
6663
6670
|
}
|
|
6671
|
+
|
|
6672
|
+
function withOrigin(path) {
|
|
6673
|
+
if (path.match(/https?:\\/\\//)) return path
|
|
6674
|
+
if (path.startsWith('/')) return self.origin + path
|
|
6675
|
+
return self.origin + '/' + path
|
|
6676
|
+
}
|
|
6664
6677
|
`;
|
|
6665
6678
|
|
|
6666
6679
|
// src/utils.mts
|
|
@@ -6697,13 +6710,20 @@ function generateServiceWorker(inputSpec, optionsIn = {}) {
|
|
|
6697
6710
|
...optionsIn
|
|
6698
6711
|
};
|
|
6699
6712
|
const { preloadPaths, routable } = organizeStrategies(inputSpec);
|
|
6700
|
-
const varsBlock = generateFlag("DEBUG", options.debug) + generateFlag("SKIP_WAITING", options.skipWaiting) + generateVersion(options.version) + generatePreloadCode(preloadPaths) + generateRoutes(routable);
|
|
6713
|
+
const varsBlock = generateFlag("DEBUG", options.debug) + generateFlag("SKIP_WAITING", options.skipWaiting) + generateVersion(options.version) + generateCacheName(options.version) + generatePreloadCode(preloadPaths) + generateRoutes(routable);
|
|
6701
6714
|
return evalTemplate(varsBlock);
|
|
6702
6715
|
}
|
|
6703
6716
|
function generateVersion(version) {
|
|
6704
6717
|
return `const VERSION = '${version}';
|
|
6705
6718
|
`;
|
|
6706
6719
|
}
|
|
6720
|
+
function cacheName(version) {
|
|
6721
|
+
const parts = version.split(".");
|
|
6722
|
+
return (parts[0] || "0") + "." + (parts[1] || "0");
|
|
6723
|
+
}
|
|
6724
|
+
function generateCacheName(version) {
|
|
6725
|
+
return `const CACHE_NAME = '${cacheName(version)}';`;
|
|
6726
|
+
}
|
|
6707
6727
|
function generateFlag(name, bool) {
|
|
6708
6728
|
return `const ${name} = ${bool};
|
|
6709
6729
|
`;
|
|
@@ -6722,7 +6742,7 @@ function evalTemplate(variables) {
|
|
|
6722
6742
|
}
|
|
6723
6743
|
function pathsToJS(paths) {
|
|
6724
6744
|
const asArray = Array.isArray(paths) ? paths : [paths];
|
|
6725
|
-
return `[${asArray.map(pathToJS).join(",")}]`;
|
|
6745
|
+
return `[${asArray.map(pathToJS).join(",")}].map(withOrigin)`;
|
|
6726
6746
|
}
|
|
6727
6747
|
function pathToJS(path2) {
|
|
6728
6748
|
switch (typeof path2) {
|