@alxia/react-router 0.8.0 → 0.9.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 +107 -8
- package/dist/chunks/index-zqa30gr7.js +139 -0
- package/dist/chunks/index-zqa30gr7.js.map +12 -0
- package/dist/pwa/index.d.ts +14 -0
- package/dist/pwa/index.d.ts.map +1 -0
- package/dist/pwa/index.js +165 -0
- package/dist/pwa/index.js.map +14 -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/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 +262 -0
- package/docs/guide/openapi.md +51 -0
- package/docs/guide/pwa.md +475 -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 +25 -1
- package/docs/troubleshooting.md +195 -9
- package/package.json +32 -10
package/docs/roadmap.md
CHANGED
|
@@ -11,10 +11,17 @@ Nothing in progress.
|
|
|
11
11
|
|
|
12
12
|
## Next
|
|
13
13
|
|
|
14
|
-
|
|
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.
|
|
15
19
|
|
|
16
20
|
## Later
|
|
17
21
|
|
|
22
|
+
- **A check of an offline page's policy.** The offline page declares its
|
|
23
|
+
Content-Security-Policy in a `<meta>`; a check would read the page and
|
|
24
|
+
confirm that each inline script and style is allowed by its hash.
|
|
18
25
|
- **`context.alxia` typed under `Route.LoaderArgs`.** Once react-router's
|
|
19
26
|
`./internal` types and its main entry share one `RouterContextProvider`
|
|
20
27
|
declaration, the generated route types see the shorthand too; today
|
|
@@ -48,6 +55,23 @@ Nothing scheduled yet.
|
|
|
48
55
|
|
|
49
56
|
## Shipped
|
|
50
57
|
|
|
58
|
+
### 0.9.0
|
|
59
|
+
|
|
60
|
+
- **`@alxia/react-router/pwa`.** The code with no UI an installable app
|
|
61
|
+
shares, importing nothing: the service worker's contract and cache names
|
|
62
|
+
(`defineWorkerContract`), which requests the worker caches and which
|
|
63
|
+
responses it may store (`cachingFor`, `isStorable`), the precache list read
|
|
64
|
+
from the build, registering the worker and the update notice's store, the
|
|
65
|
+
web app manifest with its language in `?lang=`, and `clearAppCaches` for
|
|
66
|
+
sign-out. Every app-specific name is an option, so two apps never share a
|
|
67
|
+
default ([guide](guide/pwa.md)).
|
|
68
|
+
- **`@alxia/react-router/pwa/react`.** `useOnline`, `useServiceWorker({
|
|
69
|
+
enabled })` and `useUpdateNotice`, with the page's one `updateNotice`;
|
|
70
|
+
`react` is an optional peer.
|
|
71
|
+
- **`@alxia/react-router/pwa/server`.** `noCache` serves the worker's
|
|
72
|
+
script revalidated on every update check, with the worker's own policy
|
|
73
|
+
(`revalidated` and `WORKER_POLICY` beneath it).
|
|
74
|
+
|
|
51
75
|
### 0.8.0
|
|
52
76
|
|
|
53
77
|
- **`withAlxia(fn)`.** A loader, an action or a middleware given `alxia`,
|
package/docs/troubleshooting.md
CHANGED
|
@@ -38,6 +38,10 @@ a loader, a message React Router or the browser prints, or an error from
|
|
|
38
38
|
- [`error: lockfile had changes, but lockfile is frozen`](#error-lockfile-had-changes-but-lockfile-is-frozen)
|
|
39
39
|
- [`EACCES: permission denied, open '/app/…'`](#eacces-permission-denied-open-app)
|
|
40
40
|
- [`error: Cannot find package '…' from '/app/build/server/index.js'`](#error-cannot-find-package--from-appbuildserverindexjs)
|
|
41
|
+
- [`TypeError: defineWorkerContract(): cachePrefix must be a non-empty name, …`](#typeerror-defineworkercontract-cacheprefix-must-be-a-non-empty-name-)
|
|
42
|
+
- [`TypeError: defineWorkerContract(): cacheVersion must be a non-empty name without '-', …`](#typeerror-defineworkercontract-cacheversion-must-be-a-non-empty-name-without---)
|
|
43
|
+
- [`parseBrowserManifest(): not React Router's browser manifest …`](#parsebrowsermanifest-not-react-routers-browser-manifest-)
|
|
44
|
+
- [`error: Cannot find package 'react' from '…/@alxia/react-router/dist/pwa/react/index.js'`](#error-cannot-find-package-react-from-alxiareact-routerdistpwareactindexjs)
|
|
41
45
|
|
|
42
46
|
**Types**
|
|
43
47
|
|
|
@@ -63,6 +67,13 @@ a loader, a message React Router or the browser prints, or an error from
|
|
|
63
67
|
- [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
68
|
- [The build has a package's Node variant, not its `bun` one](#the-build-has-a-packages-node-variant-not-its-bun-one)
|
|
65
69
|
- [`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)
|
|
70
|
+
- [An unhashed file under `public/assets/` is cached for a year](#an-unhashed-file-under-publicassets-is-cached-for-a-year)
|
|
71
|
+
- [A cache the contract did not make is swept](#a-cache-the-contract-did-not-make-is-swept)
|
|
72
|
+
- [The update notice never shows](#the-update-notice-never-shows)
|
|
73
|
+
- [The worker registers under `react-router dev`](#the-worker-registers-under-react-router-dev)
|
|
74
|
+
- [An update waits an hour](#an-update-waits-an-hour)
|
|
75
|
+
- [The worker cannot fetch another origin](#the-worker-cannot-fetch-another-origin)
|
|
76
|
+
- [The manifest is in the wrong language](#the-manifest-is-in-the-wrong-language)
|
|
66
77
|
|
|
67
78
|
## Thrown or printed
|
|
68
79
|
|
|
@@ -133,7 +144,7 @@ key: one copy is the fix for both.
|
|
|
133
144
|
sets.
|
|
134
145
|
|
|
135
146
|
**Fix:** add `alxia()` to `vite.config.ts`, as in the
|
|
136
|
-
[setup](guide.md
|
|
147
|
+
[setup](guide/getting-started.md). With a server of your own in a monorepo, tell
|
|
137
148
|
Vite to leave the package external:
|
|
138
149
|
|
|
139
150
|
```ts
|
|
@@ -210,7 +221,7 @@ reactRouter(app, { build, getLoadContext: ({ user }, context) => context.set(use
|
|
|
210
221
|
**Fix:** serve the app with the plugin, `alxia()` in `vite.config.ts`: the
|
|
211
222
|
server is then built with the routes, and `app/context.ts` is one module
|
|
212
223
|
for both, in dev and in the build. See
|
|
213
|
-
[the app's own context keys](guide.md#the-apps-own-context-keys).
|
|
224
|
+
[the app's own context keys](guide/typing-the-loaders.md#the-apps-own-context-keys).
|
|
214
225
|
|
|
215
226
|
Without the plugin, read alxia's context with `alxiaOf`: its key,
|
|
216
227
|
`alxiaContext`, lives in this package under `node_modules`, which Vite
|
|
@@ -245,7 +256,7 @@ export default server;
|
|
|
245
256
|
```
|
|
246
257
|
|
|
247
258
|
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).
|
|
259
|
+
plugin: see [a server of your own](guide/customising-the-server.md#a-server-of-your-own-without-the-plugin).
|
|
249
260
|
|
|
250
261
|
### `alxia-react-router: the entry … does not exist. …`
|
|
251
262
|
|
|
@@ -465,7 +476,7 @@ export default createServer({
|
|
|
465
476
|
|
|
466
477
|
or `contentSecurityPolicy: false`. Better than `'unsafe-inline'`: a nonce
|
|
467
478
|
per request, with `nonce: true` and `nonceOf(loadContext)` in
|
|
468
|
-
`entry.server.tsx` ([guide](guide.md#a-csp-nonce)).
|
|
479
|
+
`entry.server.tsx` ([guide](guide/csp-nonce.md#a-csp-nonce)).
|
|
469
480
|
|
|
470
481
|
### `alxia-react-router: … already exists, and reveal leaves it as it is. Run alxia-react-router reveal --force to overwrite it.`
|
|
471
482
|
|
|
@@ -651,7 +662,7 @@ bun add -d typescript@^6
|
|
|
651
662
|
and uses it outside its `loader` and `action`, in the component or at the
|
|
652
663
|
module's top level. React Router keeps that code in the client build, for
|
|
653
664
|
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));
|
|
665
|
+
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
666
|
the client build cannot.
|
|
656
667
|
|
|
657
668
|
**Fix:** use them only in the server's code: in `loader` and `action`, or
|
|
@@ -735,7 +746,7 @@ The image runs `bun --no-install`: without the flag, Bun finds no
|
|
|
735
746
|
whatever version npm has, instead of failing.
|
|
736
747
|
|
|
737
748
|
**Why:** under `react-router build` the plugin bundles every package into
|
|
738
|
-
`build/server/index.js` ([Self-contained](guide.md#self-contained)), so
|
|
749
|
+
`build/server/index.js` ([Self-contained](guide/how-it-works.md#self-contained)), so
|
|
739
750
|
the image needs no `node_modules`. What stays outside the file must be
|
|
740
751
|
installed beside it.
|
|
741
752
|
|
|
@@ -770,6 +781,75 @@ addon built for glibc alone then stops it with
|
|
|
770
781
|
put the final stage back on `oven/bun:1`
|
|
771
782
|
([`@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
783
|
|
|
784
|
+
### `TypeError: defineWorkerContract(): cachePrefix must be a non-empty name, …`
|
|
785
|
+
|
|
786
|
+
The full message is `defineWorkerContract(): cachePrefix must be a non-empty
|
|
787
|
+
name, such as 'my-app'; got ''.`, or `… must not end with '-', which the
|
|
788
|
+
contract adds; got 'my-app-'. Use 'my-app'.`
|
|
789
|
+
|
|
790
|
+
**Why:** every cache name is `<prefix>-<kind>-<version>`, and the worker
|
|
791
|
+
deletes the caches shaped `<prefix>-<kind>-<version>`. An empty prefix would own
|
|
792
|
+
every cache of the origin, and a trailing dash would be doubled.
|
|
793
|
+
|
|
794
|
+
**Fix:** pass the app's own name, without the dash:
|
|
795
|
+
|
|
796
|
+
```ts
|
|
797
|
+
import { defineWorkerContract } from '@alxia/react-router/pwa';
|
|
798
|
+
|
|
799
|
+
export const workerContract = defineWorkerContract({ cachePrefix: 'my-app' });
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
### `TypeError: defineWorkerContract(): cacheVersion must be a non-empty name without '-', …`
|
|
803
|
+
|
|
804
|
+
The full message is `defineWorkerContract(): cacheVersion must be a
|
|
805
|
+
non-empty name without '-', such as 'v2'; got 'v1-beta'.`
|
|
806
|
+
|
|
807
|
+
**Why:** every cache name is `<prefix>-<kind>-<version>`, and the contract
|
|
808
|
+
tells a version from the rest of the name by its dashes. A version with a `-`
|
|
809
|
+
(or an empty one) would make the name ambiguous.
|
|
810
|
+
|
|
811
|
+
**Fix:** use a name with no dash, such as `'v2'` or `'2026'`:
|
|
812
|
+
|
|
813
|
+
```ts
|
|
814
|
+
import { defineWorkerContract } from '@alxia/react-router/pwa';
|
|
815
|
+
|
|
816
|
+
export const workerContract = defineWorkerContract({
|
|
817
|
+
cachePrefix: 'my-app',
|
|
818
|
+
cacheVersion: 'v2',
|
|
819
|
+
});
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
### `parseBrowserManifest(): not React Router's browser manifest …`
|
|
823
|
+
|
|
824
|
+
The full message ends with `(build/client/assets/manifest-*.js).`
|
|
825
|
+
|
|
826
|
+
**Why:** the source holds no `{ … }` object. The build script read another
|
|
827
|
+
file, an empty one, or the build has not run.
|
|
828
|
+
|
|
829
|
+
**Fix:** run `react-router build` first, then read the file named
|
|
830
|
+
`manifest-<hash>.js` in `build/client/assets`, not the server's manifest:
|
|
831
|
+
|
|
832
|
+
```ts
|
|
833
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
834
|
+
import { parseBrowserManifest } from '@alxia/react-router/pwa';
|
|
835
|
+
|
|
836
|
+
const assets = 'build/client/assets';
|
|
837
|
+
const file = readdirSync(assets).find((name) => /^manifest-.*\.js$/.test(name));
|
|
838
|
+
if (!file) throw new Error('Run react-router build first.');
|
|
839
|
+
export const manifest = parseBrowserManifest(readFileSync(`${assets}/${file}`, 'utf8'));
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
### `error: Cannot find package 'react' from '…/@alxia/react-router/dist/pwa/react/index.js'`
|
|
843
|
+
|
|
844
|
+
**Why:** `@alxia/react-router/pwa/react` imports `react`, an optional peer of
|
|
845
|
+
the package, so nothing installs it for you. An app with no `react` does not
|
|
846
|
+
need the subpath; a build script or the worker importing it by mistake meets
|
|
847
|
+
this message too.
|
|
848
|
+
|
|
849
|
+
**Fix:** add `react` where the page is built (`bun add react`), and import
|
|
850
|
+
`/pwa/react` from the page alone. The worker and a build script import
|
|
851
|
+
`@alxia/react-router/pwa`, which imports nothing.
|
|
852
|
+
|
|
773
853
|
## Types
|
|
774
854
|
|
|
775
855
|
### `Property 'alxia' does not exist on type 'Readonly<RouterContextProvider>'`
|
|
@@ -785,7 +865,7 @@ or `Route.ActionArgs` reads `context.alxia`. At runtime it is there; only
|
|
|
785
865
|
**Why:** this package augments `RouterContextProvider` as `react-router`
|
|
786
866
|
exports it, whose types are `dist/production`'s. The generated `+types`
|
|
787
867
|
files read their argument types from `react-router/internal`, whose types,
|
|
788
|
-
in react-router 8.4, point at `dist/development`: a second declaration of
|
|
868
|
+
in react-router 8.4 and 8.5, point at `dist/development`: a second declaration of
|
|
789
869
|
the class, which no exported path reaches, so no augmentation can add
|
|
790
870
|
`alxia` to it.
|
|
791
871
|
|
|
@@ -989,7 +1069,7 @@ following Content Security Policy directive: "script-src 'self'
|
|
|
989
1069
|
attribute.
|
|
990
1070
|
|
|
991
1071
|
**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
|
|
1072
|
+
([guide](guide/csp-nonce.md#a-csp-nonce)). A script of your own reads the nonce
|
|
993
1073
|
through a loader, where `nonceOf(context)` works as well:
|
|
994
1074
|
|
|
995
1075
|
```tsx
|
|
@@ -1119,7 +1199,7 @@ plugin relays the app's sockets to. So `ctx.server` is that side server: its
|
|
|
1119
1199
|
and its `requestIP` and `timeout` know nothing of a request Vite took, so
|
|
1120
1200
|
the default `ctx.ip` is `undefined`. A `page()` (Bun's HTML bundle) is not
|
|
1121
1201
|
served: Vite answers the HTML
|
|
1122
|
-
([The server](guide.md#the-server-server)).
|
|
1202
|
+
([The server](guide/typing-the-loaders.md#the-server-server)).
|
|
1123
1203
|
|
|
1124
1204
|
**Fix:** read `ctx.server` as optional, which also covers `app.request` in
|
|
1125
1205
|
a test, and take nothing from its `url` or `requestIP` in dev:
|
|
@@ -1188,3 +1268,109 @@ export default createServer({ proxy: trustProxy({ trusted: ['10.0.0.0/8'] }) });
|
|
|
1188
1268
|
|
|
1189
1269
|
React Router then gets its request at `originalUrl(ctx)`
|
|
1190
1270
|
([Behind a proxy](https://github.com/softistx/alxia/blob/develop/packages/core/docs/guide/serving.md#behind-a-proxy-proxy)).
|
|
1271
|
+
|
|
1272
|
+
### An unhashed file under `public/assets/` is cached for a year
|
|
1273
|
+
|
|
1274
|
+
A file you put in `public/assets/` (a sprite, an icon) changes on a deploy,
|
|
1275
|
+
and visitors keep the old one.
|
|
1276
|
+
|
|
1277
|
+
**Why:** Vite copies `public/` into `build/client/`, and the client's files
|
|
1278
|
+
under `build/client/assets/` are served `public, max-age=31536000,
|
|
1279
|
+
immutable`, hashed or not. The worker's `cachingFor` agrees by default:
|
|
1280
|
+
everything under `/assets/` is `immutable`, cache-first.
|
|
1281
|
+
|
|
1282
|
+
**Fix:** keep unhashed files out of `assets/`: `public/icons/` is served for
|
|
1283
|
+
an hour and is `static` for the worker. If they must stay there, give
|
|
1284
|
+
`cachingFor` an `immutable` option that leaves their folder out, so the worker
|
|
1285
|
+
revalidates them ([the option](guide/pwa.md#what-the-worker-caches)),
|
|
1286
|
+
and expect the HTTP cache to hold them for a year until the file's name
|
|
1287
|
+
changes.
|
|
1288
|
+
|
|
1289
|
+
### A cache the contract did not make is swept
|
|
1290
|
+
|
|
1291
|
+
Signing out, or a new worker activating, deletes a cache the app made itself.
|
|
1292
|
+
|
|
1293
|
+
**Why:** a contract owns every cache named exactly
|
|
1294
|
+
`<prefix>-<kind>-<version>`, kind being lowercase letters and digits and
|
|
1295
|
+
version having no `-`. That includes a kind the app retired (`my-app-pages-v0` is swept once
|
|
1296
|
+
it is not current). It is also why `app` and `app-admin` can share an origin:
|
|
1297
|
+
`app-admin-assets-v1` has one segment too many to be `app`'s. Contracts never
|
|
1298
|
+
take each other's caches; only a cache made by other code, such as
|
|
1299
|
+
`my-app-runtime-v1`, matches the shape and is swept.
|
|
1300
|
+
|
|
1301
|
+
**Fix:** name your own caches outside the shape. A name is outside it when it
|
|
1302
|
+
has more than `<kind>-<version>` after `<prefix>-`, with the prefix `my-app`:
|
|
1303
|
+
`my-app-runtime-v1-x`. A kind with an uppercase letter, `_` or `.`
|
|
1304
|
+
(`my-app-Runtime-v1`, `my-app-run_time-v1`), or a different prefix, is outside
|
|
1305
|
+
too.
|
|
1306
|
+
|
|
1307
|
+
### The update notice never shows
|
|
1308
|
+
|
|
1309
|
+
A new version is deployed and the page offers no "Reload".
|
|
1310
|
+
|
|
1311
|
+
**Why:** the notice is offered only when a worker finished installing while
|
|
1312
|
+
an older one controls the page. A first install controls nothing, so it
|
|
1313
|
+
offers nothing; the next visit, with the first worker in control, does. And
|
|
1314
|
+
nothing is registered at all where the app does not call
|
|
1315
|
+
`registerServiceWorker` (for example while `useServiceWorker({ enabled })`
|
|
1316
|
+
is given `false`, as `import.meta.env.PROD` is under `react-router dev`). A
|
|
1317
|
+
tab that chose "Later" stays quiet until the next session.
|
|
1318
|
+
|
|
1319
|
+
**Fix:** build, serve and load the page twice, then deploy a change and load
|
|
1320
|
+
it again. In the console, `navigator.serviceWorker.controller` must not be
|
|
1321
|
+
`null` and `(await navigator.serviceWorker.getRegistration()).waiting` must
|
|
1322
|
+
hold the new worker.
|
|
1323
|
+
|
|
1324
|
+
### The worker registers under `react-router dev`
|
|
1325
|
+
|
|
1326
|
+
The dev server serves stale code after an edit, or a page keeps an old
|
|
1327
|
+
version of itself, and `navigator.serviceWorker.controller` is not `null`.
|
|
1328
|
+
|
|
1329
|
+
**Why:** `useServiceWorker` registers whenever `enabled` is `true`, and the
|
|
1330
|
+
package does not know the mode: it never reads `import.meta.env`. A worker
|
|
1331
|
+
under a dev server caches the code you are editing.
|
|
1332
|
+
|
|
1333
|
+
**Fix:** pass the mode from the app, `useServiceWorker({ enabled:
|
|
1334
|
+
import.meta.env.PROD })` ([the hook](guide/pwa.md#in-react)). Then unregister
|
|
1335
|
+
the worker the dev server already took (DevTools, Application, Service
|
|
1336
|
+
Workers, Unregister) and clear its site data.
|
|
1337
|
+
|
|
1338
|
+
### An update waits an hour
|
|
1339
|
+
|
|
1340
|
+
A new worker is deployed and tabs still run the old one for up to an hour.
|
|
1341
|
+
|
|
1342
|
+
**Why:** the worker's script was served with the client's hour of
|
|
1343
|
+
`Cache-Control`, so the browser's update check found the cached script.
|
|
1344
|
+
Either `noCache` is not declared, or it is declared in `configure`, which
|
|
1345
|
+
runs after the client's files: they answer first and the middleware never
|
|
1346
|
+
sees the request.
|
|
1347
|
+
|
|
1348
|
+
**Fix:** declare it in `beforeAll`
|
|
1349
|
+
([serving the worker's script](guide/pwa.md#serving-the-workers-script)), and
|
|
1350
|
+
check with `curl -I https://your.app/service-worker.js` that `cache-control` is
|
|
1351
|
+
`no-cache`.
|
|
1352
|
+
|
|
1353
|
+
### The worker cannot fetch another origin
|
|
1354
|
+
|
|
1355
|
+
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.
|
|
1356
|
+
|
|
1357
|
+
**Why:** `noCache` gives the worker's script `WORKER_POLICY`,
|
|
1358
|
+
`default-src 'none'; connect-src 'self'`: the worker fetches this site only.
|
|
1359
|
+
|
|
1360
|
+
**Fix:** if the worker really must reach another origin, serve the script
|
|
1361
|
+
with a wider policy through `revalidated(response, policy)` in a middleware of
|
|
1362
|
+
your own ([serving the worker's script](guide/pwa.md#serving-the-workers-script)),
|
|
1363
|
+
rather than loosening the pages' policy.
|
|
1364
|
+
|
|
1365
|
+
### The manifest is in the wrong language
|
|
1366
|
+
|
|
1367
|
+
The installed app's name and description are in the default language,
|
|
1368
|
+
whatever the user chose.
|
|
1369
|
+
|
|
1370
|
+
**Why:** the browser fetches the manifest without cookies, so the `language`
|
|
1371
|
+
cookie never reaches the server. Only the link's query does.
|
|
1372
|
+
|
|
1373
|
+
**Fix:** link `manifestHref(language)` (`/manifest.webmanifest?lang=fr`), and
|
|
1374
|
+
read it with `manifestLanguage(url, supported, fallback)`; the fallback is a
|
|
1375
|
+
supported language the app resolves from the request's `Accept-Language`, not
|
|
1376
|
+
from the cookie ([the manifest route](guide/pwa.md#the-web-app-manifest-and-why-the-language-travels-in-lang)).
|
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",
|
|
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`)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -27,6 +27,21 @@
|
|
|
27
27
|
"import": "./dist/vite/index.js",
|
|
28
28
|
"default": "./dist/vite/index.js"
|
|
29
29
|
},
|
|
30
|
+
"./pwa": {
|
|
31
|
+
"types": "./dist/pwa/index.d.ts",
|
|
32
|
+
"import": "./dist/pwa/index.js",
|
|
33
|
+
"default": "./dist/pwa/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./pwa/react": {
|
|
36
|
+
"types": "./dist/pwa/react/index.d.ts",
|
|
37
|
+
"import": "./dist/pwa/react/index.js",
|
|
38
|
+
"default": "./dist/pwa/react/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./pwa/server": {
|
|
41
|
+
"types": "./dist/pwa/server/index.d.ts",
|
|
42
|
+
"import": "./dist/pwa/server/index.js",
|
|
43
|
+
"default": "./dist/pwa/server/index.js"
|
|
44
|
+
},
|
|
30
45
|
"./package.json": "./package.json"
|
|
31
46
|
},
|
|
32
47
|
"repository": {
|
|
@@ -47,30 +62,37 @@
|
|
|
47
62
|
"entrypoints": [
|
|
48
63
|
"src/index.ts",
|
|
49
64
|
"src/vite/index.ts",
|
|
65
|
+
"src/pwa/index.ts",
|
|
66
|
+
"src/pwa/react/index.ts",
|
|
67
|
+
"src/pwa/server/index.ts",
|
|
50
68
|
"src/cli/index.ts"
|
|
51
69
|
]
|
|
52
70
|
},
|
|
53
71
|
"devDependencies": {
|
|
54
|
-
"@alxia/compress": "^0.3.
|
|
55
|
-
"@alxia/core": "^0.
|
|
56
|
-
"@alxia/openapi": "^0.6.
|
|
57
|
-
"@react-router/dev": "^8.
|
|
72
|
+
"@alxia/compress": "^0.3.10",
|
|
73
|
+
"@alxia/core": "^0.15.1",
|
|
74
|
+
"@alxia/openapi": "^0.6.10",
|
|
75
|
+
"@react-router/dev": "^8.5.0",
|
|
58
76
|
"@types/bun": "^1.4.2",
|
|
59
77
|
"@types/react": "^19.3.0",
|
|
60
78
|
"@types/react-dom": "^19.3.0",
|
|
61
79
|
"isbot": "^5.2.2",
|
|
62
|
-
"react": "^19.3.
|
|
63
|
-
"react-dom": "^19.3.
|
|
64
|
-
"react-router": "^8.
|
|
80
|
+
"react": "^19.3.1",
|
|
81
|
+
"react-dom": "^19.3.1",
|
|
82
|
+
"react-router": "^8.5.0",
|
|
65
83
|
"vite": "^7.0.0"
|
|
66
84
|
},
|
|
67
85
|
"peerDependencies": {
|
|
68
|
-
"@alxia/core": "^0.
|
|
86
|
+
"@alxia/core": "^0.15.1",
|
|
87
|
+
"react": "^19.0.0",
|
|
69
88
|
"react-router": "^8.0.0",
|
|
70
89
|
"typescript": "^6.0.3 || ^7.0.0",
|
|
71
90
|
"vite": "^7.0.0 || ^8.0.0"
|
|
72
91
|
},
|
|
73
92
|
"peerDependenciesMeta": {
|
|
93
|
+
"react": {
|
|
94
|
+
"optional": true
|
|
95
|
+
},
|
|
74
96
|
"vite": {
|
|
75
97
|
"optional": true
|
|
76
98
|
}
|