@alxia/react-router 0.8.1 → 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 +222 -10
- 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-9xv2a8et.js +108 -0
- 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.d.ts +14 -0
- package/dist/pwa/index.d.ts.map +1 -0
- package/dist/pwa/index.js +65 -0
- package/dist/pwa/index.js.map +10 -0
- package/dist/pwa/model/caching.d.ts +46 -0
- package/dist/pwa/model/caching.d.ts.map +1 -0
- package/dist/pwa/model/clear-app-caches.d.ts +9 -0
- package/dist/pwa/model/clear-app-caches.d.ts.map +1 -0
- package/dist/pwa/model/manifest.d.ts +80 -0
- package/dist/pwa/model/manifest.d.ts.map +1 -0
- package/dist/pwa/model/precache-list.d.ts +40 -0
- package/dist/pwa/model/precache-list.d.ts.map +1 -0
- package/dist/pwa/model/registration.d.ts +39 -0
- package/dist/pwa/model/registration.d.ts.map +1 -0
- package/dist/pwa/model/start-service-worker.d.ts +24 -0
- package/dist/pwa/model/start-service-worker.d.ts.map +1 -0
- package/dist/pwa/model/storable.d.ts +8 -0
- package/dist/pwa/model/storable.d.ts.map +1 -0
- package/dist/pwa/model/update-notice.d.ts +38 -0
- package/dist/pwa/model/update-notice.d.ts.map +1 -0
- package/dist/pwa/model/worker-contract.d.ts +61 -0
- package/dist/pwa/model/worker-contract.d.ts.map +1 -0
- package/dist/pwa/react/index.d.ts +9 -0
- package/dist/pwa/react/index.d.ts.map +1 -0
- package/dist/pwa/react/index.js +75 -0
- package/dist/pwa/react/index.js.map +13 -0
- package/dist/pwa/react/use-online.d.ts +6 -0
- package/dist/pwa/react/use-online.d.ts.map +1 -0
- package/dist/pwa/react/use-service-worker.d.ts +33 -0
- package/dist/pwa/react/use-service-worker.d.ts.map +1 -0
- package/dist/pwa/react/use-update-notice.d.ts +14 -0
- package/dist/pwa/react/use-update-notice.d.ts.map +1 -0
- package/dist/pwa/server/index.d.ts +7 -0
- package/dist/pwa/server/index.d.ts.map +1 -0
- package/dist/pwa/server/index.js +18 -0
- package/dist/pwa/server/index.js.map +10 -0
- package/dist/pwa/server/no-cache.d.ts +22 -0
- package/dist/pwa/server/no-cache.d.ts.map +1 -0
- 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 +12 -3
- package/docs/guide/beside-the-pages.md +178 -0
- package/docs/guide/csp-nonce.md +100 -0
- package/docs/guide/customising-the-server.md +292 -0
- package/docs/guide/deploying.md +99 -0
- package/docs/guide/getting-started.md +112 -0
- package/docs/guide/how-it-works.md +268 -0
- package/docs/guide/openapi.md +51 -0
- package/docs/guide/pwa.md +636 -0
- package/docs/guide/testing.md +42 -0
- package/docs/guide/typing-the-loaders.md +311 -0
- package/docs/guide.md +27 -1391
- package/docs/roadmap.md +39 -1
- package/docs/troubleshooting.md +402 -11
- package/package.json +70 -6
package/docs/roadmap.md
CHANGED
|
@@ -11,10 +11,15 @@ Nothing in progress.
|
|
|
11
11
|
|
|
12
12
|
## Next
|
|
13
13
|
|
|
14
|
-
Nothing
|
|
14
|
+
Nothing planned.
|
|
15
15
|
|
|
16
16
|
## Later
|
|
17
17
|
|
|
18
|
+
- **`service-worker.js` served revalidated with no setup.** Today the app
|
|
19
|
+
adds `noCache` for `SERVICE_WORKER_URL` itself.
|
|
20
|
+
- **A check of an offline page's policy.** The offline page declares its
|
|
21
|
+
Content-Security-Policy in a `<meta>`; a check would read the page and
|
|
22
|
+
confirm that each inline script and style is allowed by its hash.
|
|
18
23
|
- **`context.alxia` typed under `Route.LoaderArgs`.** Once react-router's
|
|
19
24
|
`./internal` types and its main entry share one `RouterContextProvider`
|
|
20
25
|
declaration, the generated route types see the shorthand too; today
|
|
@@ -48,6 +53,39 @@ Nothing scheduled yet.
|
|
|
48
53
|
|
|
49
54
|
## Shipped
|
|
50
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
|
+
|
|
72
|
+
### 0.9.0
|
|
73
|
+
|
|
74
|
+
- **`@alxia/react-router/pwa`.** The code with no UI an installable app
|
|
75
|
+
shares, importing nothing: the service worker's contract and cache names
|
|
76
|
+
(`defineWorkerContract`), which requests the worker caches and which
|
|
77
|
+
responses it may store (`cachingFor`, `isStorable`), the precache list read
|
|
78
|
+
from the build, registering the worker and the update notice's store, the
|
|
79
|
+
web app manifest with its language in `?lang=`, and `clearAppCaches` for
|
|
80
|
+
sign-out. Every app-specific name is an option, so two apps never share a
|
|
81
|
+
default ([guide](guide/pwa.md)).
|
|
82
|
+
- **`@alxia/react-router/pwa/react`.** `useOnline`, `useServiceWorker({
|
|
83
|
+
enabled })` and `useUpdateNotice`, with the page's one `updateNotice`;
|
|
84
|
+
`react` is an optional peer.
|
|
85
|
+
- **`@alxia/react-router/pwa/server`.** `noCache` serves the worker's
|
|
86
|
+
script revalidated on every update check, with the worker's own policy
|
|
87
|
+
(`revalidated` and `WORKER_POLICY` beneath it).
|
|
88
|
+
|
|
51
89
|
### 0.8.0
|
|
52
90
|
|
|
53
91
|
- **`withAlxia(fn)`.** A loader, an action or a middleware given `alxia`,
|
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)
|
|
@@ -38,6 +40,19 @@ a loader, a message React Router or the browser prints, or an error from
|
|
|
38
40
|
- [`error: lockfile had changes, but lockfile is frozen`](#error-lockfile-had-changes-but-lockfile-is-frozen)
|
|
39
41
|
- [`EACCES: permission denied, open '/app/…'`](#eacces-permission-denied-open-app)
|
|
40
42
|
- [`error: Cannot find package '…' from '/app/build/server/index.js'`](#error-cannot-find-package--from-appbuildserverindexjs)
|
|
43
|
+
- [`TypeError: defineWorkerContract(): cachePrefix must be a non-empty name, …`](#typeerror-defineworkercontract-cacheprefix-must-be-a-non-empty-name-)
|
|
44
|
+
- [`TypeError: defineWorkerContract(): cacheVersion must be a non-empty name without '-', …`](#typeerror-defineworkercontract-cacheversion-must-be-a-non-empty-name-without---)
|
|
45
|
+
- [`parseBrowserManifest(): not React Router's browser manifest …`](#parsebrowsermanifest-not-react-routers-browser-manifest-)
|
|
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)
|
|
41
56
|
|
|
42
57
|
**Types**
|
|
43
58
|
|
|
@@ -63,14 +78,23 @@ a loader, a message React Router or the browser prints, or an error from
|
|
|
63
78
|
- [A `publish` under `react-router dev` misses the sockets opened before an edit](#a-publish-under-react-router-dev-misses-the-sockets-opened-before-an-edit)
|
|
64
79
|
- [The build has a package's Node variant, not its `bun` one](#the-build-has-a-packages-node-variant-not-its-bun-one)
|
|
65
80
|
- [`request.url` in a loader is `http://` and the internal host behind a proxy](#requesturl-in-a-loader-is-http-and-the-internal-host-behind-a-proxy)
|
|
81
|
+
- [An unhashed file under `public/assets/` is cached for a year](#an-unhashed-file-under-publicassets-is-cached-for-a-year)
|
|
82
|
+
- [A cache the contract did not make is swept](#a-cache-the-contract-did-not-make-is-swept)
|
|
83
|
+
- [The update notice never shows](#the-update-notice-never-shows)
|
|
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)
|
|
87
|
+
- [An update waits an hour](#an-update-waits-an-hour)
|
|
88
|
+
- [The worker cannot fetch another origin](#the-worker-cannot-fetch-another-origin)
|
|
89
|
+
- [The manifest is in the wrong language](#the-manifest-is-in-the-wrong-language)
|
|
66
90
|
|
|
67
91
|
## Thrown or printed
|
|
68
92
|
|
|
69
93
|
### `alxia-react-router: … is running on Node, and alxia's server runs on Bun. …`
|
|
70
94
|
|
|
71
95
|
**When:** `bun run dev`, `vite preview`, or a `react-router build` that
|
|
72
|
-
prerenders
|
|
73
|
-
`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`:
|
|
74
98
|
|
|
75
99
|
```
|
|
76
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.
|
|
@@ -92,7 +116,18 @@ bun = true
|
|
|
92
116
|
Or run the one command on Bun: `bun --bun react-router dev`. Under
|
|
93
117
|
`vite preview` or a prerendering build, the message begins
|
|
94
118
|
`alxia-react-router: vite preview, or a react-router build that prerenders, is running on Node`
|
|
95
|
-
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`.
|
|
96
131
|
|
|
97
132
|
### `ReferenceError: Bun is not defined`
|
|
98
133
|
|
|
@@ -133,7 +168,7 @@ key: one copy is the fix for both.
|
|
|
133
168
|
sets.
|
|
134
169
|
|
|
135
170
|
**Fix:** add `alxia()` to `vite.config.ts`, as in the
|
|
136
|
-
[setup](guide.md
|
|
171
|
+
[setup](guide/getting-started.md). With a server of your own in a monorepo, tell
|
|
137
172
|
Vite to leave the package external:
|
|
138
173
|
|
|
139
174
|
```ts
|
|
@@ -210,7 +245,7 @@ reactRouter(app, { build, getLoadContext: ({ user }, context) => context.set(use
|
|
|
210
245
|
**Fix:** serve the app with the plugin, `alxia()` in `vite.config.ts`: the
|
|
211
246
|
server is then built with the routes, and `app/context.ts` is one module
|
|
212
247
|
for both, in dev and in the build. See
|
|
213
|
-
[the app's own context keys](guide.md#the-apps-own-context-keys).
|
|
248
|
+
[the app's own context keys](guide/typing-the-loaders.md#the-apps-own-context-keys).
|
|
214
249
|
|
|
215
250
|
Without the plugin, read alxia's context with `alxiaOf`: its key,
|
|
216
251
|
`alxiaContext`, lives in this package under `node_modules`, which Vite
|
|
@@ -245,7 +280,7 @@ export default server;
|
|
|
245
280
|
```
|
|
246
281
|
|
|
247
282
|
An alxia app of your own, with `reactRouter()` on it, is served without the
|
|
248
|
-
plugin: see [a server of your own](guide.md#a-server-of-your-own-without-the-plugin).
|
|
283
|
+
plugin: see [a server of your own](guide/customising-the-server.md#a-server-of-your-own-without-the-plugin).
|
|
249
284
|
|
|
250
285
|
### `alxia-react-router: the entry … does not exist. …`
|
|
251
286
|
|
|
@@ -262,6 +297,20 @@ Vite's root. Under `react-router dev` each request is a 500 saying so;
|
|
|
262
297
|
|
|
263
298
|
**Fix:** correct the path, or leave `entry` out.
|
|
264
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
|
+
|
|
265
314
|
### `alxia-react-router: React Router's Vite plugin is not in this config. …`
|
|
266
315
|
|
|
267
316
|
```text
|
|
@@ -465,7 +514,7 @@ export default createServer({
|
|
|
465
514
|
|
|
466
515
|
or `contentSecurityPolicy: false`. Better than `'unsafe-inline'`: a nonce
|
|
467
516
|
per request, with `nonce: true` and `nonceOf(loadContext)` in
|
|
468
|
-
`entry.server.tsx` ([guide](guide.md#a-csp-nonce)).
|
|
517
|
+
`entry.server.tsx` ([guide](guide/csp-nonce.md#a-csp-nonce)).
|
|
469
518
|
|
|
470
519
|
### `alxia-react-router: … already exists, and reveal leaves it as it is. Run alxia-react-router reveal --force to overwrite it.`
|
|
471
520
|
|
|
@@ -651,7 +700,7 @@ bun add -d typescript@^6
|
|
|
651
700
|
and uses it outside its `loader` and `action`, in the component or at the
|
|
652
701
|
module's top level. React Router keeps that code in the client build, for
|
|
653
702
|
the browser, which has no such module. The server build leaves Bun's
|
|
654
|
-
modules external ([Built for Bun](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#built-for-bun));
|
|
703
|
+
modules external ([Built for Bun](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/how-it-works.md#built-for-bun));
|
|
655
704
|
the client build cannot.
|
|
656
705
|
|
|
657
706
|
**Fix:** use them only in the server's code: in `loader` and `action`, or
|
|
@@ -735,7 +784,7 @@ The image runs `bun --no-install`: without the flag, Bun finds no
|
|
|
735
784
|
whatever version npm has, instead of failing.
|
|
736
785
|
|
|
737
786
|
**Why:** under `react-router build` the plugin bundles every package into
|
|
738
|
-
`build/server/index.js` ([Self-contained](guide.md#self-contained)), so
|
|
787
|
+
`build/server/index.js` ([Self-contained](guide/how-it-works.md#self-contained)), so
|
|
739
788
|
the image needs no `node_modules`. What stays outside the file must be
|
|
740
789
|
installed beside it.
|
|
741
790
|
|
|
@@ -770,6 +819,219 @@ addon built for glibc alone then stops it with
|
|
|
770
819
|
put the final stage back on `oven/bun:1`
|
|
771
820
|
([`@alxia/create`'s troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/create/docs/troubleshooting.md#error--is-linked-against-glibc-dt_needed-libmso6-but-this-bun-build-uses-musl)).
|
|
772
821
|
|
|
822
|
+
### `TypeError: defineWorkerContract(): cachePrefix must be a non-empty name, …`
|
|
823
|
+
|
|
824
|
+
The full message is `defineWorkerContract(): cachePrefix must be a non-empty
|
|
825
|
+
name, such as 'my-app'; got ''.`, or `… must not end with '-', which the
|
|
826
|
+
contract adds; got 'my-app-'. Use 'my-app'.`
|
|
827
|
+
|
|
828
|
+
**Why:** every cache name is `<prefix>-<kind>-<version>`, and the worker
|
|
829
|
+
deletes the caches shaped `<prefix>-<kind>-<version>`. An empty prefix would own
|
|
830
|
+
every cache of the origin, and a trailing dash would be doubled.
|
|
831
|
+
|
|
832
|
+
**Fix:** pass the app's own name, without the dash:
|
|
833
|
+
|
|
834
|
+
```ts
|
|
835
|
+
import { defineWorkerContract } from '@alxia/react-router/pwa';
|
|
836
|
+
|
|
837
|
+
export const workerContract = defineWorkerContract({ cachePrefix: 'my-app' });
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
### `TypeError: defineWorkerContract(): cacheVersion must be a non-empty name without '-', …`
|
|
841
|
+
|
|
842
|
+
The full message is `defineWorkerContract(): cacheVersion must be a
|
|
843
|
+
non-empty name without '-', such as 'v2'; got 'v1-beta'.`
|
|
844
|
+
|
|
845
|
+
**Why:** every cache name is `<prefix>-<kind>-<version>`, and the contract
|
|
846
|
+
tells a version from the rest of the name by its dashes. A version with a `-`
|
|
847
|
+
(or an empty one) would make the name ambiguous.
|
|
848
|
+
|
|
849
|
+
**Fix:** use a name with no dash, such as `'v2'` or `'2026'`:
|
|
850
|
+
|
|
851
|
+
```ts
|
|
852
|
+
import { defineWorkerContract } from '@alxia/react-router/pwa';
|
|
853
|
+
|
|
854
|
+
export const workerContract = defineWorkerContract({
|
|
855
|
+
cachePrefix: 'my-app',
|
|
856
|
+
cacheVersion: 'v2',
|
|
857
|
+
});
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
### `parseBrowserManifest(): not React Router's browser manifest …`
|
|
861
|
+
|
|
862
|
+
The full message ends with `(build/client/assets/manifest-*.js).`
|
|
863
|
+
|
|
864
|
+
**Why:** the source holds no `{ … }` object. The build script read another
|
|
865
|
+
file, an empty one, or the build has not run.
|
|
866
|
+
|
|
867
|
+
**Fix:** run `react-router build` first, then read the file named
|
|
868
|
+
`manifest-<hash>.js` in `build/client/assets`, not the server's manifest:
|
|
869
|
+
|
|
870
|
+
```ts
|
|
871
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
872
|
+
import { parseBrowserManifest } from '@alxia/react-router/pwa';
|
|
873
|
+
|
|
874
|
+
const assets = 'build/client/assets';
|
|
875
|
+
const file = readdirSync(assets).find((name) => /^manifest-.*\.js$/.test(name));
|
|
876
|
+
if (!file) throw new Error('Run react-router build first.');
|
|
877
|
+
export const manifest = parseBrowserManifest(readFileSync(`${assets}/${file}`, 'utf8'));
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
### `error: Cannot find package 'react' from '…/@alxia/react-router/dist/pwa/react/index.js'`
|
|
881
|
+
|
|
882
|
+
**Why:** `@alxia/react-router/pwa/react` imports `react`, an optional peer of
|
|
883
|
+
the package, so nothing installs it for you. An app with no `react` does not
|
|
884
|
+
need the subpath; a build script or the worker importing it by mistake meets
|
|
885
|
+
this message too.
|
|
886
|
+
|
|
887
|
+
**Fix:** add `react` where the page is built (`bun add react`), and import
|
|
888
|
+
`/pwa/react` from the page alone. The worker and a build script import
|
|
889
|
+
`@alxia/react-router/pwa`, which imports nothing.
|
|
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
|
+
|
|
773
1035
|
## Types
|
|
774
1036
|
|
|
775
1037
|
### `Property 'alxia' does not exist on type 'Readonly<RouterContextProvider>'`
|
|
@@ -989,7 +1251,7 @@ following Content Security Policy directive: "script-src 'self'
|
|
|
989
1251
|
attribute.
|
|
990
1252
|
|
|
991
1253
|
**Fix:** reveal the entry and pass `nonceOf(loadContext)` to both
|
|
992
|
-
([guide](guide.md#a-csp-nonce)). A script of your own reads the nonce
|
|
1254
|
+
([guide](guide/csp-nonce.md#a-csp-nonce)). A script of your own reads the nonce
|
|
993
1255
|
through a loader, where `nonceOf(context)` works as well:
|
|
994
1256
|
|
|
995
1257
|
```tsx
|
|
@@ -1119,7 +1381,7 @@ plugin relays the app's sockets to. So `ctx.server` is that side server: its
|
|
|
1119
1381
|
and its `requestIP` and `timeout` know nothing of a request Vite took, so
|
|
1120
1382
|
the default `ctx.ip` is `undefined`. A `page()` (Bun's HTML bundle) is not
|
|
1121
1383
|
served: Vite answers the HTML
|
|
1122
|
-
([The server](guide.md#the-server-server)).
|
|
1384
|
+
([The server](guide/typing-the-loaders.md#the-server-server)).
|
|
1123
1385
|
|
|
1124
1386
|
**Fix:** read `ctx.server` as optional, which also covers `app.request` in
|
|
1125
1387
|
a test, and take nothing from its `url` or `requestIP` in dev:
|
|
@@ -1188,3 +1450,132 @@ export default createServer({ proxy: trustProxy({ trusted: ['10.0.0.0/8'] }) });
|
|
|
1188
1450
|
|
|
1189
1451
|
React Router then gets its request at `originalUrl(ctx)`
|
|
1190
1452
|
([Behind a proxy](https://github.com/softistx/alxia/blob/develop/packages/core/docs/guide/serving.md#behind-a-proxy-proxy)).
|
|
1453
|
+
|
|
1454
|
+
### An unhashed file under `public/assets/` is cached for a year
|
|
1455
|
+
|
|
1456
|
+
A file you put in `public/assets/` (a sprite, an icon) changes on a deploy,
|
|
1457
|
+
and visitors keep the old one.
|
|
1458
|
+
|
|
1459
|
+
**Why:** Vite copies `public/` into `build/client/`, and the client's files
|
|
1460
|
+
under `build/client/assets/` are served `public, max-age=31536000,
|
|
1461
|
+
immutable`, hashed or not. The worker's `cachingFor` agrees by default:
|
|
1462
|
+
everything under `/assets/` is `immutable`, cache-first.
|
|
1463
|
+
|
|
1464
|
+
**Fix:** keep unhashed files out of `assets/`: `public/icons/` is served for
|
|
1465
|
+
an hour and is `static` for the worker. If they must stay there, give
|
|
1466
|
+
`cachingFor` an `immutable` option that leaves their folder out, so the worker
|
|
1467
|
+
revalidates them ([the option](guide/pwa.md#what-the-worker-caches)),
|
|
1468
|
+
and expect the HTTP cache to hold them for a year until the file's name
|
|
1469
|
+
changes.
|
|
1470
|
+
|
|
1471
|
+
### A cache the contract did not make is swept
|
|
1472
|
+
|
|
1473
|
+
Signing out, or a new worker activating, deletes a cache the app made itself.
|
|
1474
|
+
|
|
1475
|
+
**Why:** a contract owns every cache named exactly
|
|
1476
|
+
`<prefix>-<kind>-<version>`, kind being lowercase letters and digits and
|
|
1477
|
+
version having no `-`. That includes a kind the app retired (`my-app-pages-v0` is swept once
|
|
1478
|
+
it is not current). It is also why `app` and `app-admin` can share an origin:
|
|
1479
|
+
`app-admin-assets-v1` has one segment too many to be `app`'s. Contracts never
|
|
1480
|
+
take each other's caches; only a cache made by other code, such as
|
|
1481
|
+
`my-app-runtime-v1`, matches the shape and is swept.
|
|
1482
|
+
|
|
1483
|
+
**Fix:** name your own caches outside the shape. A name is outside it when it
|
|
1484
|
+
has more than `<kind>-<version>` after `<prefix>-`, with the prefix `my-app`:
|
|
1485
|
+
`my-app-runtime-v1-x`. A kind with an uppercase letter, `_` or `.`
|
|
1486
|
+
(`my-app-Runtime-v1`, `my-app-run_time-v1`), or a different prefix, is outside
|
|
1487
|
+
too.
|
|
1488
|
+
|
|
1489
|
+
### The update notice never shows
|
|
1490
|
+
|
|
1491
|
+
A new version is deployed and the page offers no "Reload".
|
|
1492
|
+
|
|
1493
|
+
**Why:** the notice is offered only when a worker finished installing while
|
|
1494
|
+
an older one controls the page. A first install controls nothing, so it
|
|
1495
|
+
offers nothing; the next visit, with the first worker in control, does. And
|
|
1496
|
+
nothing is registered at all where the app does not call
|
|
1497
|
+
`registerServiceWorker` (for example while `useServiceWorker({ enabled })`
|
|
1498
|
+
is given `false`, as `import.meta.env.PROD` is under `react-router dev`). A
|
|
1499
|
+
tab that chose "Later" stays quiet until the next session.
|
|
1500
|
+
|
|
1501
|
+
**Fix:** build, serve and load the page twice, then deploy a change and load
|
|
1502
|
+
it again. In the console, `navigator.serviceWorker.controller` must not be
|
|
1503
|
+
`null` and `(await navigator.serviceWorker.getRegistration()).waiting` must
|
|
1504
|
+
hold the new worker.
|
|
1505
|
+
|
|
1506
|
+
### The worker registers under `react-router dev`
|
|
1507
|
+
|
|
1508
|
+
The dev server serves stale code after an edit, or a page keeps an old
|
|
1509
|
+
version of itself, and `navigator.serviceWorker.controller` is not `null`.
|
|
1510
|
+
|
|
1511
|
+
**Why:** `useServiceWorker` registers whenever `enabled` is `true`, and the
|
|
1512
|
+
package does not know the mode: it never reads `import.meta.env`. A worker
|
|
1513
|
+
under a dev server caches the code you are editing.
|
|
1514
|
+
|
|
1515
|
+
**Fix:** pass the mode from the app, `useServiceWorker({ enabled:
|
|
1516
|
+
import.meta.env.PROD })` ([the hook](guide/pwa.md#in-react)). Then unregister
|
|
1517
|
+
the worker the dev server already took (DevTools, Application, Service
|
|
1518
|
+
Workers, Unregister) and clear its site data.
|
|
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
|
+
|
|
1543
|
+
### An update waits an hour
|
|
1544
|
+
|
|
1545
|
+
A new worker is deployed and tabs still run the old one for up to an hour.
|
|
1546
|
+
|
|
1547
|
+
**Why:** the worker's script was served with the client's hour of
|
|
1548
|
+
`Cache-Control`, so the browser's update check found the cached script.
|
|
1549
|
+
Either `noCache` is not declared, or it is declared in `configure`, which
|
|
1550
|
+
runs after the client's files: they answer first and the middleware never
|
|
1551
|
+
sees the request.
|
|
1552
|
+
|
|
1553
|
+
**Fix:** declare it in `beforeAll`
|
|
1554
|
+
([serving the worker's script](guide/pwa.md#serving-the-workers-script)), and
|
|
1555
|
+
check with `curl -I https://your.app/service-worker.js` that `cache-control` is
|
|
1556
|
+
`no-cache`.
|
|
1557
|
+
|
|
1558
|
+
### The worker cannot fetch another origin
|
|
1559
|
+
|
|
1560
|
+
The console reports `Refused to connect to 'https://api.example.com/…' because it violates the following Content Security Policy directive: "connect-src 'self'"` from the worker.
|
|
1561
|
+
|
|
1562
|
+
**Why:** `noCache` gives the worker's script `WORKER_POLICY`,
|
|
1563
|
+
`default-src 'none'; connect-src 'self'`: the worker fetches this site only.
|
|
1564
|
+
|
|
1565
|
+
**Fix:** if the worker really must reach another origin, serve the script
|
|
1566
|
+
with a wider policy through `revalidated(response, policy)` in a middleware of
|
|
1567
|
+
your own ([serving the worker's script](guide/pwa.md#serving-the-workers-script)),
|
|
1568
|
+
rather than loosening the pages' policy.
|
|
1569
|
+
|
|
1570
|
+
### The manifest is in the wrong language
|
|
1571
|
+
|
|
1572
|
+
The installed app's name and description are in the default language,
|
|
1573
|
+
whatever the user chose.
|
|
1574
|
+
|
|
1575
|
+
**Why:** the browser fetches the manifest without cookies, so the `language`
|
|
1576
|
+
cookie never reaches the server. Only the link's query does.
|
|
1577
|
+
|
|
1578
|
+
**Fix:** link `manifestHref(language)` (`/manifest.webmanifest?lang=fr`), and
|
|
1579
|
+
read it with `manifestLanguage(url, supported, fallback)`; the fallback is a
|
|
1580
|
+
supported language the app resolves from the request's `Accept-Language`, not
|
|
1581
|
+
from the cookie ([the manifest route](guide/pwa.md#the-web-app-manifest-and-why-the-language-travels-in-lang)).
|