@alxia/react-router 0.9.0 → 0.10.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.
Files changed (51) hide show
  1. package/README.md +126 -13
  2. package/dist/chunks/index-59vhfj6n.js +10 -0
  3. package/dist/chunks/index-59vhfj6n.js.map +10 -0
  4. package/dist/chunks/index-5gqcpyyg.js +34 -0
  5. package/dist/chunks/index-5gqcpyyg.js.map +10 -0
  6. package/dist/chunks/{index-zqa30gr7.js → index-9xv2a8et.js} +7 -38
  7. package/dist/chunks/index-9xv2a8et.js.map +11 -0
  8. package/dist/chunks/index-mppz0806.js +119 -0
  9. package/dist/chunks/index-mppz0806.js.map +11 -0
  10. package/dist/chunks/index-rfr02vg4.js +40 -0
  11. package/dist/chunks/index-rfr02vg4.js.map +10 -0
  12. package/dist/chunks/index-zdbqzcjp.js +97 -0
  13. package/dist/chunks/index-zdbqzcjp.js.map +12 -0
  14. package/dist/pwa/build/build-worker.d.ts +41 -0
  15. package/dist/pwa/build/build-worker.d.ts.map +1 -0
  16. package/dist/pwa/build/index.d.ts +9 -0
  17. package/dist/pwa/build/index.d.ts.map +1 -0
  18. package/dist/pwa/build/index.js +9 -0
  19. package/dist/pwa/build/index.js.map +9 -0
  20. package/dist/pwa/build/precache.d.ts +17 -0
  21. package/dist/pwa/build/precache.d.ts.map +1 -0
  22. package/dist/pwa/index.js +34 -134
  23. package/dist/pwa/index.js.map +4 -8
  24. package/dist/pwa/react/index.js +1 -1
  25. package/dist/pwa/worker/index.d.ts +9 -0
  26. package/dist/pwa/worker/index.d.ts.map +1 -0
  27. package/dist/pwa/worker/index.js +144 -0
  28. package/dist/pwa/worker/index.js.map +12 -0
  29. package/dist/pwa/worker/injected.d.ts +15 -0
  30. package/dist/pwa/worker/injected.d.ts.map +1 -0
  31. package/dist/pwa/worker/plan.d.ts +65 -0
  32. package/dist/pwa/worker/plan.d.ts.map +1 -0
  33. package/dist/pwa/worker/scope.d.ts +18 -0
  34. package/dist/pwa/worker/scope.d.ts.map +1 -0
  35. package/dist/pwa/worker/start-worker.d.ts +17 -0
  36. package/dist/pwa/worker/start-worker.d.ts.map +1 -0
  37. package/dist/vite/index.d.ts +10 -0
  38. package/dist/vite/index.d.ts.map +1 -1
  39. package/dist/vite/index.js +49 -3
  40. package/dist/vite/index.js.map +6 -5
  41. package/dist/vite/runtime.d.ts +1 -1
  42. package/dist/vite/runtime.d.ts.map +1 -1
  43. package/dist/vite/worker.d.ts +16 -0
  44. package/dist/vite/worker.d.ts.map +1 -0
  45. package/docs/README.md +1 -1
  46. package/docs/guide/how-it-works.md +7 -1
  47. package/docs/guide/pwa.md +372 -211
  48. package/docs/roadmap.md +19 -5
  49. package/docs/troubleshooting.md +208 -3
  50. package/package.json +46 -4
  51. package/dist/chunks/index-zqa30gr7.js.map +0 -12
package/docs/roadmap.md CHANGED
@@ -11,14 +11,12 @@ Nothing in progress.
11
11
 
12
12
  ## Next
13
13
 
14
- - **The service worker and its build.** A worker of Workbox strategies over
15
- `cachingFor` and `isStorable`, and the build that writes it, reading
16
- `precacheList` and possibly run by `/vite`'s `alxia()`, with the `workbox-*`
17
- packages as optional peers. Until it ships, the worker and its build stay
18
- the app's.
14
+ Nothing planned.
19
15
 
20
16
  ## Later
21
17
 
18
+ - **`service-worker.js` served revalidated with no setup.** Today the app
19
+ adds `noCache` for `SERVICE_WORKER_URL` itself.
22
20
  - **A check of an offline page's policy.** The offline page declares its
23
21
  Content-Security-Policy in a `<meta>`; a check would read the page and
24
22
  confirm that each inline script and style is allowed by its hash.
@@ -55,6 +53,22 @@ Nothing in progress.
55
53
 
56
54
  ## Shipped
57
55
 
56
+ ### 0.10.0
57
+
58
+ - **`@alxia/react-router/pwa/worker`.** `startWorker({ contract })` is the
59
+ whole service worker on Workbox: the precache, a page load always from the
60
+ network with the offline page when it fails, hashed bundles cache-first,
61
+ other static files stale-while-revalidate, and the update applied on
62
+ request. An app's `app/service-worker.ts` is a few lines; the `workbox-*`
63
+ packages are optional peers ([guide](guide/pwa.md)).
64
+ - **`@alxia/react-router/pwa/build`.** `buildWorker({ file })` bundles the
65
+ worker after `react-router build`, with the precache list written in: the
66
+ offline page, every route's code and the fonts you name.
67
+ - **`alxia({ worker })`.** With `app/service-worker.ts` in the app,
68
+ `react-router build` writes `build/client/service-worker.js` itself, with
69
+ no script to add; `worker` names another file, tunes the build or turns
70
+ it off.
71
+
58
72
  ### 0.9.0
59
73
 
60
74
  - **`@alxia/react-router/pwa`.** The code with no UI an installable app
@@ -8,12 +8,14 @@ a loader, a message React Router or the browser prints, or an error from
8
8
  **Thrown or printed**
9
9
 
10
10
  - [`alxia-react-router: … is running on Node, and alxia's server runs on Bun. …`](#alxia-react-router--is-running-on-node-and-alxias-server-runs-on-bun-)
11
+ - [`alxia-react-router: react-router build with a service worker is running on Node, and the worker is bundled by Bun.build`](#alxia-react-router-react-router-build-with-a-service-worker-is-running-on-node-and-the-worker-is-bundled-by-bunbuild)
11
12
  - [`ReferenceError: Bun is not defined`](#referenceerror-bun-is-not-defined)
12
13
  - [`alxiaOf(): this request has no alxia context. …`](#alxiaof-this-request-has-no-alxia-context-)
13
14
  - [`TypeError: undefined is not an object (evaluating 'context.alxia.user')`](#typeerror-undefined-is-not-an-object-evaluating-contextalxiauser)
14
15
  - [`Error: No value found for context`](#error-no-value-found-for-context)
15
16
  - [`alxia-react-router: … must export createServer() from @alxia/react-router as its default export: …`](#alxia-react-router--must-export-createserver-from-alxiareact-router-as-its-default-export-)
16
17
  - [`alxia-react-router: the entry … does not exist. …`](#alxia-react-router-the-entry--does-not-exist-)
18
+ - [`alxia-react-router: the worker … does not exist. …`](#alxia-react-router-the-worker--does-not-exist-)
17
19
  - [`alxia-react-router: React Router's Vite plugin is not in this config. …`](#alxia-react-router-react-routers-vite-plugin-is-not-in-this-config-)
18
20
  - [`alxia-react-router: serverBundles splits React Router's server build in several, and alxia serves one. …`](#alxia-react-router-serverbundles-splits-react-routers-server-build-in-several-and-alxia-serves-one-)
19
21
  - [`alxia-react-router: Vite's ssr environment does not run modules in this process, so the server cannot be loaded.`](#alxia-react-router-vites-ssr-environment-does-not-run-modules-in-this-process-so-the-server-cannot-be-loaded)
@@ -42,6 +44,15 @@ a loader, a message React Router or the browser prints, or an error from
42
44
  - [`TypeError: defineWorkerContract(): cacheVersion must be a non-empty name without '-', …`](#typeerror-defineworkercontract-cacheversion-must-be-a-non-empty-name-without---)
43
45
  - [`parseBrowserManifest(): not React Router's browser manifest …`](#parsebrowsermanifest-not-react-routers-browser-manifest-)
44
46
  - [`error: Cannot find package 'react' from '…/@alxia/react-router/dist/pwa/react/index.js'`](#error-cannot-find-package-react-from-alxiareact-routerdistpwareactindexjs)
47
+ - [`startWorker(): no precache list. …`](#startworker-no-precache-list-)
48
+ - [`TypeError: startWorker(): contract is required: …`](#typeerror-startworker-contract-is-required-)
49
+ - [`error: Could not resolve: "workbox-core". Maybe you need to "bun install"?`](#error-could-not-resolve-workbox-core-maybe-you-need-to-bun-install)
50
+ - [`buildWorker() runs on Bun: it is running on Node`](#buildworker-runs-on-bun-it-is-running-on-node)
51
+ - [`buildWorker(): app/service-worker.ts does not exist.`](#buildworker-appservice-workerts-does-not-exist)
52
+ - [`buildWorker(): expected one React Router browser manifest`](#buildworker-expected-one-react-router-browser-manifest)
53
+ - [`buildWorker(): build/client/offline.html does not exist`](#buildworker-buildclientofflinehtml-does-not-exist)
54
+ - [`buildWorker(): … is outside ….`](#buildworker--is-outside-)
55
+ - [`buildWorker(): Bun.build failed:`](#buildworker-bunbuild-failed)
45
56
 
46
57
  **Types**
47
58
 
@@ -71,6 +82,8 @@ a loader, a message React Router or the browser prints, or an error from
71
82
  - [A cache the contract did not make is swept](#a-cache-the-contract-did-not-make-is-swept)
72
83
  - [The update notice never shows](#the-update-notice-never-shows)
73
84
  - [The worker registers under `react-router dev`](#the-worker-registers-under-react-router-dev)
85
+ - [The service worker is not built](#the-service-worker-is-not-built)
86
+ - [Offline, a client navigation to a route never visited shows the offline page](#offline-a-client-navigation-to-a-route-never-visited-shows-the-offline-page)
74
87
  - [An update waits an hour](#an-update-waits-an-hour)
75
88
  - [The worker cannot fetch another origin](#the-worker-cannot-fetch-another-origin)
76
89
  - [The manifest is in the wrong language](#the-manifest-is-in-the-wrong-language)
@@ -80,8 +93,8 @@ a loader, a message React Router or the browser prints, or an error from
80
93
  ### `alxia-react-router: … is running on Node, and alxia's server runs on Bun. …`
81
94
 
82
95
  **When:** `bun run dev`, `vite preview`, or a `react-router build` that
83
- prerenders, on a machine where a node is installed. The full message under
84
- `react-router dev`:
96
+ prerenders or builds a service worker (`app/service-worker.ts`), on a machine
97
+ where a node is installed. The full message under `react-router dev`:
85
98
 
86
99
  ```
87
100
  alxia-react-router: react-router dev is running on Node, and alxia's server runs on Bun. Add a bunfig.toml beside package.json with "[run]" and "bun = true", so bun run starts it on Bun, or run it as bun --bun react-router dev.
@@ -103,7 +116,18 @@ bun = true
103
116
  Or run the one command on Bun: `bun --bun react-router dev`. Under
104
117
  `vite preview` or a prerendering build, the message begins
105
118
  `alxia-react-router: vite preview, or a react-router build that prerenders, is running on Node`
106
- and names `bun --bun vite preview` and `bun --bun react-router build`.
119
+ and names `bun --bun vite preview` and `bun --bun react-router build`. A
120
+ build that has a service worker reads
121
+ `alxia-react-router: react-router build with a service worker is running on Node, and the worker is bundled by Bun.build`
122
+ and names the same `bun --bun react-router build`.
123
+
124
+ ### `alxia-react-router: react-router build with a service worker is running on Node, and the worker is bundled by Bun.build`
125
+
126
+ The same cause as
127
+ [the entry above](#alxia-react-router--is-running-on-node-and-alxias-server-runs-on-bun-):
128
+ the build runs on Node, and the worker is bundled with `Bun.build`. Run it as
129
+ `bun --bun react-router build`, or add a `bunfig.toml` beside `package.json`
130
+ with `[run]` and `bun = true`.
107
131
 
108
132
  ### `ReferenceError: Bun is not defined`
109
133
 
@@ -273,6 +297,20 @@ Vite's root. Under `react-router dev` each request is a 500 saying so;
273
297
 
274
298
  **Fix:** correct the path, or leave `entry` out.
275
299
 
300
+ ### `alxia-react-router: the worker … does not exist. …`
301
+
302
+ ```text
303
+ Error: alxia-react-router: the worker app/pwa/worker.ts does not exist. Create it, or leave worker out to build app/service-worker.ts when it exists.
304
+ ```
305
+
306
+ **When:** `alxia({ worker })` names a file that is not there, relative to
307
+ Vite's root, and `react-router build` fails.
308
+
309
+ **Why:** a missing `app/service-worker.ts` just means no worker, but a
310
+ `worker` you named is taken as meant.
311
+
312
+ **Fix:** correct the path, or leave `worker` out.
313
+
276
314
  ### `alxia-react-router: React Router's Vite plugin is not in this config. …`
277
315
 
278
316
  ```text
@@ -850,6 +888,150 @@ this message too.
850
888
  `/pwa/react` from the page alone. The worker and a build script import
851
889
  `@alxia/react-router/pwa`, which imports nothing.
852
890
 
891
+ ### `startWorker(): no precache list. …`
892
+
893
+ **Why:** the full message ends "Build this worker with `alxia()` from
894
+ `@alxia/react-router/vite` (`app/service-worker.ts`) or `buildWorker()` from
895
+ `@alxia/react-router/pwa/build`, which write it in." `startWorker` precaches
896
+ the list the build defines in the bundle (`globalThis.__ALXIA_PRECACHE__`);
897
+ a worker bundled by anything else has none, and the page would install a
898
+ worker that precaches nothing.
899
+
900
+ **Fix:** either build the worker with the build that writes the list
901
+ (`alxia()` or `buildWorker()`), or pass the list yourself, which works for a
902
+ worker bundled any other way:
903
+
904
+ ```ts
905
+ startWorker({
906
+ contract,
907
+ precache: [{ url: '/offline.html', revision: 'v1' }],
908
+ });
909
+ ```
910
+
911
+ `precache` wins over the list the build wrote. A bundle of your own may
912
+ instead define `globalThis.__ALXIA_PRECACHE__`.
913
+
914
+ ### `TypeError: startWorker(): contract is required: …`
915
+
916
+ **Why:** the full message continues "pass `defineWorkerContract({ cachePrefix })`
917
+ from `@alxia/react-router/pwa`." The worker's cache names, its offline page
918
+ and its URL come from the contract the page shares, so there is no default.
919
+
920
+ **Fix:**
921
+
922
+ ```ts
923
+ // file: app/service-worker.ts
924
+ import { defineWorkerContract } from '@alxia/react-router/pwa';
925
+ import { startWorker } from '@alxia/react-router/pwa/worker';
926
+
927
+ startWorker({ contract: defineWorkerContract({ cachePrefix: 'my-app' }) });
928
+ ```
929
+
930
+ ### `error: Could not resolve: "workbox-core". Maybe you need to "bun install"?`
931
+
932
+ **Why:** `@alxia/react-router/pwa/worker` imports six `workbox-*` packages,
933
+ optional peers of the package, so nothing installs them for you. They are
934
+ bundled into the worker, never loaded at run time.
935
+
936
+ **Fix:** add them as development dependencies:
937
+
938
+ ```sh
939
+ bun add -d workbox-core workbox-expiration workbox-navigation-preload \
940
+ workbox-precaching workbox-routing workbox-strategies
941
+ ```
942
+
943
+ The `buildWorker()` messages below are the ones a script of your own sees.
944
+ Under `alxia()` each of them begins with `alxia-react-router: `.
945
+
946
+ ### `buildWorker() runs on Bun: it is running on Node`
947
+
948
+ ```text
949
+ buildWorker() runs on Bun: it is running on Node. Run it with bun, or run react-router build with bun --bun.
950
+ ```
951
+
952
+ **When:** a script of your own calls `buildWorker()` and runs on Node. Under
953
+ `alxia()` the check comes first and reads
954
+ [`… is running on Node, and the worker is bundled by Bun.build`](#alxia-react-router--is-running-on-node-and-alxias-server-runs-on-bun-).
955
+
956
+ **Why:** the worker is bundled with `Bun.build`, which Node does not have.
957
+
958
+ **Fix:** run the script with `bun scripts/build-worker.ts`, or
959
+ `react-router build` as `bun --bun react-router build`.
960
+
961
+ ### `buildWorker(): app/service-worker.ts does not exist.`
962
+
963
+ ```text
964
+ buildWorker(): app/service-worker.ts does not exist.
965
+ ```
966
+
967
+ **Why:** `file` is read from `root` (`process.cwd()` by default), not from the
968
+ script's folder. Under `alxia()` the message begins
969
+ `alxia-react-router: buildWorker(): …`.
970
+
971
+ **Fix:** pass the path relative to the project root, or pass `root`.
972
+
973
+ ### `buildWorker(): expected one React Router browser manifest`
974
+
975
+ ```text
976
+ buildWorker(): expected one React Router browser manifest, build/client/assets/manifest-*.js, found 0. Run react-router build first.
977
+ ```
978
+
979
+ The message ends `found 2` when two are left.
980
+
981
+ **Why:** the precache list is read from React Router's browser manifest,
982
+ which `react-router build` writes. None means the build did not run, or
983
+ `client` is wrong. Two mean a stale build folder.
984
+
985
+ **Fix:** run `react-router build` first, check `client`, and delete `build/`
986
+ when two manifests are left.
987
+
988
+ ### `buildWorker(): build/client/offline.html does not exist`
989
+
990
+ ```text
991
+ buildWorker(): build/client/offline.html does not exist. Add public/offline.html, or pass offline: false (`worker: { offline: false }` in alxia()), or pass the app's contract (`worker: { contract }` in alxia()).
992
+ ```
993
+
994
+ **Why:** the offline page is precached by default and is read from the client
995
+ build, where `public/offline.html` is copied. The build reads the contract's
996
+ `offlineUrl` from the `contract` option, and the worker from the contract it
997
+ is given: two objects can disagree.
998
+
999
+ **Fix:** add the page to `public/`, or pass `offline: false` for a worker with
1000
+ no offline page. If the contract's `offlineUrl` is another path, pass the same
1001
+ contract object to the build (`alxia({ worker: { contract } })`, or
1002
+ `buildWorker({ contract })`).
1003
+
1004
+ ### `buildWorker(): … is outside ….`
1005
+
1006
+ ```text
1007
+ buildWorker(): /../service-worker.js is outside /app/build/client.
1008
+ ```
1009
+
1010
+ **Why:** the contract's `serviceWorkerUrl` or `offlineUrl` is joined onto the
1011
+ client build, and the result leaves it, or is the client build itself (`/`).
1012
+ The build refuses to write or read a file there. It also refuses an absolute
1013
+ URL (`https://example.com/sw.js`), a protocol-relative one (`//host/sw.js`) and
1014
+ one that does not start with `/`, since none names a file of the build.
1015
+
1016
+ **Fix:** use a root-relative URL inside the site, such as
1017
+ `/service-worker.js`, in the contract.
1018
+
1019
+ ### `buildWorker(): Bun.build failed:`
1020
+
1021
+ ```text
1022
+ buildWorker(): Bun.build failed:
1023
+ ```
1024
+
1025
+ Bun's logs follow the message.
1026
+
1027
+ **Why:** the worker's source or one of its imports did not bundle: a missing
1028
+ `workbox-*` package, a wrong import path.
1029
+
1030
+ **Fix:** read the first log line. The six `workbox-*` packages are optional
1031
+ peers, installed as
1032
+ [`Could not resolve: "workbox-core"`](#error-could-not-resolve-workbox-core-maybe-you-need-to-bun-install)
1033
+ shows.
1034
+
853
1035
  ## Types
854
1036
 
855
1037
  ### `Property 'alxia' does not exist on type 'Readonly<RouterContextProvider>'`
@@ -1335,6 +1517,29 @@ import.meta.env.PROD })` ([the hook](guide/pwa.md#in-react)). Then unregister
1335
1517
  the worker the dev server already took (DevTools, Application, Service
1336
1518
  Workers, Unregister) and clear its site data.
1337
1519
 
1520
+ ### The service worker is not built
1521
+
1522
+ `react-router build` ends and there is no `build/client/service-worker.js`.
1523
+
1524
+ **Why:** `alxia()` builds no worker when the file is not at
1525
+ `app/service-worker.ts` (the `appDirectory` of `react-router.config.ts` may
1526
+ hold another) and no `worker` option names it, when `worker: false` is set,
1527
+ or when the version of `@alxia/react-router` is older than 0.10.
1528
+
1529
+ **Fix:** put the file at `app/service-worker.ts`, or name it with
1530
+ `alxia({ worker: 'app/pwa/worker.ts' })`, and update the package.
1531
+
1532
+ ### Offline, a client navigation to a route never visited shows the offline page
1533
+
1534
+ The page opens offline, but a link to a route the user never opened leads to
1535
+ `/offline.html`.
1536
+
1537
+ **Why:** React Router asks `/__manifest` for the routes a link needs, and the
1538
+ worker never caches that request.
1539
+
1540
+ **Fix:** set `routeDiscovery: { mode: 'initial' }` in `react-router.config.ts`,
1541
+ so the client has every route from its first load.
1542
+
1338
1543
  ### An update waits an hour
1339
1544
 
1340
1545
  A new worker is deployed and tabs still run the old one for up to an hour.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alxia/react-router",
3
- "version": "0.9.0",
4
- "description": "alxia as the server of a React Router framework app: server rendering behind the app's hooks, loaders reading its typed context, the client's assets served; the code with no UI an installable app shares (`/pwa`), its hooks (`/pwa/react`) and `noCache` for the worker's script (`/pwa/server`)",
3
+ "version": "0.10.0",
4
+ "description": "alxia as the server of a React Router framework app: server rendering behind the app's hooks, loaders reading its typed context, the client's assets served; the code with no UI an installable app shares (`/pwa`), its hooks (`/pwa/react`), `noCache` for the worker's script (`/pwa/server`), the service worker on Workbox (`/pwa/worker`) and its build (`/pwa/build`)",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -42,6 +42,16 @@
42
42
  "import": "./dist/pwa/server/index.js",
43
43
  "default": "./dist/pwa/server/index.js"
44
44
  },
45
+ "./pwa/build": {
46
+ "types": "./dist/pwa/build/index.d.ts",
47
+ "import": "./dist/pwa/build/index.js",
48
+ "default": "./dist/pwa/build/index.js"
49
+ },
50
+ "./pwa/worker": {
51
+ "types": "./dist/pwa/worker/index.d.ts",
52
+ "import": "./dist/pwa/worker/index.js",
53
+ "default": "./dist/pwa/worker/index.js"
54
+ },
45
55
  "./package.json": "./package.json"
46
56
  },
47
57
  "repository": {
@@ -65,6 +75,8 @@
65
75
  "src/pwa/index.ts",
66
76
  "src/pwa/react/index.ts",
67
77
  "src/pwa/server/index.ts",
78
+ "src/pwa/worker/index.ts",
79
+ "src/pwa/build/index.ts",
68
80
  "src/cli/index.ts"
69
81
  ]
70
82
  },
@@ -80,14 +92,26 @@
80
92
  "react": "^19.3.1",
81
93
  "react-dom": "^19.3.1",
82
94
  "react-router": "^8.5.0",
83
- "vite": "^7.0.0"
95
+ "vite": "^7.0.0",
96
+ "workbox-core": "^7.4.1",
97
+ "workbox-expiration": "^7.4.1",
98
+ "workbox-navigation-preload": "^7.4.1",
99
+ "workbox-precaching": "^7.4.1",
100
+ "workbox-routing": "^7.4.1",
101
+ "workbox-strategies": "^7.4.1"
84
102
  },
85
103
  "peerDependencies": {
86
104
  "@alxia/core": "^0.15.1",
87
105
  "react": "^19.0.0",
88
106
  "react-router": "^8.0.0",
89
107
  "typescript": "^6.0.3 || ^7.0.0",
90
- "vite": "^7.0.0 || ^8.0.0"
108
+ "vite": "^7.0.0 || ^8.0.0",
109
+ "workbox-core": "^7.0.0",
110
+ "workbox-expiration": "^7.0.0",
111
+ "workbox-navigation-preload": "^7.0.0",
112
+ "workbox-precaching": "^7.0.0",
113
+ "workbox-routing": "^7.0.0",
114
+ "workbox-strategies": "^7.0.0"
91
115
  },
92
116
  "peerDependenciesMeta": {
93
117
  "react": {
@@ -95,6 +119,24 @@
95
119
  },
96
120
  "vite": {
97
121
  "optional": true
122
+ },
123
+ "workbox-core": {
124
+ "optional": true
125
+ },
126
+ "workbox-expiration": {
127
+ "optional": true
128
+ },
129
+ "workbox-navigation-preload": {
130
+ "optional": true
131
+ },
132
+ "workbox-precaching": {
133
+ "optional": true
134
+ },
135
+ "workbox-routing": {
136
+ "optional": true
137
+ },
138
+ "workbox-strategies": {
139
+ "optional": true
98
140
  }
99
141
  }
100
142
  }
@@ -1,12 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/pwa/model/worker-contract.ts", "../src/pwa/model/registration.ts", "../src/pwa/model/update-notice.ts"],
4
- "sourcesContent": [
5
- "/**\n * What the service worker and the page agree on: the worker's address, the\n * offline page, the message that applies an update, and the names of the\n * caches the worker owns. Dependency-free: the worker bundles it, with no\n * DOM and no React.\n */\n\n/** Where the build writes the worker; registered with scope `/`. */\nexport const SERVICE_WORKER_URL = '/service-worker.js';\n\n/** The page served when a navigation fails for want of a network. */\nexport const OFFLINE_URL = '/offline.html';\n\n/** The message a page posts to a waiting worker to make it take over. */\nexport const SKIP_WAITING = 'SKIP_WAITING';\n\nexport interface WorkerContractOptions {\n\t/**\n\t * Every Cache Storage cache the worker owns is named\n\t * `<cachePrefix>-<kind>-<version>` (kind: letters and digits; version:\n\t * no `-`); a cache of another shape is never swept.\n\t * Required: two apps on one origin must not share it.\n\t */\n\tcachePrefix: string;\n\t/**\n\t * Bumped when a cache's content changes shape: the next worker deletes\n\t * every cache of the prefix that has another version when it activates.\n\t * `'v1'` by default; not empty, and without a `-`.\n\t */\n\tcacheVersion?: string;\n\t/** `SERVICE_WORKER_URL` by default. */\n\tserviceWorkerUrl?: string;\n\t/** `OFFLINE_URL` by default. */\n\tofflineUrl?: string;\n}\n\nexport type CacheNames = {\n\t/**\n\t * The offline page and the hashed scripts and stylesheets of every route.\n\t * `<prefix>-precache-<version>` is the name Workbox builds from\n\t * `setCacheNameDetails({ prefix, precache: 'precache', suffix: version })`.\n\t */\n\treadonly precache: string;\n\t/** `/assets/*` bundles: hashed names, so cache-first. */\n\treadonly assets: string;\n\t/** Icons, sprites, the logo: stale-while-revalidate. */\n\treadonly static: string;\n};\n\nexport interface WorkerContract {\n\treadonly cachePrefix: string;\n\treadonly cacheVersion: string;\n\treadonly serviceWorkerUrl: string;\n\treadonly offlineUrl: string;\n\treadonly cacheNames: CacheNames;\n\t/** The caches a worker deletes on activate: the prefix's, of another version. */\n\tobsoleteCaches(names: readonly string[]): string[];\n\t/**\n\t * The caches `clearAppCaches()` deletes: all of the prefix's but the\n\t * current precache, which holds the offline page and the app's code, never\n\t * a user's data.\n\t */\n\tclearableCaches(names: readonly string[]): string[];\n}\n\n/** Builds the contract of one app from its cache prefix. */\nexport function defineWorkerContract(\n\toptions: WorkerContractOptions,\n): WorkerContract {\n\tconst { cachePrefix } = options;\n\tif (typeof cachePrefix !== 'string' || cachePrefix === '') {\n\t\tthrow new TypeError(\n\t\t\t`defineWorkerContract(): cachePrefix must be a non-empty name, such as 'my-app'; got '${String(cachePrefix)}'.`,\n\t\t);\n\t}\n\tif (cachePrefix.endsWith('-')) {\n\t\tthrow new TypeError(\n\t\t\t`defineWorkerContract(): cachePrefix must not end with '-', which the contract adds; got '${cachePrefix}'. Use '${cachePrefix.replace(/-+$/, '')}'.`,\n\t\t);\n\t}\n\tconst cacheVersion = options.cacheVersion ?? 'v1';\n\tif (\n\t\ttypeof cacheVersion !== 'string' ||\n\t\tcacheVersion === '' ||\n\t\tcacheVersion.includes('-')\n\t) {\n\t\tthrow new TypeError(\n\t\t\t`defineWorkerContract(): cacheVersion must be a non-empty name without '-', such as 'v2'; got '${String(cacheVersion)}'.`,\n\t\t);\n\t}\n\tconst cacheNames: CacheNames = Object.freeze({\n\t\tprecache: `${cachePrefix}-precache-${cacheVersion}`,\n\t\tassets: `${cachePrefix}-assets-${cacheVersion}`,\n\t\tstatic: `${cachePrefix}-static-${cacheVersion}`,\n\t});\n\tconst current = new Set<string>(Object.values(cacheNames));\n\t// Exactly `<prefix>-<kind>-<version>`: the prefix 'app' must not claim\n\t// 'app-admin-assets-v1' (one segment too many), but still owns a retired\n\t// kind such as 'app-pages-v0'.\n\tconst shape = new RegExp(\n\t\t`^${cachePrefix.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}-[a-z0-9]+-[^-]+$`,\n\t);\n\tconst owned = (name: string) => shape.test(name);\n\tconst contract: WorkerContract = {\n\t\tcachePrefix,\n\t\tcacheVersion,\n\t\tserviceWorkerUrl: options.serviceWorkerUrl ?? SERVICE_WORKER_URL,\n\t\tofflineUrl: options.offlineUrl ?? OFFLINE_URL,\n\t\tcacheNames,\n\t\tobsoleteCaches: (names) =>\n\t\t\tnames.filter((name) => owned(name) && !current.has(name)),\n\t\tclearableCaches: (names) =>\n\t\t\tnames.filter((name) => owned(name) && name !== cacheNames.precache),\n\t};\n\treturn Object.freeze(contract);\n}\n",
6
- "import { SERVICE_WORKER_URL, SKIP_WAITING } from './worker-contract';\n\n/** What the registration reads of `navigator.serviceWorker`. */\nexport type ServiceWorkerContainerLike = Pick<\n\tServiceWorkerContainer,\n\t'controller' | 'addEventListener' | 'register'\n>;\n\n/**\n * Calls `onWaiting` with each new version that finished installing while an\n * older one controls the page: the one waiting now, and each found later.\n * A first install controls nothing yet, so it offers nothing.\n */\nexport function watchForUpdates(\n\tregistration: ServiceWorkerRegistration,\n\tcontainer: Pick<ServiceWorkerContainerLike, 'controller'>,\n\tonWaiting: (worker: ServiceWorker) => void,\n): void {\n\tconst offer = (worker: ServiceWorker | null) => {\n\t\tif (worker && container.controller) onWaiting(worker);\n\t};\n\toffer(registration.waiting);\n\tregistration.addEventListener('updatefound', () => {\n\t\tconst installing = registration.installing;\n\t\tinstalling?.addEventListener('statechange', () => {\n\t\t\tif (installing.state === 'installed') offer(installing);\n\t\t});\n\t});\n}\n\n/** Moves this page onto a waiting worker: `apply(worker)`. */\nexport type ApplyUpdate = (\n\tworker: Pick<ServiceWorker, 'postMessage' | 'state'>,\n) => void;\n\n/**\n * The one update path of a page, created once: `apply(worker)` activates\n * the waiting worker and reloads once, when it takes control, so the page\n * runs the version its assets came from.\n *\n * - Several tabs: another tab may have activated the worker already. Its\n * `controllerchange` reached this page before anyone asked, so this page\n * did not reload (a form may be half filled); its \"Reload\" then reloads\n * at once, the worker being active.\n * - Clicked twice, or a `controllerchange` after a direct reload: one\n * reload, never two.\n */\nexport function createUpdater(\n\tcontainer: Pick<\n\t\tServiceWorkerContainerLike,\n\t\t'controller' | 'addEventListener'\n\t>,\n\treload: () => void = () => window.location.reload(),\n): ApplyUpdate {\n\tlet requested = false;\n\tlet reloaded = false;\n\tconst reloadOnce = () => {\n\t\tif (reloaded) return;\n\t\treloaded = true;\n\t\treload();\n\t};\n\tcontainer.addEventListener('controllerchange', () => {\n\t\tif (requested) reloadOnce();\n\t});\n\treturn (worker) => {\n\t\tconst active =\n\t\t\tworker.state === 'activating' ||\n\t\t\tworker.state === 'activated' ||\n\t\t\tcontainer.controller === worker;\n\t\tif (active) return reloadOnce();\n\t\trequested = true;\n\t\tworker.postMessage({ type: SKIP_WAITING });\n\t};\n}\n\n/** Runs `run` once the page has loaded: registering never competes with it. */\nexport function afterLoad(run: () => void): void {\n\tif (document.readyState === 'complete') run();\n\telse window.addEventListener('load', run, { once: true });\n}\n\nexport interface RegisterOptions {\n\t/** The worker's path. `SERVICE_WORKER_URL` by default. */\n\turl?: string;\n\t/** The scope it controls. `'/'` by default. */\n\tscope?: string;\n}\n\nlet updater: ApplyUpdate | undefined;\n\n/**\n * Registers the worker and watches for updates, handing each waiting worker\n * to `onWaiting` with the page's one `apply` (`createUpdater`, made on the\n * first call: one per page). A tab coming back to the foreground checks for\n * an update, as a long-lived page otherwise would only on its next full load.\n */\nexport async function registerServiceWorker(\n\tcontainer: ServiceWorkerContainerLike,\n\tonWaiting: (worker: ServiceWorker, apply: ApplyUpdate) => void,\n\toptions: RegisterOptions = {},\n): Promise<ServiceWorkerRegistration> {\n\tupdater ??= createUpdater(container);\n\tconst apply = updater;\n\tconst registration = await container.register(\n\t\toptions.url ?? SERVICE_WORKER_URL,\n\t\t{ scope: options.scope ?? '/' },\n\t);\n\twatchForUpdates(registration, container, (worker) =>\n\t\tonWaiting(worker, apply),\n\t);\n\tdocument.addEventListener('visibilitychange', () => {\n\t\tif (document.visibilityState === 'visible')\n\t\t\tregistration.update().catch(() => {\n\t\t\t\t// Offline, or the server is down: the next check will tell.\n\t\t\t});\n\t});\n\treturn registration;\n}\n",
7
- "/**\n * The session's \"Later\": the notice stays hidden until the next visit.\n * `sessionStorage` is per origin, so one key serves every app; a second\n * argument of `createUpdateNotice` names another.\n */\nexport const UPDATE_LATER_KEY = 'alxia.update-later';\n\n/** The part of `Storage` the notice reads and writes. */\nexport type NoticeStorage = {\n\tgetItem(key: string): string | null;\n\tsetItem(key: string, value: string): void;\n};\n\n/** What the notice offers while a new version waits. */\nexport type UpdateNotice = {\n\t/** Moves the page onto the new version: the worker's `apply`. */\n\treload: () => void;\n\t/** Hides the notice until the next visit (the tab's session). */\n\tlater: () => void;\n};\n\n/** What `createUpdateNotice` returns: the shape `useSyncExternalStore` takes. */\nexport interface UpdateNoticeStore {\n\t/** A new version waits: `apply` moves the page onto it. */\n\toffer(apply: () => void): void;\n\t/** The notice to show, if any (the same object until it changes). */\n\tcurrent(): UpdateNotice | undefined;\n\t/** Calls `listener` on each change; returns the function that stops it. */\n\tsubscribe(listener: () => void): () => void;\n}\n\n/**\n * The update notice's state: offered by the worker's registration, read by\n * the banners, hidden for the session by \"Later\". `storage` is the session's\n * (`sessionStorage`), which may be missing or throw (private mode, blocked\n * storage): the notice then hides for this page only. It is a function so\n * that nothing reads the global at load.\n */\nexport function createUpdateNotice(\n\tstorage: () => NoticeStorage | undefined,\n\tkey: string = UPDATE_LATER_KEY,\n): UpdateNoticeStore {\n\tlet reload: (() => void) | undefined;\n\tlet laterThisPage = false;\n\tlet notice: UpdateNotice | undefined;\n\tconst listeners = new Set<() => void>();\n\n\tconst later = () => {\n\t\tlaterThisPage = true;\n\t\ttry {\n\t\t\tstorage()?.setItem(key, '1');\n\t\t} catch {\n\t\t\t// No storage: hidden for this page only.\n\t\t}\n\t\trefresh();\n\t};\n\tconst laterAsked = () => {\n\t\tif (laterThisPage) return true;\n\t\ttry {\n\t\t\treturn storage()?.getItem(key) === '1';\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n\tfunction refresh() {\n\t\tnotice = reload && !laterAsked() ? { reload, later } : undefined;\n\t\tfor (const listener of listeners) listener();\n\t}\n\n\treturn {\n\t\toffer(apply) {\n\t\t\treload = apply;\n\t\t\trefresh();\n\t\t},\n\t\tcurrent: () => notice,\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t};\n}\n\n/** `sessionStorage`, or nothing where there is none (the server). */\nexport const sessionStore = (): NoticeStorage | undefined =>\n\ttypeof sessionStorage === 'undefined' ? undefined : sessionStorage;\n"
8
- ],
9
- "mappings": ";AAQO,IAAM,sBAAqB;AAG3B,IAAM,eAAc;AAGpB,IAAM,gBAAe;AAoDrB,SAAS,qBAAoB,CACnC,SACiB;AAAA,EACjB,QAAQ,gBAAgB;AAAA,EACxB,IAAI,OAAO,gBAAgB,YAAY,gBAAgB,IAAI;AAAA,IAC1D,MAAM,IAAI,UACT,wFAAwF,OAAO,WAAW,KAC3G;AAAA,EACD;AAAA,EACA,IAAI,YAAY,SAAS,GAAG,GAAG;AAAA,IAC9B,MAAM,IAAI,UACT,4FAA4F,sBAAsB,YAAY,QAAQ,OAAO,EAAE,KAChJ;AAAA,EACD;AAAA,EACA,MAAM,eAAe,QAAQ,gBAAgB;AAAA,EAC7C,IACC,OAAO,iBAAiB,YACxB,iBAAiB,MACjB,aAAa,SAAS,GAAG,GACxB;AAAA,IACD,MAAM,IAAI,UACT,iGAAiG,OAAO,YAAY,KACrH;AAAA,EACD;AAAA,EACA,MAAM,aAAyB,OAAO,OAAO;AAAA,IAC5C,UAAU,GAAG,wBAAwB;AAAA,IACrC,QAAQ,GAAG,sBAAsB;AAAA,IACjC,QAAQ,GAAG,sBAAsB;AAAA,EAClC,CAAC;AAAA,EACD,MAAM,UAAU,IAAI,IAAY,OAAO,OAAO,UAAU,CAAC;AAAA,EAIzD,MAAM,QAAQ,IAAI,OACjB,IAAI,YAAY,QAAQ,uBAAuB,MAAM,oBACtD;AAAA,EACA,MAAM,QAAQ,CAAC,SAAiB,MAAM,KAAK,IAAI;AAAA,EAC/C,MAAM,WAA2B;AAAA,IAChC;AAAA,IACA;AAAA,IACA,kBAAkB,QAAQ,oBAAoB;AAAA,IAC9C,YAAY,QAAQ,cAAc;AAAA,IAClC;AAAA,IACA,gBAAgB,CAAC,UAChB,MAAM,OAAO,CAAC,SAAS,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC;AAAA,IACzD,iBAAiB,CAAC,UACjB,MAAM,OAAO,CAAC,SAAS,MAAM,IAAI,KAAK,SAAS,WAAW,QAAQ;AAAA,EACpE;AAAA,EACA,OAAO,OAAO,OAAO,QAAQ;AAAA;;;ACrGvB,SAAS,gBAAe,CAC9B,cACA,WACA,WACO;AAAA,EACP,MAAM,QAAQ,CAAC,WAAiC;AAAA,IAC/C,IAAI,UAAU,UAAU;AAAA,MAAY,UAAU,MAAM;AAAA;AAAA,EAErD,MAAM,aAAa,OAAO;AAAA,EAC1B,aAAa,iBAAiB,eAAe,MAAM;AAAA,IAClD,MAAM,aAAa,aAAa;AAAA,IAChC,YAAY,iBAAiB,eAAe,MAAM;AAAA,MACjD,IAAI,WAAW,UAAU;AAAA,QAAa,MAAM,UAAU;AAAA,KACtD;AAAA,GACD;AAAA;AAoBK,SAAS,cAAa,CAC5B,WAIA,SAAqB,MAAM,OAAO,SAAS,OAAO,GACpC;AAAA,EACd,IAAI,YAAY;AAAA,EAChB,IAAI,WAAW;AAAA,EACf,MAAM,aAAa,MAAM;AAAA,IACxB,IAAI;AAAA,MAAU;AAAA,IACd,WAAW;AAAA,IACX,OAAO;AAAA;AAAA,EAER,UAAU,iBAAiB,oBAAoB,MAAM;AAAA,IACpD,IAAI;AAAA,MAAW,WAAW;AAAA,GAC1B;AAAA,EACD,OAAO,CAAC,WAAW;AAAA,IAClB,MAAM,SACL,OAAO,UAAU,gBACjB,OAAO,UAAU,eACjB,UAAU,eAAe;AAAA,IAC1B,IAAI;AAAA,MAAQ,OAAO,WAAW;AAAA,IAC9B,YAAY;AAAA,IACZ,OAAO,YAAY,EAAE,MAAM,cAAa,CAAC;AAAA;AAAA;AAKpC,SAAS,UAAS,CAAC,KAAuB;AAAA,EAChD,IAAI,SAAS,eAAe;AAAA,IAAY,IAAI;AAAA,EACvC;AAAA,WAAO,iBAAiB,QAAQ,KAAK,EAAE,MAAM,KAAK,CAAC;AAAA;AAUzD,IAAI;AAQJ,eAAsB,sBAAqB,CAC1C,WACA,WACA,UAA2B,CAAC,GACS;AAAA,EACrC,YAAY,eAAc,SAAS;AAAA,EACnC,MAAM,QAAQ;AAAA,EACd,MAAM,eAAe,MAAM,UAAU,SACpC,QAAQ,OAAO,qBACf,EAAE,OAAO,QAAQ,SAAS,IAAI,CAC/B;AAAA,EACA,iBAAgB,cAAc,WAAW,CAAC,WACzC,UAAU,QAAQ,KAAK,CACxB;AAAA,EACA,SAAS,iBAAiB,oBAAoB,MAAM;AAAA,IACnD,IAAI,SAAS,oBAAoB;AAAA,MAChC,aAAa,OAAO,EAAE,MAAM,MAAM,EAEjC;AAAA,GACF;AAAA,EACD,OAAO;AAAA;;;AC/GD,IAAM,oBAAmB;AAiCzB,SAAS,mBAAkB,CACjC,SACA,MAAc,mBACM;AAAA,EACpB,IAAI;AAAA,EACJ,IAAI,gBAAgB;AAAA,EACpB,IAAI;AAAA,EACJ,MAAM,YAAY,IAAI;AAAA,EAEtB,MAAM,QAAQ,MAAM;AAAA,IACnB,gBAAgB;AAAA,IAChB,IAAI;AAAA,MACH,QAAQ,GAAG,QAAQ,KAAK,GAAG;AAAA,MAC1B,MAAM;AAAA,IAGR,QAAQ;AAAA;AAAA,EAET,MAAM,aAAa,MAAM;AAAA,IACxB,IAAI;AAAA,MAAe,OAAO;AAAA,IAC1B,IAAI;AAAA,MACH,OAAO,QAAQ,GAAG,QAAQ,GAAG,MAAM;AAAA,MAClC,MAAM;AAAA,MACP,OAAO;AAAA;AAAA;AAAA,EAGT,SAAS,OAAO,GAAG;AAAA,IAClB,SAAS,UAAU,CAAC,WAAW,IAAI,EAAE,QAAQ,MAAM,IAAI;AAAA,IACvD,WAAW,YAAY;AAAA,MAAW,SAAS;AAAA;AAAA,EAG5C,OAAO;AAAA,IACN,KAAK,CAAC,OAAO;AAAA,MACZ,SAAS;AAAA,MACT,QAAQ;AAAA;AAAA,IAET,SAAS,MAAM;AAAA,IACf,SAAS,CAAC,UAAU;AAAA,MACnB,UAAU,IAAI,QAAQ;AAAA,MACtB,OAAO,MAAM;AAAA,QACZ,UAAU,OAAO,QAAQ;AAAA;AAAA;AAAA,EAG5B;AAAA;AAIM,IAAM,gBAAe,MAC3B,OAAO,mBAAmB,cAAc,YAAY;",
10
- "debugId": "E59639D8397E531464756E2164756E21",
11
- "names": []
12
- }