jg-sw 1.1.0 → 1.1.1

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/API.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # <a id="api-reference"></a>jg-sw API reference
2
2
 
3
- Every export of `jg-sw@1.1.0`, 26 in all, with its signature, its errors, and an example. It's generated from the package's TSDoc.
3
+ Every export of `jg-sw@1.1.1`, 26 in all, with its signature, its errors, and an example. It's generated from the package's TSDoc.
4
4
 
5
5
  ## <a id="api-entry-main"></a>`jg-sw`
6
6
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.1
4
+
5
+ ### Added
6
+
7
+ - `RECIPES.md`, patterns for apps that use the package: an offline Vite app, and telling people an update is ready. Each block is typechecked against the release.
8
+
3
9
  ## 1.1.0
4
10
 
5
11
  A fresh start: breaking changes in a minor version, once, because nothing depended on 1.0.x.
package/README.md CHANGED
@@ -161,6 +161,8 @@ The worker loads Firebase's compat SDK from `gstatic`. When no app window is vis
161
161
 
162
162
  The full API reference ships in the package as `API.md`, generated from its TSDoc for the installed version, and is at https://jg-packages.jesusgraterol.dev/packages/jg-sw/.
163
163
 
164
+ Recipes for common jobs ship as `RECIPES.md`, with every block typechecked against the release, and are at https://jg-packages.jesusgraterol.dev/packages/jg-sw/recipes/.
165
+
164
166
  ## License
165
167
 
166
168
  [MIT](https://github.com/jesusgraterol/jg-packages/blob/main/packages/jg-sw/LICENSE)
package/RECIPES.md ADDED
@@ -0,0 +1,105 @@
1
+ # jg-sw recipes
2
+
3
+ Patterns for Vite apps that keep working offline, and tell people when a new version is ready.
4
+
5
+ ## An offline Vite app
6
+
7
+ Use this when the app should open without a network after the first visit. The plugin writes `sw.js` into the build after Vite's own files, and the worker caches the files you list when it installs. Putting the config in `vite.config.ts` gets it typechecked with the rest of the file:
8
+
9
+ ```ts
10
+ // vite.config.ts
11
+ import { defineConfig } from 'vite';
12
+ import { serviceWorkerPlugin } from 'jg-sw/build';
13
+
14
+ export default defineConfig({
15
+ plugins: [
16
+ serviceWorkerPlugin({
17
+ config: {
18
+ // Vite's build.outDir, where the worker is written too
19
+ outDir: 'dist',
20
+ template: 'base',
21
+
22
+ // the namespace of this app's caches, so a new version deletes only its own old ones
23
+ cacheNamePrefix: 'my-app',
24
+
25
+ // the page and Vite's hashed bundles, which is all the app needs to start offline
26
+ includeToPrecache: ['/', '/index.html', '/assets'],
27
+ excludeFilesFromPrecache: [],
28
+
29
+ // the worker answers from its cache first, so keep API data and other pages out of it
30
+ excludeMIMETypesFromCache: ['application/json', 'text/html'],
31
+ },
32
+ }),
33
+ ],
34
+ });
35
+ ```
36
+
37
+ The worker answers same-origin `GET` requests from its cache before the network, and caches what it fetches, so whatever it caches stays the same until the next build replaces the cache. That suits hashed bundles, and would serve stale data from an API on the same origin: `excludeMIMETypesFromCache` keeps JSON out, and responses marked `Cache-Control: no-store` or `private` never enter the cache either. Add the files from `public/` that the page loads, such as its icons, to `includeToPrecache` when they should show offline. `config` also takes a path, such as `'jg-sw.config.json'`, when the CLI reads the same file.
38
+
39
+ Register the worker from the app's entry module. Precaching downloads every listed file, so it starts once the page has loaded:
40
+
41
+ ```ts
42
+ // src/main.ts
43
+ import { hasErrorCode } from 'jg-exception';
44
+ import { ERRORS, registerServiceWorker } from 'jg-sw';
45
+
46
+ window.addEventListener(
47
+ 'load',
48
+ async () => {
49
+ // it never rejects: a failure lands in registrationError
50
+ const { registrationError } = await registerServiceWorker();
51
+
52
+ // without service workers the app still runs, online only, so that case needs no report
53
+ if (
54
+ registrationError !== undefined &&
55
+ !hasErrorCode(registrationError, ERRORS.SW_NO_BROWSER_SUPPORT)
56
+ ) {
57
+ console.error(registrationError);
58
+ }
59
+ },
60
+ { once: true },
61
+ );
62
+ ```
63
+
64
+ During `vite`, the dev server answers `/sw.js` with a worker that caches nothing, so registration works and every request still reaches the dev server. To try the offline app, run `vite build` and `vite preview`, load the page once, then turn the network off in the browser's devtools. The worker matches each request exactly and has no fallback page, so offline, open the app at `/`: reloading a client-side route, such as `/settings`, gets the worker's network error instead of the app.
65
+
66
+ ## Telling people an update is ready
67
+
68
+ Each build makes a new worker, which installs next to the running one and waits, so open pages keep the version they loaded. Use this to tell people a new version is ready and let them choose when to switch, because switching reloads the page. The state comes from `registerServiceWorker`, so call it first, as in the previous recipe:
69
+
70
+ ```ts
71
+ import { getServiceWorkerSnapshot, subscribeToServiceWorker, updateApp } from 'jg-sw';
72
+
73
+ // shows the button only while a new version waits
74
+ const showUpdateState = (): void => {
75
+ updateButton.hidden = !getServiceWorkerSnapshot().hasWaitingWorker;
76
+ };
77
+ subscribeToServiceWorker(showUpdateState);
78
+
79
+ // a version may already wait when this runs, such as one installed during an earlier visit
80
+ showUpdateState();
81
+
82
+ updateButton.addEventListener('click', () => {
83
+ // it reloads the page, so it runs only when the person asks
84
+ updateButton.disabled = true;
85
+ void updateApp();
86
+ });
87
+ ```
88
+
89
+ `updateApp()` asks the waiting worker to take over, and reloads the page a single time when the new worker controls it, never in a loop. Don't call it on your own while a form holds unsaved input. A "Check for updates" item, such as in a settings menu, can call it with no version waiting: it looks for one, waits up to `UPDATE_INSTALL_TIMEOUT_SECONDS` for it to install, and reloads the page either way.
90
+
91
+ Browsers look for a new worker when a page in its scope loads. A single-page app can stay open for days without loading another page, so look again when the person returns to the tab:
92
+
93
+ ```ts
94
+ import { getServiceWorkerSnapshot } from 'jg-sw';
95
+
96
+ document.addEventListener('visibilitychange', () => {
97
+ const { registration } = getServiceWorkerSnapshot();
98
+ if (document.visibilityState === 'visible' && registration !== undefined) {
99
+ // offline, the check fails, and the next return to the tab tries again
100
+ registration.update().catch(() => undefined);
101
+ }
102
+ });
103
+ ```
104
+
105
+ When the check finds a new version, the snapshot changes once it has installed, and the button from the previous block appears.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jg-sw",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Service workers for web apps: a generated caching worker with a Vite plugin and CLI, plus registration, updates, and installation.",
5
5
  "author": "Jesus Graterol",
6
6
  "license": "MIT",
@@ -23,7 +23,8 @@
23
23
  "files": [
24
24
  "dist",
25
25
  "CHANGELOG.md",
26
- "API.md"
26
+ "API.md",
27
+ "RECIPES.md"
27
28
  ],
28
29
  "sideEffects": false,
29
30
  "engines": {
@@ -50,9 +51,9 @@
50
51
  "firebase-cloud-messaging"
51
52
  ],
52
53
  "dependencies": {
53
- "jg-exception": "^1.1.0",
54
- "jg-fs": "^1.1.0",
55
- "jg-utilities": "^1.1.0",
54
+ "jg-exception": "^1.2.0",
55
+ "jg-fs": "^1.1.1",
56
+ "jg-utilities": "^1.1.1",
56
57
  "zod": "^4.6.7"
57
58
  },
58
59
  "peerDependencies": {