@owlmeans/client 0.1.18-rc.40 → 0.1.18-rc.41
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 +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/client/SKILL.md +91 -3
- package/build/consts.d.ts +8 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +8 -0
- package/build/consts.js.map +1 -1
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/lazy-retry.d.ts +47 -0
- package/build/lazy-retry.d.ts.map +1 -0
- package/build/lazy-retry.js +128 -0
- package/build/lazy-retry.js.map +1 -0
- package/build/lazy.d.ts +32 -0
- package/build/lazy.d.ts.map +1 -0
- package/build/lazy.js +114 -0
- package/build/lazy.js.map +1 -0
- package/build/types.d.ts +69 -2
- package/build/types.d.ts.map +1 -1
- package/package.json +19 -12
- package/src/consts.ts +10 -0
- package/src/index.ts +2 -0
- package/src/lazy-retry.ts +141 -0
- package/src/lazy.tsx +161 -0
- package/src/types.ts +77 -2
- package/tests/context.ts +33 -0
- package/tests/harness/index.html +11 -0
- package/tests/harness/mount.tsx +86 -0
- package/tests/harness/piece.tsx +4 -0
- package/tests/lazy-retry.spec.ts +199 -0
- package/tests/lazy.spec.ts +145 -0
- package/tsconfig.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ imports everything from here. Server code never uses this package; its equivalen
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
bun add @owlmeans/client@^0.1.18-rc.
|
|
17
|
+
bun add @owlmeans/client@^0.1.18-rc.41
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
`react` and `@remix-run/router` are peer dependencies.
|
|
@@ -273,7 +273,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
273
273
|
your project's skill store (`.agents/skills/`):
|
|
274
274
|
|
|
275
275
|
```sh
|
|
276
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
276
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.39
|
|
277
277
|
```
|
|
278
278
|
|
|
279
279
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/client",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.41",
|
|
5
|
+
"generatedAt": "2026-09-24T14:00:35.038Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: client
|
|
3
|
-
description: How to use @owlmeans/client — the platform-agnostic React client framework (web and native) — makeClientContext, App/Router, useNavigate/Navigator, useEntrypoint/RoutedComponent, useStoreModel/useStoreList, useValue, the modal and debug services. Auto-invoked when importing client framework primitives, navigating between screens, or reading client state from React.
|
|
3
|
+
description: How to use @owlmeans/client — the platform-agnostic React client framework (web and native) — makeClientContext, App/Router, useNavigate/Navigator, useEntrypoint/RoutedComponent, useStoreModel/useStoreList, useValue, lazyComponent/lazyHandler code-splitting and chunk-failure recovery (retryImport, isChunkLoadError, recoverFromChunkError), the modal and debug services. Auto-invoked when importing client framework primitives, navigating between screens, or reading client state from React.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/client
|
|
9
9
|
|
|
10
10
|
**Layer:** Client
|
|
11
|
-
**Install:** `"@owlmeans/client": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/client": "^0.1.18-rc.41"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
The React substrate `@owlmeans/web-client` (browser) and the native equivalent are built on. A
|
|
14
14
|
cross-platform package imports from here; an application normally imports from the platform
|
|
@@ -36,6 +36,9 @@ package, which re-exports what it needs — **except the hooks below, which are
|
|
|
36
36
|
| `useEntrypoint<T>()` | The `EntrypointContextParams` of the screen currently rendering — `{ alias, path, params, context }` |
|
|
37
37
|
| `RoutedComponent<Extra>` | Type of a component bound to a frontend protocol |
|
|
38
38
|
| `handler(Component, preprender?)` | Wrap a React component as an entrypoint handler |
|
|
39
|
+
| `lazyComponent(load, exportName, opts?)` / `lazyHandler(load, exportName, opts?)` | A code-split component with a static `.preload()`; and `handler(lazyComponent(...))` with `.preload` carried through. Types `LazyComponent`, `LazyHandler`, `LazyComponentOptions`, `LazyErrorRenderer` — see Code-splitting |
|
|
40
|
+
| `retryImport(load, opts?)` / `isChunkLoadError(error)` | Run a dynamic `import()` again while it fails to FETCH; tell a fetch failure from a module that loaded and broke. `RetryImportOptions` — see Chunk failures |
|
|
41
|
+
| `reloadOnce(key, windowMs)` / `recoverFromChunkError()` | The guarded page reload, and the one every chunk-failure path in a tab shares — see Chunk failures |
|
|
39
42
|
| `useStoreModel` / `useStoreList` | React hooks over a `@owlmeans/state` resource — one record by id, or a live query |
|
|
40
43
|
| `useValue(loader, deps?, forceDefault?)` / `UseValueParams<T>` | Render an async result. The second argument is the **dependency list**, not a default — see Async values |
|
|
41
44
|
| `useToggle(opened?)` / `Toggleable` | An open/close/toggle handle, which is what a modal surface binds to |
|
|
@@ -43,7 +46,7 @@ package, which re-exports what it needs — **except the hooks below, which are
|
|
|
43
46
|
| `ModalBodyProps` / `useSetupModalNavigator()` | `{ modal?: ModalService }` — the props a modal body is rendered with; and the hook that lets a body navigate |
|
|
44
47
|
| `appendDebugService` / `createDebugService` / `appendStateDebug(ctx, alias)` / `DebugService` | The debug menu — `context.debug()` |
|
|
45
48
|
| `ClientError`, `ComponentError`, `ComponentPropError`, `ComponentPropUndefined` | The client error family, registered with `ResilientError` |
|
|
46
|
-
| `DEF_MODAL_ALIAS` (`modal`), `DEF_DEBUG_ALIAS` (`debug`), `DEBUGGER_FLAG` / `DEBUG_CONFIG_KEY` (`debugger`) | Constants |
|
|
49
|
+
| `DEF_MODAL_ALIAS` (`modal`), `DEF_DEBUG_ALIAS` (`debug`), `DEBUGGER_FLAG` / `DEBUG_CONFIG_KEY` (`debugger`), `DEF_IMPORT_RETRY_ATTEMPTS` (2), `DEF_IMPORT_RETRY_DELAYS_MS` (500, 1500), `CHUNK_RELOAD_KEY` (`owlmeans:chunk-reload`), `CHUNK_RELOAD_WINDOW_MS` (60 s) | Constants |
|
|
47
50
|
|
|
48
51
|
## Subpath Exports
|
|
49
52
|
|
|
@@ -94,6 +97,91 @@ A screen's guards are its own plus every ancestor's, taken from `getGuards()`. A
|
|
|
94
97
|
open screen; when the list is non-empty and no guard matches, the renderer throws
|
|
95
98
|
`AuthorizationError('frontend-guard')`.
|
|
96
99
|
|
|
100
|
+
## Code-splitting a screen or component
|
|
101
|
+
|
|
102
|
+
`lazyComponent(load, exportName, opts?)` turns a dynamic `import()` into a component whose chunk
|
|
103
|
+
loads on first render, with the `Suspense` boundary INSIDE it — the fallback replaces only this
|
|
104
|
+
component and the layout around it stays mounted. `lazyHandler` is `handler(lazyComponent(...))`
|
|
105
|
+
with `.preload` carried through, so it binds exactly like `handler(Component)`. Both are
|
|
106
|
+
re-exported by `@owlmeans/web-client` and `@owlmeans/web-panel` next to `handler`; the
|
|
107
|
+
chunk-failure tools below by `@owlmeans/web-client`.
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { lazyComponent, lazyHandler } from '@owlmeans/client'
|
|
111
|
+
|
|
112
|
+
// Module scope — never inside a render, a hook or an entrypoint handler factory.
|
|
113
|
+
export const reportsScreen = lazyHandler(
|
|
114
|
+
() => import('./screens/reports.js'), 'ReportsScreen', { fallback: <Spinner /> }
|
|
115
|
+
)
|
|
116
|
+
const Chart = lazyComponent(() => import('./chart.js'), 'Chart', {
|
|
117
|
+
fallback: props => <Skeleton height={props.height} />,
|
|
118
|
+
error: (props, error, retry) => <ChartUnavailable onRetry={retry} />,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// Prefetch on intent: the screen then renders without its fallback.
|
|
122
|
+
<a onMouseEnter={() => void reportsScreen.preload()} onFocus={() => void reportsScreen.preload()}>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- **Module scope only.** The route renderer (`utils/route.tsx`) wraps the resolved screen in a
|
|
126
|
+
fresh `memo(...)` on every render, so the route subtree remounts on each navigation. A lazy
|
|
127
|
+
object made at module scope is already resolved by then and renders synchronously; one created
|
|
128
|
+
during a render or inside a handler factory is a new `React.lazy` each time and re-suspends — the
|
|
129
|
+
fallback flashes on every visit.
|
|
130
|
+
- **`preload()`** starts or joins the load and resolves to the component. Once loaded, every later
|
|
131
|
+
render resolves in the same tick — no re-suspend.
|
|
132
|
+
- **`fallback`** is a node or `fallback(props)`. **`error`** is a node or a `LazyErrorRenderer`
|
|
133
|
+
`(props, error, retry) => ReactNode`; `retry()` resets the piece's boundary and renders the
|
|
134
|
+
recreated lazy, which loads the chunk again.
|
|
135
|
+
- **`retry`** — the load runs through `retryImport` by default; pass `RetryImportOptions` to tune
|
|
136
|
+
it or `false` to load once.
|
|
137
|
+
- **An `exportName` the module does not export** rejects with a `SyntaxError` — never retried.
|
|
138
|
+
|
|
139
|
+
### Chunk failures
|
|
140
|
+
|
|
141
|
+
A lazy piece ALWAYS carries its own error boundary, so a failed chunk never unmounts what is around
|
|
142
|
+
it:
|
|
143
|
+
|
|
144
|
+
| The piece fails with | `error` given | `error` omitted |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| a chunk-load failure (`isChunkLoadError`), after `retryImport` gave up | with `reload` (default for `lazyHandler`): the guarded reload starts, `fallback` stays, and `error` renders once the guard refuses; without it: `error` renders in place | `recoverFromChunkError()` starts the guarded reload; `fallback` stays in place |
|
|
147
|
+
| anything else (a module that loaded and broke, its own render) | `error` renders in place | propagates to the nearest boundary above, as if the piece had none |
|
|
148
|
+
|
|
149
|
+
Give every piece a deliberate `error`: a leaf that has a plain rendering of the same content (a
|
|
150
|
+
formatter, a highlighter) degrades to it; anything else shows a notice with a retry. A whole screen
|
|
151
|
+
(`lazyHandler`) reloads once before its notice (`reload: true` by default) — it has nothing to
|
|
152
|
+
degrade to.
|
|
153
|
+
|
|
154
|
+
- **Retry scope.** `retryImport` covers a TRANSIENT fetch failure — a blip, an edge answering 404
|
|
155
|
+
or 5xx for a moment. Chromium keeps a failed module fetch for the document's lifetime and rejects
|
|
156
|
+
every later `import()` of that URL at once, so a retry imports the URL the error names with a
|
|
157
|
+
fresh `t` parameter (`chunkUrlOf` + `cacheBustedUrl`, `bustCache` on by default; same-origin
|
|
158
|
+
http(s) URLs only): a new URL, fetched again. That recovers a built chunk. It cannot recover a
|
|
159
|
+
DEV-served module: React Fast Refresh makes every module import itself by its own URL, so the
|
|
160
|
+
busted copy depends on the remembered failure — only a new document loads it, which is what
|
|
161
|
+
`reload` and the guarded reload are for. Safari names no URL; its retries repeat `load`.
|
|
162
|
+
- **A failed load stays failed for the instance that saw it** until its `retry()`: React re-renders
|
|
163
|
+
that instance while recovering from the error, and a fresh load there would suspend again
|
|
164
|
+
forever. A NEW mount — a navigation back, another place in the tree — takes the recreated lazy and
|
|
165
|
+
loads again; so does `preload()`.
|
|
166
|
+
- **`isChunkLoadError(error)`** is true for a browser's failed dynamic import (Chromium "Failed to
|
|
167
|
+
fetch dynamically imported module", Safari "Importing a module script failed", Firefox "error
|
|
168
|
+
loading dynamically imported module"), Vite's "Unable to preload CSS", webpack's `ChunkLoadError`,
|
|
169
|
+
a `vite:preloadError` event, and an element's `error` event. It is false for a `SyntaxError`
|
|
170
|
+
about a missing export and for a throw while the module evaluated — loading those again changes
|
|
171
|
+
nothing.
|
|
172
|
+
- **`retryImport(load, opts?)`** runs `load` again while `shouldRetry(error)` (default
|
|
173
|
+
`isChunkLoadError`) holds, `attempts` (2) more times at most, pausing `delaysMs[i]` before retry
|
|
174
|
+
`i` (500 ms, 1500 ms; the last entry repeats), and rethrows the last failure.
|
|
175
|
+
- **`reloadOnce(key, windowMs)`** reloads the page at most once per `windowMs` per tab, keeping the
|
|
176
|
+
time in `sessionStorage` under `key`; never while offline and never without storage (with no
|
|
177
|
+
guard kept, a failure that survives the reload would reload forever); a platform with no page to
|
|
178
|
+
reload does nothing. It answers whether a reload started.
|
|
179
|
+
- **`recoverFromChunkError()`** is `reloadOnce(CHUNK_RELOAD_KEY, CHUNK_RELOAD_WINDOW_MS)` — the ONE
|
|
180
|
+
guard a tab shares. An application that also reloads on `vite:preloadError` calls it rather than
|
|
181
|
+
keeping a guard of its own, so one failure never reloads twice.
|
|
182
|
+
|
|
183
|
+
Source of truth: `src/lazy.tsx` and `src/lazy-retry.ts` in this package.
|
|
184
|
+
|
|
97
185
|
## Client state
|
|
98
186
|
|
|
99
187
|
State lives on the context as a `@owlmeans/state` resource; these hooks subscribe to it.
|
package/build/consts.d.ts
CHANGED
|
@@ -2,4 +2,12 @@ export declare const DEF_MODAL_ALIAS = "modal";
|
|
|
2
2
|
export declare const DEF_DEBUG_ALIAS = "debug";
|
|
3
3
|
export declare const DEBUGGER_FLAG = "debugger";
|
|
4
4
|
export declare const DEBUG_CONFIG_KEY = "debugger";
|
|
5
|
+
/** How many times `retryImport` loads a chunk again after its first failure. */
|
|
6
|
+
export declare const DEF_IMPORT_RETRY_ATTEMPTS = 2;
|
|
7
|
+
/** The pause before each retry, in order; the last one repeats. */
|
|
8
|
+
export declare const DEF_IMPORT_RETRY_DELAYS_MS: readonly number[];
|
|
9
|
+
/** The `sessionStorage` key `recoverFromChunkError` keeps the time of its last reload under. */
|
|
10
|
+
export declare const CHUNK_RELOAD_KEY = "owlmeans:chunk-reload";
|
|
11
|
+
/** At most one chunk-recovery reload per this window, per tab. */
|
|
12
|
+
export declare const CHUNK_RELOAD_WINDOW_MS = 60000;
|
|
5
13
|
//# sourceMappingURL=consts.d.ts.map
|
package/build/consts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,eAAe,UAAU,CAAA;AACtC,eAAO,MAAM,eAAe,UAAU,CAAA;AAEtC,eAAO,MAAM,aAAa,aAAa,CAAA;AACvC,eAAO,MAAM,gBAAgB,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,eAAe,UAAU,CAAA;AACtC,eAAO,MAAM,eAAe,UAAU,CAAA;AAEtC,eAAO,MAAM,aAAa,aAAa,CAAA;AACvC,eAAO,MAAM,gBAAgB,aAAa,CAAA;AAE1C,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,IAAI,CAAA;AAC1C,mEAAmE;AACnE,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAAgB,CAAA;AAExE,gGAAgG;AAChG,eAAO,MAAM,gBAAgB,0BAA0B,CAAA;AACvD,kEAAkE;AAClE,eAAO,MAAM,sBAAsB,QAAS,CAAA"}
|
package/build/consts.js
CHANGED
|
@@ -2,4 +2,12 @@ export const DEF_MODAL_ALIAS = 'modal';
|
|
|
2
2
|
export const DEF_DEBUG_ALIAS = 'debug';
|
|
3
3
|
export const DEBUGGER_FLAG = 'debugger';
|
|
4
4
|
export const DEBUG_CONFIG_KEY = 'debugger';
|
|
5
|
+
/** How many times `retryImport` loads a chunk again after its first failure. */
|
|
6
|
+
export const DEF_IMPORT_RETRY_ATTEMPTS = 2;
|
|
7
|
+
/** The pause before each retry, in order; the last one repeats. */
|
|
8
|
+
export const DEF_IMPORT_RETRY_DELAYS_MS = [500, 1500];
|
|
9
|
+
/** The `sessionStorage` key `recoverFromChunkError` keeps the time of its last reload under. */
|
|
10
|
+
export const CHUNK_RELOAD_KEY = 'owlmeans:chunk-reload';
|
|
11
|
+
/** At most one chunk-recovery reload per this window, per tab. */
|
|
12
|
+
export const CHUNK_RELOAD_WINDOW_MS = 60_000;
|
|
5
13
|
//# sourceMappingURL=consts.js.map
|
package/build/consts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAA;AACtC,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAA;AAEtC,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAA;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAA"}
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAA;AACtC,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAA;AAEtC,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAA;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAA;AAE1C,gFAAgF;AAChF,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAA;AAC1C,mEAAmE;AACnE,MAAM,CAAC,MAAM,0BAA0B,GAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;AAExE,gGAAgG;AAChG,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAA;AACvD,kEAAkE;AAClE,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAA"}
|
package/build/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export * from './context.js';
|
|
|
2
2
|
export * from './components/index.js';
|
|
3
3
|
export * from './services/index.js';
|
|
4
4
|
export * from './helper.js';
|
|
5
|
+
export * from './lazy.js';
|
|
6
|
+
export * from './lazy-retry.js';
|
|
5
7
|
export * from './navigate.js';
|
|
6
8
|
export * from './entrypoint.js';
|
|
7
9
|
export * from './router.js';
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
|
package/build/index.js
CHANGED
|
@@ -2,6 +2,8 @@ export * from './context.js';
|
|
|
2
2
|
export * from './components/index.js';
|
|
3
3
|
export * from './services/index.js';
|
|
4
4
|
export * from './helper.js';
|
|
5
|
+
export * from './lazy.js';
|
|
6
|
+
export * from './lazy-retry.js';
|
|
5
7
|
export * from './navigate.js';
|
|
6
8
|
export * from './entrypoint.js';
|
|
7
9
|
export * from './router.js';
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { RetryImportOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether `error` is a chunk that could not be FETCHED: a browser's failed dynamic import, Vite's
|
|
4
|
+
* stylesheet preload failure, webpack's `ChunkLoadError`, Vite's `vite:preloadError` event, or
|
|
5
|
+
* the `error` event of the element a loader fetched through. A module that was fetched and then
|
|
6
|
+
* failed — a missing export, a throw while it evaluated — is not one: loading it again changes
|
|
7
|
+
* nothing.
|
|
8
|
+
*/
|
|
9
|
+
export declare const isChunkLoadError: (error: unknown) => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The URL of the chunk a failed dynamic import tried to fetch, when the browser's error names it
|
|
12
|
+
* and it is an http(s) URL of this page's origin — never an arbitrary URL out of a message. `null`
|
|
13
|
+
* otherwise (Safari, a non-browser platform, another origin).
|
|
14
|
+
*/
|
|
15
|
+
export declare const chunkUrlOf: (error: unknown) => string | null;
|
|
16
|
+
/**
|
|
17
|
+
* `href` with a fresh `t` parameter: a URL the browser has not seen, so it fetches the module
|
|
18
|
+
* again. `t` is the parameter Vite's dev server already gives module URLs; a static host ignores it.
|
|
19
|
+
*/
|
|
20
|
+
export declare const cacheBustedUrl: (href: string, now?: number) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Run `load` — a dynamic `import()` — and, while it rejects with a chunk-load failure, run it again
|
|
23
|
+
* after a pause, `attempts` more times at most. Any other rejection is rethrown at once.
|
|
24
|
+
*
|
|
25
|
+
* This covers a TRANSIENT failure — a network blip, an edge answering 404 or 5xx for a moment.
|
|
26
|
+
* A browser remembers a failed module fetch for the document's lifetime (Chromium does) and
|
|
27
|
+
* rejects every later `import()` of that URL at once, so a retry imports the URL the error names
|
|
28
|
+
* with a cache-busting parameter instead (`bustCache`, default on): a new URL, fetched again. Where
|
|
29
|
+
* the error names no URL (Safari), `load` runs again as is; what always recovers is a new
|
|
30
|
+
* document, which `recoverFromChunkError` starts, once and guarded.
|
|
31
|
+
*/
|
|
32
|
+
export declare const retryImport: <M>(load: () => Promise<M>, opts?: RetryImportOptions) => Promise<M>;
|
|
33
|
+
/**
|
|
34
|
+
* Reload the page, at most once per `windowMs` in this tab — the time of the last reload is kept
|
|
35
|
+
* in `sessionStorage` under `key`. Never while offline (the reload would land on the browser's own
|
|
36
|
+
* error page), and never without storage: with nowhere to keep the guard, a failure that survives
|
|
37
|
+
* the reload would reload forever. A platform with no page to reload does nothing. Answers whether
|
|
38
|
+
* a reload was started.
|
|
39
|
+
*/
|
|
40
|
+
export declare const reloadOnce: (key: string, windowMs: number) => boolean;
|
|
41
|
+
/**
|
|
42
|
+
* The guarded reload a chunk that failed for good ends in: `reloadOnce` under one key every caller
|
|
43
|
+
* in the tab shares, so a lazy boundary and an application's own `vite:preloadError` listener
|
|
44
|
+
* never reload twice for one failure.
|
|
45
|
+
*/
|
|
46
|
+
export declare const recoverFromChunkError: () => boolean;
|
|
47
|
+
//# sourceMappingURL=lazy-retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy-retry.d.ts","sourceRoot":"","sources":["../src/lazy-retry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAQpD;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,UAAW,OAAO,KAAG,OAUjD,CAAA;AAOD;;;;GAIG;AACH,eAAO,MAAM,UAAU,UAAW,OAAO,KAAG,MAAM,GAAG,IAgBpD,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,SAAU,MAAM,QAAO,MAAM,KAAgB,MAIvE,CAAA;AAKD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,GAAU,CAAC,QAAQ,MAAM,OAAO,CAAC,CAAC,CAAC,SAAS,kBAAkB,KAAG,OAAO,CAAC,CAAC,CAsBjG,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,QAAS,MAAM,YAAY,MAAM,KAAG,OAoB1D,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,QAAO,OAA+D,CAAA"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { CHUNK_RELOAD_KEY, CHUNK_RELOAD_WINDOW_MS, DEF_IMPORT_RETRY_ATTEMPTS, DEF_IMPORT_RETRY_DELAYS_MS } from './consts.js';
|
|
2
|
+
/**
|
|
3
|
+
* What a browser says when a dynamic `import()` could not fetch its module — Chromium, Safari and
|
|
4
|
+
* Firefox, in that order — and what Vite's preload helper says when a chunk's stylesheet could not.
|
|
5
|
+
*/
|
|
6
|
+
const CHUNK_FAILURE = /failed to fetch dynamically imported module|importing a module script failed|error loading dynamically imported module|unable to preload css/i;
|
|
7
|
+
/**
|
|
8
|
+
* Whether `error` is a chunk that could not be FETCHED: a browser's failed dynamic import, Vite's
|
|
9
|
+
* stylesheet preload failure, webpack's `ChunkLoadError`, Vite's `vite:preloadError` event, or
|
|
10
|
+
* the `error` event of the element a loader fetched through. A module that was fetched and then
|
|
11
|
+
* failed — a missing export, a throw while it evaluated — is not one: loading it again changes
|
|
12
|
+
* nothing.
|
|
13
|
+
*/
|
|
14
|
+
export const isChunkLoadError = (error) => {
|
|
15
|
+
if (error == null || typeof error !== 'object') {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (typeof Event !== 'undefined' && error instanceof Event) {
|
|
19
|
+
return error.type === 'vite:preloadError' || error.type === 'error';
|
|
20
|
+
}
|
|
21
|
+
const { name, message } = error;
|
|
22
|
+
return name === 'ChunkLoadError' || (typeof message === 'string' && CHUNK_FAILURE.test(message));
|
|
23
|
+
};
|
|
24
|
+
const pause = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
25
|
+
/** The module URL a Chromium or Firefox import failure names; Safari's message names none. */
|
|
26
|
+
const FAILED_URL = /dynamically imported module:?\s+(\S+)/i;
|
|
27
|
+
/**
|
|
28
|
+
* The URL of the chunk a failed dynamic import tried to fetch, when the browser's error names it
|
|
29
|
+
* and it is an http(s) URL of this page's origin — never an arbitrary URL out of a message. `null`
|
|
30
|
+
* otherwise (Safari, a non-browser platform, another origin).
|
|
31
|
+
*/
|
|
32
|
+
export const chunkUrlOf = (error) => {
|
|
33
|
+
const message = error?.message;
|
|
34
|
+
const found = typeof message === 'string' ? FAILED_URL.exec(message)?.[1] : undefined;
|
|
35
|
+
if (found == null) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const url = new URL(found);
|
|
40
|
+
const origin = typeof location !== 'undefined' ? location.origin : undefined;
|
|
41
|
+
if ((url.protocol !== 'https:' && url.protocol !== 'http:') || (origin != null && url.origin !== origin)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return url.href;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* `href` with a fresh `t` parameter: a URL the browser has not seen, so it fetches the module
|
|
52
|
+
* again. `t` is the parameter Vite's dev server already gives module URLs; a static host ignores it.
|
|
53
|
+
*/
|
|
54
|
+
export const cacheBustedUrl = (href, now = Date.now()) => {
|
|
55
|
+
const url = new URL(href);
|
|
56
|
+
url.searchParams.set('t', String(now));
|
|
57
|
+
return url.href;
|
|
58
|
+
};
|
|
59
|
+
const importUrl = (url) => import(/* @vite-ignore */ /* webpackIgnore: true */ url);
|
|
60
|
+
/**
|
|
61
|
+
* Run `load` — a dynamic `import()` — and, while it rejects with a chunk-load failure, run it again
|
|
62
|
+
* after a pause, `attempts` more times at most. Any other rejection is rethrown at once.
|
|
63
|
+
*
|
|
64
|
+
* This covers a TRANSIENT failure — a network blip, an edge answering 404 or 5xx for a moment.
|
|
65
|
+
* A browser remembers a failed module fetch for the document's lifetime (Chromium does) and
|
|
66
|
+
* rejects every later `import()` of that URL at once, so a retry imports the URL the error names
|
|
67
|
+
* with a cache-busting parameter instead (`bustCache`, default on): a new URL, fetched again. Where
|
|
68
|
+
* the error names no URL (Safari), `load` runs again as is; what always recovers is a new
|
|
69
|
+
* document, which `recoverFromChunkError` starts, once and guarded.
|
|
70
|
+
*/
|
|
71
|
+
export const retryImport = async (load, opts) => {
|
|
72
|
+
const attempts = Math.max(0, opts?.attempts ?? DEF_IMPORT_RETRY_ATTEMPTS);
|
|
73
|
+
const delays = opts?.delaysMs ?? DEF_IMPORT_RETRY_DELAYS_MS;
|
|
74
|
+
const shouldRetry = opts?.shouldRetry ?? isChunkLoadError;
|
|
75
|
+
const bust = opts?.bustCache !== false;
|
|
76
|
+
const importBusted = opts?.importUrl ?? importUrl;
|
|
77
|
+
let next = load;
|
|
78
|
+
for (let attempt = 0;; attempt++) {
|
|
79
|
+
try {
|
|
80
|
+
return await next();
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (attempt >= attempts || !shouldRetry(error)) {
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
const url = bust ? chunkUrlOf(error) : null;
|
|
87
|
+
if (url != null) {
|
|
88
|
+
next = async () => await importBusted(cacheBustedUrl(url));
|
|
89
|
+
}
|
|
90
|
+
await pause(delays[Math.min(attempt, delays.length - 1)] ?? 0);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Reload the page, at most once per `windowMs` in this tab — the time of the last reload is kept
|
|
96
|
+
* in `sessionStorage` under `key`. Never while offline (the reload would land on the browser's own
|
|
97
|
+
* error page), and never without storage: with nowhere to keep the guard, a failure that survives
|
|
98
|
+
* the reload would reload forever. A platform with no page to reload does nothing. Answers whether
|
|
99
|
+
* a reload was started.
|
|
100
|
+
*/
|
|
101
|
+
export const reloadOnce = (key, windowMs) => {
|
|
102
|
+
const location = typeof window !== 'undefined' ? window.location : undefined;
|
|
103
|
+
if (typeof location?.reload !== 'function') {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
if (typeof navigator !== 'undefined' && navigator.onLine === false) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
if (now - Number(window.sessionStorage.getItem(key) ?? 0) < windowMs) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
window.sessionStorage.setItem(key, String(now));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
location.reload();
|
|
120
|
+
return true;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* The guarded reload a chunk that failed for good ends in: `reloadOnce` under one key every caller
|
|
124
|
+
* in the tab shares, so a lazy boundary and an application's own `vite:preloadError` listener
|
|
125
|
+
* never reload twice for one failure.
|
|
126
|
+
*/
|
|
127
|
+
export const recoverFromChunkError = () => reloadOnce(CHUNK_RELOAD_KEY, CHUNK_RELOAD_WINDOW_MS);
|
|
128
|
+
//# sourceMappingURL=lazy-retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy-retry.js","sourceRoot":"","sources":["../src/lazy-retry.ts"],"names":[],"mappings":"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"}
|
package/build/lazy.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ComponentExport, ExportProps, LazyComponent, LazyComponentOptions, LazyHandler } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wrap an async module import as a stable lazily-loaded component with a `.preload()`.
|
|
4
|
+
*
|
|
5
|
+
* MUST be called at module scope, never inside a render, a hook body or an entrypoint handler
|
|
6
|
+
* factory — the OwlMeans route renderer (`utils/route.tsx`) remounts the whole route subtree on
|
|
7
|
+
* every navigation (a fresh component identity per render), so a lazy object created during
|
|
8
|
+
* render would re-suspend on every navigation instead of rendering instantly once loaded.
|
|
9
|
+
*
|
|
10
|
+
* The `Suspense` boundary sits inside the returned component, so the fallback replaces only this
|
|
11
|
+
* component — the layout around it stays mounted while the chunk loads.
|
|
12
|
+
*
|
|
13
|
+
* Once loaded, later renders resolve synchronously (no re-suspend), so a component whose chunk
|
|
14
|
+
* was already preloaded never shows its fallback. A load that fails to fetch is retried in place
|
|
15
|
+
* (`retryImport`, tuned or turned off by `opts.retry`); one that still fails is never cached: the
|
|
16
|
+
* internal `React.lazy` is recreated, so the next mount, `retry()` or `preload()` loads for real.
|
|
17
|
+
*
|
|
18
|
+
* The component always carries its own error boundary, so a chunk that failed for good never
|
|
19
|
+
* unmounts what is around it: `opts.error` renders in its place, or — without one — the guarded
|
|
20
|
+
* reload (`recoverFromChunkError`) starts and the fallback stays. Any other error without
|
|
21
|
+
* `opts.error` goes on to the nearest boundary above, as if there were none.
|
|
22
|
+
*/
|
|
23
|
+
export declare const lazyComponent: <M, K extends ComponentExport<M>>(load: () => Promise<M>, exportName: K, opts?: LazyComponentOptions) => LazyComponent<ExportProps<M, K>>;
|
|
24
|
+
/**
|
|
25
|
+
* `handler(lazyComponent(...))`, carrying `.preload()` through — bind it to a screen exactly like
|
|
26
|
+
* `handler(Component)`. The same module-scope rule applies: call it where the entrypoints are
|
|
27
|
+
* declared, never inside a render. A screen whose chunk still fails after its retries starts the
|
|
28
|
+
* guarded reload before showing its `error` (`reload` defaults to `true` here): a whole screen has
|
|
29
|
+
* nothing to degrade to, and in a dev-served app only a new document can load it again.
|
|
30
|
+
*/
|
|
31
|
+
export declare const lazyHandler: <M, K extends ComponentExport<M>>(load: () => Promise<M>, exportName: K, opts?: LazyComponentOptions) => LazyHandler<ExportProps<M, K>>;
|
|
32
|
+
//# sourceMappingURL=lazy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy.d.ts","sourceRoot":"","sources":["../src/lazy.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,oBAAoB,EAAE,WAAW,EAC/E,MAAM,YAAY,CAAA;AAKnB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,CAAC,SAAS,eAAe,CAAC,CAAC,CAAC,QACrD,MAAM,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,SAAS,oBAAoB,KACjE,aAAa,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAgDjC,CAAA;AA+DD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,EAAE,CAAC,SAAS,eAAe,CAAC,CAAC,CAAC,QACnD,MAAM,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,SAAS,oBAAoB,KACjE,WAAW,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAK/B,CAAA"}
|
package/build/lazy.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { lazy, Suspense, Component, useState } from 'react';
|
|
3
|
+
import { handler } from './helper.js';
|
|
4
|
+
import { isChunkLoadError, recoverFromChunkError, retryImport } from './lazy-retry.js';
|
|
5
|
+
/**
|
|
6
|
+
* Wrap an async module import as a stable lazily-loaded component with a `.preload()`.
|
|
7
|
+
*
|
|
8
|
+
* MUST be called at module scope, never inside a render, a hook body or an entrypoint handler
|
|
9
|
+
* factory — the OwlMeans route renderer (`utils/route.tsx`) remounts the whole route subtree on
|
|
10
|
+
* every navigation (a fresh component identity per render), so a lazy object created during
|
|
11
|
+
* render would re-suspend on every navigation instead of rendering instantly once loaded.
|
|
12
|
+
*
|
|
13
|
+
* The `Suspense` boundary sits inside the returned component, so the fallback replaces only this
|
|
14
|
+
* component — the layout around it stays mounted while the chunk loads.
|
|
15
|
+
*
|
|
16
|
+
* Once loaded, later renders resolve synchronously (no re-suspend), so a component whose chunk
|
|
17
|
+
* was already preloaded never shows its fallback. A load that fails to fetch is retried in place
|
|
18
|
+
* (`retryImport`, tuned or turned off by `opts.retry`); one that still fails is never cached: the
|
|
19
|
+
* internal `React.lazy` is recreated, so the next mount, `retry()` or `preload()` loads for real.
|
|
20
|
+
*
|
|
21
|
+
* The component always carries its own error boundary, so a chunk that failed for good never
|
|
22
|
+
* unmounts what is around it: `opts.error` renders in its place, or — without one — the guarded
|
|
23
|
+
* reload (`recoverFromChunkError`) starts and the fallback stays. Any other error without
|
|
24
|
+
* `opts.error` goes on to the nearest boundary above, as if there were none.
|
|
25
|
+
*/
|
|
26
|
+
export const lazyComponent = (load, exportName, opts) => {
|
|
27
|
+
let loaded;
|
|
28
|
+
let loading;
|
|
29
|
+
const fetchModule = () => opts?.retry === false ? load() : retryImport(load, opts?.retry);
|
|
30
|
+
const runLoad = () => loading ??= fetchModule().then(module => {
|
|
31
|
+
const Comp = module[exportName];
|
|
32
|
+
if (Comp == null) {
|
|
33
|
+
throw new SyntaxError(`Lazy module has no component export "${exportName}"`);
|
|
34
|
+
}
|
|
35
|
+
return loaded = Comp;
|
|
36
|
+
}).catch((error) => {
|
|
37
|
+
loading = undefined;
|
|
38
|
+
current = createLazy();
|
|
39
|
+
throw error;
|
|
40
|
+
});
|
|
41
|
+
const preload = () => loaded != null ? Promise.resolve(loaded) : runLoad();
|
|
42
|
+
const createLazy = () => lazy(() => {
|
|
43
|
+
const ready = loaded;
|
|
44
|
+
if (ready != null) {
|
|
45
|
+
// Already resolved: hand React a thenable that settles synchronously, so its lazy
|
|
46
|
+
// initializer reads the module in the same tick and never suspends.
|
|
47
|
+
const settled = { then: (resolve) => resolve({ default: ready }) };
|
|
48
|
+
return settled;
|
|
49
|
+
}
|
|
50
|
+
return runLoad().then(Comp => ({ default: Comp }));
|
|
51
|
+
});
|
|
52
|
+
let current = createLazy();
|
|
53
|
+
const Lazy = (props) => {
|
|
54
|
+
// The lazy THIS instance renders, taken when it mounts: a failed load stays failed for it
|
|
55
|
+
// while React re-renders it to recover from the error, until `retry` swaps in the recreated
|
|
56
|
+
// lazy. A new mount — a navigation back, another place — takes the recreated one at once.
|
|
57
|
+
const [Current, setCurrent] = useState(() => current);
|
|
58
|
+
const fallback = (typeof opts?.fallback === 'function' ? opts.fallback(props) : opts?.fallback) ?? null;
|
|
59
|
+
return _jsx(LazyErrorBoundary, { error: opts?.error, reload: opts?.reload === true, fallback: fallback, props: props, onRetry: () => setCurrent(() => current), children: _jsx(Suspense, { fallback: fallback, children: _jsx(Current, { ...props }) }) });
|
|
60
|
+
};
|
|
61
|
+
Lazy.displayName = `Lazy(${exportName})`;
|
|
62
|
+
return Object.assign(Lazy, { preload });
|
|
63
|
+
};
|
|
64
|
+
class LazyErrorBoundary extends Component {
|
|
65
|
+
state = { failed: false, error: undefined };
|
|
66
|
+
static getDerivedStateFromError(error) {
|
|
67
|
+
return { failed: true, error };
|
|
68
|
+
}
|
|
69
|
+
componentDidCatch(error) {
|
|
70
|
+
// A chunk that failed for good is recovered by a new document: always without a surface of its
|
|
71
|
+
// own, and with one when asked (`reload`) — a whole screen rather than a piece that degrades.
|
|
72
|
+
// The reload is guarded (once a minute per tab); when the guard refuses, the surface stays.
|
|
73
|
+
if (isChunkLoadError(error) && (this.props.error === undefined || this.props.reload)) {
|
|
74
|
+
if (recoverFromChunkError()) {
|
|
75
|
+
this.setState({ reloading: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Render the children again, over the recreated lazy, which loads anew. */
|
|
80
|
+
retry = () => {
|
|
81
|
+
this.setState({ failed: false, error: undefined, reloading: false });
|
|
82
|
+
this.props.onRetry();
|
|
83
|
+
};
|
|
84
|
+
render() {
|
|
85
|
+
if (!this.state.failed) {
|
|
86
|
+
return this.props.children;
|
|
87
|
+
}
|
|
88
|
+
const { error, fallback, props } = this.props;
|
|
89
|
+
if (this.state.reloading === true) {
|
|
90
|
+
return fallback;
|
|
91
|
+
}
|
|
92
|
+
if (error !== undefined) {
|
|
93
|
+
return typeof error === 'function' ? error(props, this.state.error, this.retry) : error;
|
|
94
|
+
}
|
|
95
|
+
if (isChunkLoadError(this.state.error)) {
|
|
96
|
+
return fallback;
|
|
97
|
+
}
|
|
98
|
+
// Not a chunk failure and no surface for it: the application's own boundary decides.
|
|
99
|
+
throw this.state.error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* `handler(lazyComponent(...))`, carrying `.preload()` through — bind it to a screen exactly like
|
|
104
|
+
* `handler(Component)`. The same module-scope rule applies: call it where the entrypoints are
|
|
105
|
+
* declared, never inside a render. A screen whose chunk still fails after its retries starts the
|
|
106
|
+
* guarded reload before showing its `error` (`reload` defaults to `true` here): a whole screen has
|
|
107
|
+
* nothing to degrade to, and in a dev-served app only a new document can load it again.
|
|
108
|
+
*/
|
|
109
|
+
export const lazyHandler = (load, exportName, opts) => {
|
|
110
|
+
const LazyComp = lazyComponent(load, exportName, { ...opts, reload: opts?.reload ?? true });
|
|
111
|
+
const refed = handler(LazyComp);
|
|
112
|
+
return Object.assign(refed, { preload: LazyComp.preload });
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=lazy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy.js","sourceRoot":"","sources":["../src/lazy.tsx"],"names":[],"mappings":"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"}
|