@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/README.md
CHANGED
|
@@ -92,7 +92,10 @@ Vite's preview server, as `bun run start` would: the pages, `/api`, the
|
|
|
92
92
|
client's files and the app's middlewares. So does React Router's prerendering.
|
|
93
93
|
|
|
94
94
|
`@alxia/core` and `react-router` 8 are peers, and `vite` 7 or 8 is an
|
|
95
|
-
optional peer, for `/vite`; `react` 19 is an optional peer, for `/pwa/react`. The
|
|
95
|
+
optional peer, for `/vite`; `react` 19 is an optional peer, for `/pwa/react`. The six `workbox-*`
|
|
96
|
+
packages (`workbox-core`, `workbox-expiration`, `workbox-navigation-preload`,
|
|
97
|
+
`workbox-precaching`, `workbox-routing`, `workbox-strategies`) are optional
|
|
98
|
+
peers too, for `/pwa/worker` (the [PWA section](#installable-and-offline-pwa) has the install line). The package declares no dependency.
|
|
96
99
|
|
|
97
100
|
Its `typescript` peer is 6 or 7. The template's TypeScript 5.9 typechecks,
|
|
98
101
|
but `bun add` warns about it; `bun add -d typescript@^6` silences the
|
|
@@ -484,23 +487,51 @@ no UI: the service worker's contract, which requests it caches and which
|
|
|
484
487
|
responses it may store, the precache list read from a build, the
|
|
485
488
|
registration and update notice, the web app manifest, and the cleaning at
|
|
486
489
|
sign-out. It imports nothing, so the worker, a build script, the page and
|
|
487
|
-
the server can all import it. The
|
|
488
|
-
|
|
489
|
-
|
|
490
|
+
the server can all import it. The worker itself is `startWorker` in
|
|
491
|
+
`/pwa/worker`, its build `buildWorker` in `/pwa/build`, the hooks are in
|
|
492
|
+
`/pwa/react` (`react`, an optional peer) and `noCache` in `/pwa/server`; this
|
|
493
|
+
package never reads `import.meta.env`, so the app passes `enabled`.
|
|
494
|
+
|
|
495
|
+
With `app/service-worker.ts` in the app, `react-router build` writes
|
|
496
|
+
`build/client/service-worker.js` itself: `alxia()` bundles the file after the
|
|
497
|
+
client build, with the precache list written in, and there is no script to
|
|
498
|
+
add. It needs Bun (`bun --bun react-router build`, or a `bunfig.toml` with
|
|
499
|
+
`[run] bun = true`). `alxia({ worker: 'app/pwa/worker.ts' })` names another file
|
|
500
|
+
and `alxia({ worker: false })` builds none.
|
|
501
|
+
|
|
502
|
+
The worker bundles Workbox, whose six packages are optional peers:
|
|
503
|
+
|
|
504
|
+
```sh
|
|
505
|
+
bun add -d workbox-core workbox-expiration workbox-navigation-preload workbox-precaching workbox-routing workbox-strategies
|
|
506
|
+
```
|
|
490
507
|
|
|
491
508
|
```ts
|
|
492
|
-
// file: app/pwa-contract.ts
|
|
493
|
-
// every cache is named `my-app-<kind>-<version>`.
|
|
509
|
+
// file: app/pwa-contract.ts
|
|
510
|
+
// Shared by the worker and the page: every cache is named `my-app-<kind>-<version>`.
|
|
494
511
|
import { defineWorkerContract } from '@alxia/react-router/pwa';
|
|
495
512
|
|
|
496
513
|
export const workerContract = defineWorkerContract({ cachePrefix: 'my-app' });
|
|
497
514
|
```
|
|
498
515
|
|
|
516
|
+
```ts
|
|
517
|
+
// file: app/service-worker.ts
|
|
518
|
+
import { startWorker } from '@alxia/react-router/pwa/worker';
|
|
519
|
+
import { workerContract } from './pwa-contract';
|
|
520
|
+
|
|
521
|
+
startWorker({ contract: workerContract });
|
|
522
|
+
```
|
|
523
|
+
|
|
499
524
|
```tsx
|
|
500
525
|
// file: app/root.tsx
|
|
526
|
+
import { manifestHref } from '@alxia/react-router/pwa';
|
|
501
527
|
import { useServiceWorker } from '@alxia/react-router/pwa/react';
|
|
502
528
|
import { Outlet } from 'react-router';
|
|
503
529
|
|
|
530
|
+
// The browser reads the manifest to offer the install.
|
|
531
|
+
export function links() {
|
|
532
|
+
return [{ rel: 'manifest', href: manifestHref('en') }];
|
|
533
|
+
}
|
|
534
|
+
|
|
504
535
|
// A production build only, never under `react-router dev`.
|
|
505
536
|
export default function App() {
|
|
506
537
|
useServiceWorker({ enabled: import.meta.env.PROD });
|
|
@@ -511,18 +542,84 @@ export default function App() {
|
|
|
511
542
|
```ts
|
|
512
543
|
// file: app/server.ts
|
|
513
544
|
import { createServer } from '@alxia/react-router';
|
|
514
|
-
import {
|
|
545
|
+
import {
|
|
546
|
+
MANIFEST_HEADERS,
|
|
547
|
+
MANIFEST_PATH,
|
|
548
|
+
SERVICE_WORKER_URL,
|
|
549
|
+
webAppManifest,
|
|
550
|
+
} from '@alxia/react-router/pwa';
|
|
515
551
|
import { noCache } from '@alxia/react-router/pwa/server';
|
|
516
552
|
|
|
517
|
-
//
|
|
553
|
+
// beforeAll runs before the client's files: the worker's script is
|
|
554
|
+
// revalidated. The manifest is a route, served with its own headers.
|
|
518
555
|
export default createServer({
|
|
519
556
|
beforeAll: (app) => app.use(SERVICE_WORKER_URL, noCache),
|
|
557
|
+
configure: (app) =>
|
|
558
|
+
app.get(MANIFEST_PATH, ({ reply }) => {
|
|
559
|
+
const manifest = webAppManifest({
|
|
560
|
+
name: 'My app',
|
|
561
|
+
description: 'My installable app',
|
|
562
|
+
lang: 'en',
|
|
563
|
+
themeColor: '#1e40af',
|
|
564
|
+
backgroundColor: '#ffffff',
|
|
565
|
+
});
|
|
566
|
+
return reply(200, JSON.stringify(manifest), { headers: MANIFEST_HEADERS });
|
|
567
|
+
}),
|
|
520
568
|
});
|
|
521
569
|
```
|
|
522
570
|
|
|
571
|
+
The manifest names four icons (`MINIMAL_2023_ICONS`): put
|
|
572
|
+
`pwa-64x64.png`, `pwa-192x192.png`, `pwa-512x512.png` and
|
|
573
|
+
`maskable-icon-512x512.png` in `public/`, which `@vite-pwa/assets-generator`'s
|
|
574
|
+
`minimal2023Preset` writes from one image. A browser offers the install with
|
|
575
|
+
a 192 and a 512.
|
|
576
|
+
|
|
577
|
+
The worker serves a page load from `public/offline.html` when the network
|
|
578
|
+
fails, and the build needs the file:
|
|
579
|
+
|
|
580
|
+
```html
|
|
581
|
+
<!-- file: public/offline.html -->
|
|
582
|
+
<!doctype html>
|
|
583
|
+
<html lang="en">
|
|
584
|
+
<head>
|
|
585
|
+
<meta charset="utf-8" />
|
|
586
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
587
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'" />
|
|
588
|
+
<title>Offline</title>
|
|
589
|
+
</head>
|
|
590
|
+
<body>
|
|
591
|
+
<h1>You are offline</h1>
|
|
592
|
+
<p>Check your connection and try again.</p>
|
|
593
|
+
</body>
|
|
594
|
+
</html>
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
Set `routeDiscovery: { mode: 'initial' }` in `react-router.config.ts`, so a
|
|
598
|
+
client navigation offline to a route never visited finds its modules
|
|
599
|
+
([more](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md#offline-a-client-navigation-to-a-route-never-visited-shows-the-offline-page)):
|
|
600
|
+
|
|
601
|
+
```ts
|
|
602
|
+
import type { Config } from '@react-router/dev/config';
|
|
603
|
+
|
|
604
|
+
export default { ssr: true, routeDiscovery: { mode: 'initial' } } satisfies Config;
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
An app that built its worker with a script of its own has steps to drop:
|
|
608
|
+
[moving from a build script of your own](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/pwa.md#moving-from-a-build-script-of-your-own).
|
|
609
|
+
|
|
610
|
+
For a build pipeline of your own, `buildWorker` from `/pwa/build` does what
|
|
611
|
+
`alxia()` does. It needs Bun and runs after `react-router build`:
|
|
612
|
+
|
|
613
|
+
```ts
|
|
614
|
+
// file: scripts/build-worker.ts
|
|
615
|
+
import { buildWorker } from '@alxia/react-router/pwa/build';
|
|
616
|
+
|
|
617
|
+
await buildWorker({ file: 'app/service-worker.ts' });
|
|
618
|
+
```
|
|
619
|
+
|
|
523
620
|
The [PWA guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/pwa.md)
|
|
524
|
-
has the worker's
|
|
525
|
-
rest.
|
|
621
|
+
has the worker's options, the offline page, the manifest route, the update
|
|
622
|
+
banner and the rest.
|
|
526
623
|
|
|
527
624
|
## Traps
|
|
528
625
|
|
|
@@ -574,8 +671,8 @@ From `@alxia/react-router/vite`:
|
|
|
574
671
|
|
|
575
672
|
| export | |
|
|
576
673
|
| --- | --- |
|
|
577
|
-
| `alxia(options?)` | the Vite plugin, for `react-router dev`, `react-router build` and `vite preview`, building the server for Bun. `entry` is the server file: `app/server.ts` by default, or the default server when there is none |
|
|
578
|
-
| `AlxiaOptions` | its options |
|
|
674
|
+
| `alxia(options?)` | the Vite plugin, for `react-router dev`, `react-router build` and `vite preview`, building the server for Bun. `entry` is the server file: `app/server.ts` by default, or the default server when there is none. `worker` is the service worker built after `react-router build`: `app/service-worker.ts` by default when it exists, a file, `AlxiaWorkerOptions`, or `false` |
|
|
675
|
+
| `AlxiaOptions`, `AlxiaWorkerOptions` | its options, and the worker's (`buildWorker()`'s but `root`, `client` and `file`, which is optional) |
|
|
579
676
|
|
|
580
677
|
From `@alxia/react-router/pwa`:
|
|
581
678
|
|
|
@@ -623,6 +720,22 @@ From `@alxia/react-router/pwa/server` (`@alxia/core` is its only package import)
|
|
|
623
720
|
| `revalidated(response, policy?)` | a response with those two headers set, on a copy when its headers are immutable; `policy` is `WORKER_POLICY` by default |
|
|
624
721
|
| `WORKER_POLICY` | `default-src 'none'; connect-src 'self'`: the worker runs no other script and fetches this site only |
|
|
625
722
|
|
|
723
|
+
From `@alxia/react-router/pwa/worker` (the six `workbox-*` packages, optional peers, are its only package imports):
|
|
724
|
+
|
|
725
|
+
| export | |
|
|
726
|
+
| --- | --- |
|
|
727
|
+
| `startWorker(options)` | the worker's strategies: the precache route, `NetworkOnly` for a page load with the precached offline page as its fallback, `CacheFirst` for hashed bundles, `StaleWhileRevalidate` for other static files (both storing only what `isStorable` accepts), the obsolete caches swept on `activate`, and `SKIP_WAITING` applied |
|
|
728
|
+
| `WorkerOptions` | `contract` (required), `caching`, `assets`, `static`, `navigationPreload`, `precache` |
|
|
729
|
+
| `ExpirationOptions` | `{ maxEntries?, maxAgeSeconds? }` |
|
|
730
|
+
|
|
731
|
+
From `@alxia/react-router/pwa/build` (Bun; Node's built-ins its only imports):
|
|
732
|
+
|
|
733
|
+
| export | |
|
|
734
|
+
| --- | --- |
|
|
735
|
+
| `buildWorker(options)` | bundles the worker into `build/client/service-worker.js` with its precache list (the offline page by its content, every route's code, `precacheAssets`), after `react-router build` |
|
|
736
|
+
| `BuildWorkerOptions` | `file` (required), `root`, `client`, `contract`, `offline`, `precacheAssets`, `minify` |
|
|
737
|
+
| `WorkerBuild` | `{ file, precache, precacheBytes, bytes }` |
|
|
738
|
+
|
|
626
739
|
The `alxia-react-router` bin, run with `bunx`:
|
|
627
740
|
|
|
628
741
|
| command | |
|
|
@@ -637,7 +750,7 @@ The `alxia-react-router` bin, run with `bunx`:
|
|
|
637
750
|
- [Customising the server](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/customising-the-server.md): `createServer()`, and the escape hatches.
|
|
638
751
|
- [Typing the loaders](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/typing-the-loaders.md): `Register`, and the app's own context keys.
|
|
639
752
|
- [A CSP nonce](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/csp-nonce.md), [Beside the pages](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/beside-the-pages.md) (middlewares, routes, WebSockets, the client's files), [OpenAPI](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/openapi.md), [Testing](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/testing.md) and [Deploying](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/deploying.md), with Docker.
|
|
640
|
-
- [Installable and offline](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/pwa.md): what
|
|
753
|
+
- [Installable and offline](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/pwa.md): what the subpaths hold, the worker's contract, the worker, what it caches, the offline page, building the worker with `alxia()` or `buildWorker`, the hooks in React, registering the worker and offering an update, serving the worker's script, the web app manifest, and clearing at sign-out.
|
|
641
754
|
- [Troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md): each message, and the traps that print none.
|
|
642
755
|
- [Roadmap](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/roadmap.md): what is coming, and what is not planned.
|
|
643
756
|
- [Example](https://github.com/softistx/alxia/tree/develop/examples/react-router): the official template, these four changes, then an `app/server.ts` with a session, an `/api`, secure headers with a nonce and a streamed page.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/pwa/worker/injected.ts
|
|
2
|
+
var PRECACHE_DEFINE = "globalThis.__ALXIA_PRECACHE__";
|
|
3
|
+
function injectedPrecache() {
|
|
4
|
+
return globalThis.__ALXIA_PRECACHE__;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export { PRECACHE_DEFINE, injectedPrecache };
|
|
8
|
+
|
|
9
|
+
//# debugId=CA917CBC5F688F4264756E2164756E21
|
|
10
|
+
//# sourceMappingURL=index-59vhfj6n.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pwa/worker/injected.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/** The define key the build sets: the worker's precache list, as JSON. */\nexport const PRECACHE_DEFINE = 'globalThis.__ALXIA_PRECACHE__';\n\n/** One file the worker precaches: its URL and a revision (`null` when hashed in the name). */\ntype Entry = { readonly url: string; readonly revision: string | null };\n\n/**\n * The precache list the build wrote into this worker, or `undefined` when the\n * worker did not go through the build. Read through `globalThis`, so a missing\n * define is `undefined`, never a `ReferenceError`.\n */\nexport function injectedPrecache(): readonly Entry[] | undefined {\n\treturn (globalThis as { __ALXIA_PRECACHE__?: readonly Entry[] })\n\t\t.__ALXIA_PRECACHE__;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AACO,IAAM,kBAAkB;AAUxB,SAAS,gBAAgB,GAAiC;AAAA,EAChE,OAAQ,WACN;AAAA;",
|
|
8
|
+
"debugId": "CA917CBC5F688F4264756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/pwa/model/precache-list.ts
|
|
2
|
+
var HASHED_ASSET = /^\/assets\/[^/]+-[\w-]{8}\.(?:js|css)$/;
|
|
3
|
+
function parseBrowserManifest2(source) {
|
|
4
|
+
const start = source.indexOf("{");
|
|
5
|
+
const end = source.lastIndexOf("}");
|
|
6
|
+
if (start < 0 || end < start) {
|
|
7
|
+
throw new Error("parseBrowserManifest(): not React Router's browser manifest (build/client/assets/manifest-*.js).");
|
|
8
|
+
}
|
|
9
|
+
return JSON.parse(source.slice(start, end + 1));
|
|
10
|
+
}
|
|
11
|
+
function precacheList2(manifest) {
|
|
12
|
+
const urls = new Set([manifest.url]);
|
|
13
|
+
for (const part of [manifest.entry, ...Object.values(manifest.routes)]) {
|
|
14
|
+
for (const url of [
|
|
15
|
+
...part.module ? [part.module] : [],
|
|
16
|
+
...part.imports ?? [],
|
|
17
|
+
...part.css ?? []
|
|
18
|
+
]) {
|
|
19
|
+
urls.add(url);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return [...urls].filter((url) => HASHED_ASSET.test(url)).map((url) => ({ url, revision: null }));
|
|
23
|
+
}
|
|
24
|
+
function assetPrecache2(assetFiles, include, base = "/assets/") {
|
|
25
|
+
return assetFiles.filter((file) => {
|
|
26
|
+
include.lastIndex = 0;
|
|
27
|
+
return include.test(file);
|
|
28
|
+
}).sort().map((file) => ({ url: `${base}${file}`, revision: null }));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { parseBrowserManifest2, precacheList2, assetPrecache2 };
|
|
32
|
+
|
|
33
|
+
//# debugId=812DDA9793A6A64B64756E2164756E21
|
|
34
|
+
//# sourceMappingURL=index-5gqcpyyg.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pwa/model/precache-list.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * What the service worker precaches, read from a build: every script and\n * stylesheet React Router's manifest names (the client entry and each\n * route's module, imports and CSS), and the manifest itself, so a client\n * navigation offline to a route never visited still finds its code. Only\n * hashed `/assets/` files: a new build names new files, so each precaches\n * under its own URL, revision `null`. Chunks only a component loads on\n * demand stay out: a cache-first route stores them the first time they load.\n *\n * Dependency-free: a build script runs it at build time.\n */\n\n/** A route or the entry, as `window.__reactRouterManifest` lists it. */\ntype ManifestModule = { module?: string; imports?: string[]; css?: string[] };\n\n/** The parts of React Router's browser manifest the list reads. */\nexport type BrowserManifest = {\n\turl: string;\n\tentry: ManifestModule;\n\troutes: Record<string, ManifestModule>;\n};\n\n/** A Workbox precache entry: a hashed URL needs no revision. */\nexport type PrecacheEntry = { url: string; revision: string | null };\n\n/** A Vite-hashed file under `/assets/`: `name-<8 hash chars>.js|css`. */\nconst HASHED_ASSET = /^\\/assets\\/[^/]+-[\\w-]{8}\\.(?:js|css)$/;\n\n/** Reads `window.__reactRouterManifest={…};` (build/client/assets/manifest-*.js). */\nexport function parseBrowserManifest(source: string): BrowserManifest {\n\tconst start = source.indexOf('{');\n\tconst end = source.lastIndexOf('}');\n\tif (start < 0 || end < start) {\n\t\tthrow new Error(\n\t\t\t\"parseBrowserManifest(): not React Router's browser manifest (build/client/assets/manifest-*.js).\",\n\t\t);\n\t}\n\treturn JSON.parse(source.slice(start, end + 1)) as BrowserManifest;\n}\n\n/** The hashed scripts and stylesheets every route needs, the manifest's own file first. */\nexport function precacheList(manifest: BrowserManifest): PrecacheEntry[] {\n\tconst urls = new Set<string>([manifest.url]);\n\tfor (const part of [manifest.entry, ...Object.values(manifest.routes)]) {\n\t\tfor (const url of [\n\t\t\t...(part.module ? [part.module] : []),\n\t\t\t...(part.imports ?? []),\n\t\t\t...(part.css ?? []),\n\t\t]) {\n\t\t\turls.add(url);\n\t\t}\n\t}\n\treturn [...urls]\n\t\t.filter((url) => HASHED_ASSET.test(url))\n\t\t.map((url) => ({ url, revision: null }));\n}\n\n/**\n * The precache entries of the files among `assetFiles` (the names in\n * build/client/assets) that `include` matches, sorted, under `base`: fonts\n * an app keeps offline, say. The regex is the app's.\n */\nexport function assetPrecache(\n\tassetFiles: readonly string[],\n\tinclude: RegExp,\n\tbase = '/assets/',\n): PrecacheEntry[] {\n\treturn assetFiles\n\t\t.filter((file) => {\n\t\t\tinclude.lastIndex = 0; // a /g or /y regex remembers its last match\n\t\t\treturn include.test(file);\n\t\t})\n\t\t.sort()\n\t\t.map((file) => ({ url: `${base}${file}`, revision: null }));\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AA0BA,IAAM,eAAe;AAGd,SAAS,qBAAoB,CAAC,QAAiC;AAAA,EACrE,MAAM,QAAQ,OAAO,QAAQ,GAAG;AAAA,EAChC,MAAM,MAAM,OAAO,YAAY,GAAG;AAAA,EAClC,IAAI,QAAQ,KAAK,MAAM,OAAO;AAAA,IAC7B,MAAM,IAAI,MACT,kGACD;AAAA,EACD;AAAA,EACA,OAAO,KAAK,MAAM,OAAO,MAAM,OAAO,MAAM,CAAC,CAAC;AAAA;AAIxC,SAAS,aAAY,CAAC,UAA4C;AAAA,EACxE,MAAM,OAAO,IAAI,IAAY,CAAC,SAAS,GAAG,CAAC;AAAA,EAC3C,WAAW,QAAQ,CAAC,SAAS,OAAO,GAAG,OAAO,OAAO,SAAS,MAAM,CAAC,GAAG;AAAA,IACvE,WAAW,OAAO;AAAA,MACjB,GAAI,KAAK,SAAS,CAAC,KAAK,MAAM,IAAI,CAAC;AAAA,MACnC,GAAI,KAAK,WAAW,CAAC;AAAA,MACrB,GAAI,KAAK,OAAO,CAAC;AAAA,IAClB,GAAG;AAAA,MACF,KAAK,IAAI,GAAG;AAAA,IACb;AAAA,EACD;AAAA,EACA,OAAO,CAAC,GAAG,IAAI,EACb,OAAO,CAAC,QAAQ,aAAa,KAAK,GAAG,CAAC,EACtC,IAAI,CAAC,SAAS,EAAE,KAAK,UAAU,KAAK,EAAE;AAAA;AAQlC,SAAS,cAAa,CAC5B,YACA,SACA,OAAO,YACW;AAAA,EAClB,OAAO,WACL,OAAO,CAAC,SAAS;AAAA,IACjB,QAAQ,YAAY;AAAA,IACpB,OAAO,QAAQ,KAAK,IAAI;AAAA,GACxB,EACA,KAAK,EACL,IAAI,CAAC,UAAU,EAAE,KAAK,GAAG,OAAO,QAAQ,UAAU,KAAK,EAAE;AAAA;",
|
|
8
|
+
"debugId": "812DDA9793A6A64B64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -1,38 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function defineWorkerContract2(options) {
|
|
6
|
-
const { cachePrefix } = options;
|
|
7
|
-
if (typeof cachePrefix !== "string" || cachePrefix === "") {
|
|
8
|
-
throw new TypeError(`defineWorkerContract(): cachePrefix must be a non-empty name, such as 'my-app'; got '${String(cachePrefix)}'.`);
|
|
9
|
-
}
|
|
10
|
-
if (cachePrefix.endsWith("-")) {
|
|
11
|
-
throw new TypeError(`defineWorkerContract(): cachePrefix must not end with '-', which the contract adds; got '${cachePrefix}'. Use '${cachePrefix.replace(/-+$/, "")}'.`);
|
|
12
|
-
}
|
|
13
|
-
const cacheVersion = options.cacheVersion ?? "v1";
|
|
14
|
-
if (typeof cacheVersion !== "string" || cacheVersion === "" || cacheVersion.includes("-")) {
|
|
15
|
-
throw new TypeError(`defineWorkerContract(): cacheVersion must be a non-empty name without '-', such as 'v2'; got '${String(cacheVersion)}'.`);
|
|
16
|
-
}
|
|
17
|
-
const cacheNames = Object.freeze({
|
|
18
|
-
precache: `${cachePrefix}-precache-${cacheVersion}`,
|
|
19
|
-
assets: `${cachePrefix}-assets-${cacheVersion}`,
|
|
20
|
-
static: `${cachePrefix}-static-${cacheVersion}`
|
|
21
|
-
});
|
|
22
|
-
const current = new Set(Object.values(cacheNames));
|
|
23
|
-
const shape = new RegExp(`^${cachePrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}-[a-z0-9]+-[^-]+$`);
|
|
24
|
-
const owned = (name) => shape.test(name);
|
|
25
|
-
const contract = {
|
|
26
|
-
cachePrefix,
|
|
27
|
-
cacheVersion,
|
|
28
|
-
serviceWorkerUrl: options.serviceWorkerUrl ?? SERVICE_WORKER_URL2,
|
|
29
|
-
offlineUrl: options.offlineUrl ?? OFFLINE_URL2,
|
|
30
|
-
cacheNames,
|
|
31
|
-
obsoleteCaches: (names) => names.filter((name) => owned(name) && !current.has(name)),
|
|
32
|
-
clearableCaches: (names) => names.filter((name) => owned(name) && name !== cacheNames.precache)
|
|
33
|
-
};
|
|
34
|
-
return Object.freeze(contract);
|
|
35
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
SERVICE_WORKER_URL2,
|
|
3
|
+
SKIP_WAITING2
|
|
4
|
+
} from "./index-rfr02vg4.js";
|
|
36
5
|
|
|
37
6
|
// src/pwa/model/registration.ts
|
|
38
7
|
function watchForUpdates2(registration, container, onWaiting) {
|
|
@@ -133,7 +102,7 @@ function createUpdateNotice2(storage, key = UPDATE_LATER_KEY2) {
|
|
|
133
102
|
}
|
|
134
103
|
var sessionStore2 = () => typeof sessionStorage === "undefined" ? undefined : sessionStorage;
|
|
135
104
|
|
|
136
|
-
export {
|
|
105
|
+
export { watchForUpdates2, createUpdater2, afterLoad2, registerServiceWorker2, UPDATE_LATER_KEY2, createUpdateNotice2, sessionStore2 };
|
|
137
106
|
|
|
138
|
-
//# debugId=
|
|
139
|
-
//# sourceMappingURL=index-
|
|
107
|
+
//# debugId=824F7298E795FD3C64756E2164756E21
|
|
108
|
+
//# sourceMappingURL=index-9xv2a8et.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pwa/model/registration.ts", "../src/pwa/model/update-notice.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"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",
|
|
6
|
+
"/**\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"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "824F7298E795FD3C64756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseBrowserManifest2,
|
|
3
|
+
precacheList2,
|
|
4
|
+
assetPrecache2
|
|
5
|
+
} from "./index-5gqcpyyg.js";
|
|
6
|
+
import {
|
|
7
|
+
PRECACHE_DEFINE
|
|
8
|
+
} from "./index-59vhfj6n.js";
|
|
9
|
+
import {
|
|
10
|
+
SERVICE_WORKER_URL2,
|
|
11
|
+
OFFLINE_URL2
|
|
12
|
+
} from "./index-rfr02vg4.js";
|
|
13
|
+
|
|
14
|
+
// src/pwa/build/build-worker.ts
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
17
|
+
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
18
|
+
|
|
19
|
+
// src/pwa/build/precache.ts
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
21
|
+
function browserManifestFile(files) {
|
|
22
|
+
const found = files.filter((file) => /^manifest-[\w-]+\.js$/.test(file));
|
|
23
|
+
const [only] = found;
|
|
24
|
+
if (found.length !== 1 || only === undefined) {
|
|
25
|
+
throw new Error(`buildWorker(): expected one React Router browser manifest, build/client/assets/manifest-*.js, found ${found.length}. Run react-router build first.`);
|
|
26
|
+
}
|
|
27
|
+
return only;
|
|
28
|
+
}
|
|
29
|
+
function revisionOf(bytes) {
|
|
30
|
+
return createHash("sha256").update(bytes).digest("hex").slice(0, 16);
|
|
31
|
+
}
|
|
32
|
+
function workerPrecache(input) {
|
|
33
|
+
const all = [
|
|
34
|
+
...input.offline ? [input.offline] : [],
|
|
35
|
+
...precacheList2(parseBrowserManifest2(input.manifestSource)),
|
|
36
|
+
...input.include ? assetPrecache2(input.assetFiles, input.include) : []
|
|
37
|
+
];
|
|
38
|
+
const seen = new Set;
|
|
39
|
+
return all.filter(({ url }) => !seen.has(url) && seen.add(url));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/pwa/build/build-worker.ts
|
|
43
|
+
function assertBun(onBun = globalThis.Bun !== undefined) {
|
|
44
|
+
if (onBun)
|
|
45
|
+
return;
|
|
46
|
+
throw new Error("buildWorker() runs on Bun: it is running on Node. Run it with bun, or run react-router build with bun --bun.");
|
|
47
|
+
}
|
|
48
|
+
function urlInClient(client, url) {
|
|
49
|
+
const path = join(client, url);
|
|
50
|
+
if (!url.startsWith("/") || /^[a-z][a-z0-9+.-]*:|^\/\//i.test(url) || !path.startsWith(client + sep) || path.endsWith(sep)) {
|
|
51
|
+
throw new Error(`buildWorker(): ${url} is outside ${client}.`);
|
|
52
|
+
}
|
|
53
|
+
return path;
|
|
54
|
+
}
|
|
55
|
+
async function buildWorker2(options) {
|
|
56
|
+
assertBun();
|
|
57
|
+
const root = resolve(options.root ?? process.cwd());
|
|
58
|
+
const client = resolve(root, options.client ?? "build/client");
|
|
59
|
+
const source = resolve(root, options.file);
|
|
60
|
+
const workerUrl = options.contract?.serviceWorkerUrl ?? SERVICE_WORKER_URL2;
|
|
61
|
+
const offlineUrl = options.contract?.offlineUrl ?? OFFLINE_URL2;
|
|
62
|
+
if (!existsSync(source)) {
|
|
63
|
+
throw new Error(`buildWorker(): ${options.file} does not exist.`);
|
|
64
|
+
}
|
|
65
|
+
const assets = join(client, "assets");
|
|
66
|
+
const files = await readdir(assets).catch(() => []);
|
|
67
|
+
const manifest = browserManifestFile(files);
|
|
68
|
+
let offline;
|
|
69
|
+
if (options.offline !== false) {
|
|
70
|
+
const page = urlInClient(client, offlineUrl);
|
|
71
|
+
if (!existsSync(page)) {
|
|
72
|
+
throw new Error(`buildWorker(): ${options.client ?? "build/client"}${offlineUrl} does not exist. Add public${offlineUrl}, or pass offline: false (\`worker: { offline: false }\` in alxia()), or pass the app's contract (\`worker: { contract }\` in alxia()).`);
|
|
73
|
+
}
|
|
74
|
+
offline = { url: offlineUrl, revision: revisionOf(await readFile(page)) };
|
|
75
|
+
}
|
|
76
|
+
const precache = workerPrecache({
|
|
77
|
+
manifestSource: await readFile(join(assets, manifest), "utf8"),
|
|
78
|
+
assetFiles: files,
|
|
79
|
+
offline,
|
|
80
|
+
include: options.precacheAssets
|
|
81
|
+
});
|
|
82
|
+
const out = urlInClient(client, workerUrl);
|
|
83
|
+
const result = await Bun.build({
|
|
84
|
+
entrypoints: [source],
|
|
85
|
+
outdir: dirname(out),
|
|
86
|
+
naming: basename(out),
|
|
87
|
+
target: "browser",
|
|
88
|
+
format: "iife",
|
|
89
|
+
minify: options.minify ?? true,
|
|
90
|
+
define: {
|
|
91
|
+
"process.env.NODE_ENV": '"production"',
|
|
92
|
+
[PRECACHE_DEFINE]: JSON.stringify(precache)
|
|
93
|
+
}
|
|
94
|
+
}).catch((error) => {
|
|
95
|
+
const detail = error instanceof AggregateError ? error.errors.map(String).join(`
|
|
96
|
+
`) : String(error);
|
|
97
|
+
throw new Error(`buildWorker(): Bun.build failed:
|
|
98
|
+
${detail}`);
|
|
99
|
+
});
|
|
100
|
+
if (!result.success) {
|
|
101
|
+
throw new Error(`buildWorker(): Bun.build failed:
|
|
102
|
+
${result.logs.map(String).join(`
|
|
103
|
+
`)}`);
|
|
104
|
+
}
|
|
105
|
+
let precacheBytes = 0;
|
|
106
|
+
for (const { url } of precache) {
|
|
107
|
+
precacheBytes += (await stat(join(client, url))).size;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
file: out,
|
|
111
|
+
precache,
|
|
112
|
+
precacheBytes,
|
|
113
|
+
bytes: (await stat(out)).size
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export { buildWorker2 };
|
|
117
|
+
|
|
118
|
+
//# debugId=763F0D0F0869B4B964756E2164756E21
|
|
119
|
+
//# sourceMappingURL=index-mppz0806.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pwa/build/build-worker.ts", "../src/pwa/build/precache.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { existsSync } from 'node:fs';\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { basename, dirname, join, resolve, sep } from 'node:path';\nimport type { PrecacheEntry } from '../model/precache-list';\nimport {\n\tOFFLINE_URL,\n\tSERVICE_WORKER_URL,\n\ttype WorkerContract,\n} from '../model/worker-contract';\nimport { PRECACHE_DEFINE } from '../worker/injected';\nimport { browserManifestFile, revisionOf, workerPrecache } from './precache';\n\nexport interface BuildWorkerOptions {\n\t/**\n\t * The worker's source, relative to `root`: `app/service-worker.ts`. Named\n\t * `file`, not `entry`: `reveal` reads `alxia({ entry })`.\n\t */\n\tfile: string;\n\t/** `process.cwd()` by default. */\n\troot?: string;\n\t/** The client build, relative to `root`: `build/client` by default. */\n\tclient?: string;\n\t/** Where the worker is written and which offline page is precached. */\n\tcontract?: Pick<WorkerContract, 'serviceWorkerUrl' | 'offlineUrl'>;\n\t/** Precache the offline page, which must then exist. `true` by default. */\n\toffline?: boolean;\n\t/** More files of `assets/` to precache, such as fonts: the app's regex. */\n\tprecacheAssets?: RegExp;\n\t/** `true` by default. */\n\tminify?: boolean;\n}\n\nexport interface WorkerBuild {\n\t/** The absolute path of the worker written. */\n\tfile: string;\n\tprecache: PrecacheEntry[];\n\t/** The precached files' size, before compression. */\n\tprecacheBytes: number;\n\t/** The worker's own size. */\n\tbytes: number;\n}\n\n/** Not `requireBun` of `vite/`: `/pwa/build` imports Node's built-ins only. */\nexport function assertBun(\n\tonBun = (globalThis as { Bun?: unknown }).Bun !== undefined,\n): void {\n\tif (onBun) return;\n\tthrow new Error(\n\t\t'buildWorker() runs on Bun: it is running on Node. Run it with bun, or run react-router build with bun --bun.',\n\t);\n}\n\n/**\n * The file a root-relative URL names inside `client`. It fails closed: a URL\n * that is absolute, protocol-relative, not rooted at `/`, or that resolves to\n * `client` itself or outside it, is refused.\n */\nfunction urlInClient(client: string, url: string): string {\n\tconst path = join(client, url);\n\tif (\n\t\t!url.startsWith('/') ||\n\t\t/^[a-z][a-z0-9+.-]*:|^\\/\\//i.test(url) ||\n\t\t!path.startsWith(client + sep) ||\n\t\tpath.endsWith(sep)\n\t) {\n\t\tthrow new Error(`buildWorker(): ${url} is outside ${client}.`);\n\t}\n\treturn path;\n}\n\n/**\n * Bundles the worker (Workbox included) into the client build, after\n * `react-router build`, with the precache list written in: the offline page\n * by its content, every route's code and `precacheAssets`. A new build\n * changes the list, so the worker's bytes, so the browser installs it.\n * Runs on Bun; the only imports of this module are Node's built-ins.\n */\nexport async function buildWorker(\n\toptions: BuildWorkerOptions,\n): Promise<WorkerBuild> {\n\tassertBun();\n\tconst root = resolve(options.root ?? process.cwd());\n\tconst client = resolve(root, options.client ?? 'build/client');\n\tconst source = resolve(root, options.file);\n\tconst workerUrl = options.contract?.serviceWorkerUrl ?? SERVICE_WORKER_URL;\n\tconst offlineUrl = options.contract?.offlineUrl ?? OFFLINE_URL;\n\tif (!existsSync(source)) {\n\t\tthrow new Error(`buildWorker(): ${options.file} does not exist.`);\n\t}\n\tconst assets = join(client, 'assets');\n\tconst files = await readdir(assets).catch(() => []);\n\tconst manifest = browserManifestFile(files);\n\tlet offline: PrecacheEntry | undefined;\n\tif (options.offline !== false) {\n\t\tconst page = urlInClient(client, offlineUrl);\n\t\tif (!existsSync(page)) {\n\t\t\tthrow new Error(\n\t\t\t\t`buildWorker(): ${options.client ?? 'build/client'}${offlineUrl} does not exist. Add public${offlineUrl}, or pass offline: false (\\`worker: { offline: false }\\` in alxia()), or pass the app's contract (\\`worker: { contract }\\` in alxia()).`,\n\t\t\t);\n\t\t}\n\t\toffline = { url: offlineUrl, revision: revisionOf(await readFile(page)) };\n\t}\n\tconst precache = workerPrecache({\n\t\tmanifestSource: await readFile(join(assets, manifest), 'utf8'),\n\t\tassetFiles: files,\n\t\toffline,\n\t\tinclude: options.precacheAssets,\n\t});\n\tconst out = urlInClient(client, workerUrl);\n\tconst result = await Bun.build({\n\t\tentrypoints: [source],\n\t\toutdir: dirname(out),\n\t\tnaming: basename(out),\n\t\ttarget: 'browser',\n\t\tformat: 'iife',\n\t\tminify: options.minify ?? true,\n\t\tdefine: {\n\t\t\t// Workbox's production build: no logging, no dev assertions.\n\t\t\t'process.env.NODE_ENV': '\"production\"',\n\t\t\t[PRECACHE_DEFINE]: JSON.stringify(precache),\n\t\t},\n\t}).catch((error: unknown) => {\n\t\tconst detail =\n\t\t\terror instanceof AggregateError\n\t\t\t\t? error.errors.map(String).join('\\n')\n\t\t\t\t: String(error);\n\t\tthrow new Error(`buildWorker(): Bun.build failed:\\n${detail}`);\n\t});\n\tif (!result.success) {\n\t\tthrow new Error(\n\t\t\t`buildWorker(): Bun.build failed:\\n${result.logs.map(String).join('\\n')}`,\n\t\t);\n\t}\n\tlet precacheBytes = 0;\n\tfor (const { url } of precache) {\n\t\tprecacheBytes += (await stat(join(client, url))).size;\n\t}\n\treturn {\n\t\tfile: out,\n\t\tprecache,\n\t\tprecacheBytes,\n\t\tbytes: (await stat(out)).size,\n\t};\n}\n",
|
|
6
|
+
"import { createHash } from 'node:crypto';\nimport {\n\tassetPrecache,\n\ttype PrecacheEntry,\n\tparseBrowserManifest,\n\tprecacheList,\n} from '../model/precache-list';\n\n/** The one React Router browser manifest among the names in build/client/assets. */\nexport function browserManifestFile(files: readonly string[]): string {\n\tconst found = files.filter((file) => /^manifest-[\\w-]+\\.js$/.test(file));\n\tconst [only] = found;\n\tif (found.length !== 1 || only === undefined) {\n\t\tthrow new Error(\n\t\t\t`buildWorker(): expected one React Router browser manifest, build/client/assets/manifest-*.js, found ${found.length}. Run react-router build first.`,\n\t\t);\n\t}\n\treturn only;\n}\n\n/** A file's revision: the first 16 hex characters of its sha256. */\nexport function revisionOf(bytes: Uint8Array): string {\n\treturn createHash('sha256').update(bytes).digest('hex').slice(0, 16);\n}\n\n/**\n * The worker's precache list: the offline page (revisioned by its content),\n * every route's code, then the asset files `include` matches. Each URL once,\n * the first kept.\n */\nexport function workerPrecache(input: {\n\tmanifestSource: string;\n\tassetFiles: readonly string[];\n\toffline?: PrecacheEntry | undefined;\n\tinclude?: RegExp | undefined;\n}): PrecacheEntry[] {\n\tconst all = [\n\t\t...(input.offline ? [input.offline] : []),\n\t\t...precacheList(parseBrowserManifest(input.manifestSource)),\n\t\t...(input.include ? assetPrecache(input.assetFiles, input.include) : []),\n\t];\n\tconst seen = new Set<string>();\n\treturn all.filter(({ url }) => !seen.has(url) && seen.add(url));\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": ";;;;;;;;;;;;;;AAAA;AACA;AACA;;;ACFA;AASO,SAAS,mBAAmB,CAAC,OAAkC;AAAA,EACrE,MAAM,QAAQ,MAAM,OAAO,CAAC,SAAS,wBAAwB,KAAK,IAAI,CAAC;AAAA,EACvE,OAAO,QAAQ;AAAA,EACf,IAAI,MAAM,WAAW,KAAK,SAAS,WAAW;AAAA,IAC7C,MAAM,IAAI,MACT,uGAAuG,MAAM,uCAC9G;AAAA,EACD;AAAA,EACA,OAAO;AAAA;AAID,SAAS,UAAU,CAAC,OAA2B;AAAA,EACrD,OAAO,WAAW,QAAQ,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAAA;AAQ7D,SAAS,cAAc,CAAC,OAKX;AAAA,EACnB,MAAM,MAAM;AAAA,IACX,GAAI,MAAM,UAAU,CAAC,MAAM,OAAO,IAAI,CAAC;AAAA,IACvC,GAAG,cAAa,sBAAqB,MAAM,cAAc,CAAC;AAAA,IAC1D,GAAI,MAAM,UAAU,eAAc,MAAM,YAAY,MAAM,OAAO,IAAI,CAAC;AAAA,EACvE;AAAA,EACA,MAAM,OAAO,IAAI;AAAA,EACjB,OAAO,IAAI,OAAO,GAAG,UAAU,CAAC,KAAK,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,CAAC;AAAA;;;ADCxD,SAAS,SAAS,CACxB,QAAS,WAAiC,QAAQ,WAC3C;AAAA,EACP,IAAI;AAAA,IAAO;AAAA,EACX,MAAM,IAAI,MACT,8GACD;AAAA;AAQD,SAAS,WAAW,CAAC,QAAgB,KAAqB;AAAA,EACzD,MAAM,OAAO,KAAK,QAAQ,GAAG;AAAA,EAC7B,IACC,CAAC,IAAI,WAAW,GAAG,KACnB,6BAA6B,KAAK,GAAG,KACrC,CAAC,KAAK,WAAW,SAAS,GAAG,KAC7B,KAAK,SAAS,GAAG,GAChB;AAAA,IACD,MAAM,IAAI,MAAM,kBAAkB,kBAAkB,SAAS;AAAA,EAC9D;AAAA,EACA,OAAO;AAAA;AAUR,eAAsB,YAAW,CAChC,SACuB;AAAA,EACvB,UAAU;AAAA,EACV,MAAM,OAAO,QAAQ,QAAQ,QAAQ,QAAQ,IAAI,CAAC;AAAA,EAClD,MAAM,SAAS,QAAQ,MAAM,QAAQ,UAAU,cAAc;AAAA,EAC7D,MAAM,SAAS,QAAQ,MAAM,QAAQ,IAAI;AAAA,EACzC,MAAM,YAAY,QAAQ,UAAU,oBAAoB;AAAA,EACxD,MAAM,aAAa,QAAQ,UAAU,cAAc;AAAA,EACnD,IAAI,CAAC,WAAW,MAAM,GAAG;AAAA,IACxB,MAAM,IAAI,MAAM,kBAAkB,QAAQ,sBAAsB;AAAA,EACjE;AAAA,EACA,MAAM,SAAS,KAAK,QAAQ,QAAQ;AAAA,EACpC,MAAM,QAAQ,MAAM,QAAQ,MAAM,EAAE,MAAM,MAAM,CAAC,CAAC;AAAA,EAClD,MAAM,WAAW,oBAAoB,KAAK;AAAA,EAC1C,IAAI;AAAA,EACJ,IAAI,QAAQ,YAAY,OAAO;AAAA,IAC9B,MAAM,OAAO,YAAY,QAAQ,UAAU;AAAA,IAC3C,IAAI,CAAC,WAAW,IAAI,GAAG;AAAA,MACtB,MAAM,IAAI,MACT,kBAAkB,QAAQ,UAAU,iBAAiB,wCAAwC,mJAC9F;AAAA,IACD;AAAA,IACA,UAAU,EAAE,KAAK,YAAY,UAAU,WAAW,MAAM,SAAS,IAAI,CAAC,EAAE;AAAA,EACzE;AAAA,EACA,MAAM,WAAW,eAAe;AAAA,IAC/B,gBAAgB,MAAM,SAAS,KAAK,QAAQ,QAAQ,GAAG,MAAM;AAAA,IAC7D,YAAY;AAAA,IACZ;AAAA,IACA,SAAS,QAAQ;AAAA,EAClB,CAAC;AAAA,EACD,MAAM,MAAM,YAAY,QAAQ,SAAS;AAAA,EACzC,MAAM,SAAS,MAAM,IAAI,MAAM;AAAA,IAC9B,aAAa,CAAC,MAAM;AAAA,IACpB,QAAQ,QAAQ,GAAG;AAAA,IACnB,QAAQ,SAAS,GAAG;AAAA,IACpB,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ,QAAQ,UAAU;AAAA,IAC1B,QAAQ;AAAA,MAEP,wBAAwB;AAAA,OACvB,kBAAkB,KAAK,UAAU,QAAQ;AAAA,IAC3C;AAAA,EACD,CAAC,EAAE,MAAM,CAAC,UAAmB;AAAA,IAC5B,MAAM,SACL,iBAAiB,iBACd,MAAM,OAAO,IAAI,MAAM,EAAE,KAAK;AAAA,CAAI,IAClC,OAAO,KAAK;AAAA,IAChB,MAAM,IAAI,MAAM;AAAA,EAAqC,QAAQ;AAAA,GAC7D;AAAA,EACD,IAAI,CAAC,OAAO,SAAS;AAAA,IACpB,MAAM,IAAI,MACT;AAAA,EAAqC,OAAO,KAAK,IAAI,MAAM,EAAE,KAAK;AAAA,CAAI,GACvE;AAAA,EACD;AAAA,EACA,IAAI,gBAAgB;AAAA,EACpB,aAAa,SAAS,UAAU;AAAA,IAC/B,kBAAkB,MAAM,KAAK,KAAK,QAAQ,GAAG,CAAC,GAAG;AAAA,EAClD;AAAA,EACA,OAAO;AAAA,IACN,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA,QAAQ,MAAM,KAAK,GAAG,GAAG;AAAA,EAC1B;AAAA;",
|
|
9
|
+
"debugId": "763F0D0F0869B4B964756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/pwa/model/worker-contract.ts
|
|
2
|
+
var SERVICE_WORKER_URL2 = "/service-worker.js";
|
|
3
|
+
var OFFLINE_URL2 = "/offline.html";
|
|
4
|
+
var SKIP_WAITING2 = "SKIP_WAITING";
|
|
5
|
+
function defineWorkerContract2(options) {
|
|
6
|
+
const { cachePrefix } = options;
|
|
7
|
+
if (typeof cachePrefix !== "string" || cachePrefix === "") {
|
|
8
|
+
throw new TypeError(`defineWorkerContract(): cachePrefix must be a non-empty name, such as 'my-app'; got '${String(cachePrefix)}'.`);
|
|
9
|
+
}
|
|
10
|
+
if (cachePrefix.endsWith("-")) {
|
|
11
|
+
throw new TypeError(`defineWorkerContract(): cachePrefix must not end with '-', which the contract adds; got '${cachePrefix}'. Use '${cachePrefix.replace(/-+$/, "")}'.`);
|
|
12
|
+
}
|
|
13
|
+
const cacheVersion = options.cacheVersion ?? "v1";
|
|
14
|
+
if (typeof cacheVersion !== "string" || cacheVersion === "" || cacheVersion.includes("-")) {
|
|
15
|
+
throw new TypeError(`defineWorkerContract(): cacheVersion must be a non-empty name without '-', such as 'v2'; got '${String(cacheVersion)}'.`);
|
|
16
|
+
}
|
|
17
|
+
const cacheNames = Object.freeze({
|
|
18
|
+
precache: `${cachePrefix}-precache-${cacheVersion}`,
|
|
19
|
+
assets: `${cachePrefix}-assets-${cacheVersion}`,
|
|
20
|
+
static: `${cachePrefix}-static-${cacheVersion}`
|
|
21
|
+
});
|
|
22
|
+
const current = new Set(Object.values(cacheNames));
|
|
23
|
+
const shape = new RegExp(`^${cachePrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}-[a-z0-9]+-[^-]+$`);
|
|
24
|
+
const owned = (name) => shape.test(name);
|
|
25
|
+
const contract = {
|
|
26
|
+
cachePrefix,
|
|
27
|
+
cacheVersion,
|
|
28
|
+
serviceWorkerUrl: options.serviceWorkerUrl ?? SERVICE_WORKER_URL2,
|
|
29
|
+
offlineUrl: options.offlineUrl ?? OFFLINE_URL2,
|
|
30
|
+
cacheNames,
|
|
31
|
+
obsoleteCaches: (names) => names.filter((name) => owned(name) && !current.has(name)),
|
|
32
|
+
clearableCaches: (names) => names.filter((name) => owned(name) && name !== cacheNames.precache)
|
|
33
|
+
};
|
|
34
|
+
return Object.freeze(contract);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { SERVICE_WORKER_URL2, OFFLINE_URL2, SKIP_WAITING2, defineWorkerContract2 };
|
|
38
|
+
|
|
39
|
+
//# debugId=5CED7E20833AD98364756E2164756E21
|
|
40
|
+
//# sourceMappingURL=index-rfr02vg4.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pwa/model/worker-contract.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
|
+
],
|
|
7
|
+
"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;",
|
|
8
|
+
"debugId": "5CED7E20833AD98364756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|