@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.
- package/README.md +126 -13
- package/dist/chunks/index-59vhfj6n.js +10 -0
- package/dist/chunks/index-59vhfj6n.js.map +10 -0
- package/dist/chunks/index-5gqcpyyg.js +34 -0
- package/dist/chunks/index-5gqcpyyg.js.map +10 -0
- package/dist/chunks/{index-zqa30gr7.js → index-9xv2a8et.js} +7 -38
- package/dist/chunks/index-9xv2a8et.js.map +11 -0
- package/dist/chunks/index-mppz0806.js +119 -0
- package/dist/chunks/index-mppz0806.js.map +11 -0
- package/dist/chunks/index-rfr02vg4.js +40 -0
- package/dist/chunks/index-rfr02vg4.js.map +10 -0
- package/dist/chunks/index-zdbqzcjp.js +97 -0
- package/dist/chunks/index-zdbqzcjp.js.map +12 -0
- package/dist/pwa/build/build-worker.d.ts +41 -0
- package/dist/pwa/build/build-worker.d.ts.map +1 -0
- package/dist/pwa/build/index.d.ts +9 -0
- package/dist/pwa/build/index.d.ts.map +1 -0
- package/dist/pwa/build/index.js +9 -0
- package/dist/pwa/build/index.js.map +9 -0
- package/dist/pwa/build/precache.d.ts +17 -0
- package/dist/pwa/build/precache.d.ts.map +1 -0
- package/dist/pwa/index.js +34 -134
- package/dist/pwa/index.js.map +4 -8
- package/dist/pwa/react/index.js +1 -1
- package/dist/pwa/worker/index.d.ts +9 -0
- package/dist/pwa/worker/index.d.ts.map +1 -0
- package/dist/pwa/worker/index.js +144 -0
- package/dist/pwa/worker/index.js.map +12 -0
- package/dist/pwa/worker/injected.d.ts +15 -0
- package/dist/pwa/worker/injected.d.ts.map +1 -0
- package/dist/pwa/worker/plan.d.ts +65 -0
- package/dist/pwa/worker/plan.d.ts.map +1 -0
- package/dist/pwa/worker/scope.d.ts +18 -0
- package/dist/pwa/worker/scope.d.ts.map +1 -0
- package/dist/pwa/worker/start-worker.d.ts +17 -0
- package/dist/pwa/worker/start-worker.d.ts.map +1 -0
- package/dist/vite/index.d.ts +10 -0
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +49 -3
- package/dist/vite/index.js.map +6 -5
- package/dist/vite/runtime.d.ts +1 -1
- package/dist/vite/runtime.d.ts.map +1 -1
- package/dist/vite/worker.d.ts +16 -0
- package/dist/vite/worker.d.ts.map +1 -0
- package/docs/README.md +1 -1
- package/docs/guide/how-it-works.md +7 -1
- package/docs/guide/pwa.md +372 -211
- package/docs/roadmap.md +19 -5
- package/docs/troubleshooting.md +208 -3
- package/package.json +46 -4
- 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
|
-
|
|
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
|
package/docs/troubleshooting.md
CHANGED
|
@@ -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
|
|
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.
|
|
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`)
|
|
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
|
-
}
|