@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/build/types.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import type { PropsWithChildren, FC, DependencyList } from 'react';
1
+ import type { PropsWithChildren, FC, DependencyList, ComponentType, ReactNode } from 'react';
2
2
  import type { AbstractRequest } from '@owlmeans/entrypoint';
3
3
  import type { ClientConfig, ClientContext as BasicClientContext } from '@owlmeans/client-context';
4
4
  import type { Criteria, ResourceRecord, Sort } from '@owlmeans/resource';
5
5
  import type { StateResourceAppend } from '@owlmeans/state';
6
- import type { ClientEntrypoint } from '@owlmeans/client-entrypoint';
6
+ import type { ClientEntrypoint, RefedEntrypointHandler } from '@owlmeans/client-entrypoint';
7
7
  import type { DebugServiceAppend, ModalServiceAppend } from './components/types.js';
8
8
  import type { ConfigResourceAppend } from '@owlmeans/config';
9
9
  import type { ConfigRecord, EntrypointReference } from '@owlmeans/context';
@@ -73,4 +73,71 @@ export interface UseStoreListOptions<T extends ResourceRecord = ResourceRecord>
73
73
  /** Which state resource to read; the context's default one when omitted. */
74
74
  resource?: string;
75
75
  }
76
+ /** How `retryImport` retries a rejected dynamic import. */
77
+ export interface RetryImportOptions {
78
+ /** Loads after the first failure, at most; `0` loads once. Default 2. */
79
+ attempts?: number;
80
+ /** The pause before each retry, in order; the last one repeats. Default 500 ms, then 1500 ms. */
81
+ delaysMs?: readonly number[];
82
+ /**
83
+ * Which rejections are retried. Default `isChunkLoadError` — a module that could not be fetched,
84
+ * never one that was fetched and then failed.
85
+ */
86
+ shouldRetry?: (error: unknown) => boolean;
87
+ /**
88
+ * Retry a failed chunk from its own URL with a cache-busting `t` parameter when the browser's
89
+ * error names it (Chromium, Firefox). Default `true`: a browser that remembers a failed module
90
+ * fetch rejects every later `import()` of the same URL at once, so only a new URL is fetched
91
+ * again. `false` repeats `load` as is.
92
+ */
93
+ bustCache?: boolean;
94
+ /** How a cache-busted URL is imported; a seam for tests. Default: a dynamic `import()`. */
95
+ importUrl?: (url: string) => Promise<unknown>;
96
+ }
97
+ /**
98
+ * What a lazily-loaded piece renders once it failed: the props it was given, the error, and
99
+ * `retry`, which resets the piece's boundary so its chunk loads again.
100
+ */
101
+ export type LazyErrorRenderer = (props: any, error: unknown, retry: () => void) => ReactNode;
102
+ /** How a lazily-loaded component behaves while its chunk loads and when the load fails. */
103
+ export interface LazyComponentOptions {
104
+ /** Shown while the chunk loads: a node, or a function of the props the component was given. */
105
+ fallback?: ReactNode | ((props: any) => ReactNode);
106
+ /**
107
+ * Rendered in place of the component when its chunk fails to load, or it throws: a node, or a
108
+ * `LazyErrorRenderer`. Omitted, a chunk failure starts the guarded reload
109
+ * (`recoverFromChunkError`) and leaves `fallback` in place, and any other error propagates to
110
+ * the nearest boundary above.
111
+ */
112
+ error?: ReactNode | LazyErrorRenderer;
113
+ /**
114
+ * How a rejected load is retried before it counts as failed — `retryImport`'s options, its
115
+ * defaults when omitted. `false` loads once.
116
+ */
117
+ retry?: false | RetryImportOptions;
118
+ /**
119
+ * When a chunk still fails after its retries, start the guarded page reload
120
+ * (`recoverFromChunkError`, once a minute per tab) before showing `error`; the fallback stays up
121
+ * while the reload starts, and `error` shows when the guard refuses. Without `error` a chunk
122
+ * failure always reloads. Default `false` for `lazyComponent` (a piece degrades in place), `true`
123
+ * for `lazyHandler` (a whole screen). A dev-served app needs it: its modules import themselves by
124
+ * their own URL (React Fast Refresh), so a browser that remembers the failed fetch can load that
125
+ * module again only in a new document.
126
+ */
127
+ reload?: boolean;
128
+ }
129
+ export interface LazyPreload<P = {}> {
130
+ /** Start (or join) the chunk load; resolves to the component once it is ready. */
131
+ preload: () => Promise<ComponentType<P>>;
132
+ }
133
+ /** A lazily-loaded component: renders like the real one, and can be preloaded ahead of use. */
134
+ export type LazyComponent<P = {}> = ComponentType<P> & LazyPreload<P>;
135
+ /** An entrypoint handler over a lazily-loaded component, carrying its `.preload()`. */
136
+ export type LazyHandler<P = {}> = RefedEntrypointHandler<P> & LazyPreload<P>;
137
+ /** Names of a module's exports that are React components. */
138
+ export type ComponentExport<M> = {
139
+ [K in keyof M]-?: M[K] extends ComponentType<any> ? K : never;
140
+ }[keyof M] & string;
141
+ /** The props of a module's component export. */
142
+ export type ExportProps<M, K extends keyof M> = M[K] extends ComponentType<infer P> ? P : never;
76
143
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@owlmeans/client",
3
- "version": "0.1.18-rc.40",
3
+ "version": "0.1.18-rc.41",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsc -b",
8
8
  "dev": "sleep 54 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
9
- "watch": "tsc -b -w --preserveWatchOutput --pretty"
9
+ "watch": "tsc -b -w --preserveWatchOutput --pretty",
10
+ "test": "bun test ./tests"
10
11
  },
11
12
  "main": "build/index.js",
12
13
  "module": "build/index.js",
@@ -28,17 +29,17 @@
28
29
  }
29
30
  },
30
31
  "dependencies": {
31
- "@owlmeans/auth": "^0.1.18-rc.35",
32
- "@owlmeans/client-context": "^0.1.18-rc.38",
33
- "@owlmeans/client-entrypoint": "^0.1.18-rc.37",
34
- "@owlmeans/client-resource": "^0.1.18-rc.37",
35
- "@owlmeans/config": "^0.1.18-rc.37",
32
+ "@owlmeans/auth": "^0.1.18-rc.36",
33
+ "@owlmeans/client-context": "^0.1.18-rc.39",
34
+ "@owlmeans/client-entrypoint": "^0.1.18-rc.38",
35
+ "@owlmeans/client-resource": "^0.1.18-rc.38",
36
+ "@owlmeans/config": "^0.1.18-rc.38",
36
37
  "@owlmeans/context": "^0.1.18-rc.32",
37
- "@owlmeans/error": "^0.1.18-rc.35",
38
- "@owlmeans/entrypoint": "^0.1.18-rc.36",
39
- "@owlmeans/resource": "^0.1.18-rc.35",
38
+ "@owlmeans/error": "^0.1.18-rc.36",
39
+ "@owlmeans/entrypoint": "^0.1.18-rc.37",
40
+ "@owlmeans/resource": "^0.1.18-rc.36",
40
41
  "@owlmeans/router": "^0.1.18-rc.31",
41
- "@owlmeans/state": "^0.1.18-rc.34"
42
+ "@owlmeans/state": "^0.1.18-rc.35"
42
43
  },
43
44
  "peerDependencies": {
44
45
  "@remix-run/router": "*",
@@ -51,9 +52,15 @@
51
52
  },
52
53
  "devDependencies": {
53
54
  "@owlmeans/dep-config": "workspace:*",
55
+ "@owlmeans/test-ui": "^0.1.18-rc.39",
54
56
  "@types/react": "^19.2.17",
57
+ "@vitejs/plugin-react": "^6.0.3",
55
58
  "nodemon": "^3.1.14",
56
- "typescript": "^7.0.2"
59
+ "playwright": "^1.61.1",
60
+ "react": "^19.2.7",
61
+ "react-dom": "^19.2.7",
62
+ "typescript": "^7.0.2",
63
+ "vite": "^8.1.2"
57
64
  },
58
65
  "publishConfig": {
59
66
  "access": "public"
package/src/consts.ts CHANGED
@@ -5,3 +5,13 @@ export const DEF_DEBUG_ALIAS = 'debug'
5
5
  export const DEBUGGER_FLAG = 'debugger'
6
6
  export const DEBUG_CONFIG_KEY = 'debugger'
7
7
 
8
+ /** How many times `retryImport` loads a chunk again after its first failure. */
9
+ export const DEF_IMPORT_RETRY_ATTEMPTS = 2
10
+ /** The pause before each retry, in order; the last one repeats. */
11
+ export const DEF_IMPORT_RETRY_DELAYS_MS: readonly number[] = [500, 1500]
12
+
13
+ /** The `sessionStorage` key `recoverFromChunkError` keeps the time of its last reload under. */
14
+ export const CHUNK_RELOAD_KEY = 'owlmeans:chunk-reload'
15
+ /** At most one chunk-recovery reload per this window, per tab. */
16
+ export const CHUNK_RELOAD_WINDOW_MS = 60_000
17
+
package/src/index.ts CHANGED
@@ -3,6 +3,8 @@ export * from './context.js'
3
3
  export * from './components/index.js'
4
4
  export * from './services/index.js'
5
5
  export * from './helper.js'
6
+ export * from './lazy.js'
7
+ export * from './lazy-retry.js'
6
8
  export * from './navigate.js'
7
9
  export * from './entrypoint.js'
8
10
  export * from './router.js'
@@ -0,0 +1,141 @@
1
+ import {
2
+ CHUNK_RELOAD_KEY, CHUNK_RELOAD_WINDOW_MS, DEF_IMPORT_RETRY_ATTEMPTS, DEF_IMPORT_RETRY_DELAYS_MS
3
+ } from './consts.js'
4
+ import type { RetryImportOptions } from './types.js'
5
+
6
+ /**
7
+ * What a browser says when a dynamic `import()` could not fetch its module — Chromium, Safari and
8
+ * Firefox, in that order — and what Vite's preload helper says when a chunk's stylesheet could not.
9
+ */
10
+ const CHUNK_FAILURE = /failed to fetch dynamically imported module|importing a module script failed|error loading dynamically imported module|unable to preload css/i
11
+
12
+ /**
13
+ * Whether `error` is a chunk that could not be FETCHED: a browser's failed dynamic import, Vite's
14
+ * stylesheet preload failure, webpack's `ChunkLoadError`, Vite's `vite:preloadError` event, or
15
+ * the `error` event of the element a loader fetched through. A module that was fetched and then
16
+ * failed — a missing export, a throw while it evaluated — is not one: loading it again changes
17
+ * nothing.
18
+ */
19
+ export const isChunkLoadError = (error: unknown): boolean => {
20
+ if (error == null || typeof error !== 'object') {
21
+ return false
22
+ }
23
+ if (typeof Event !== 'undefined' && error instanceof Event) {
24
+ return error.type === 'vite:preloadError' || error.type === 'error'
25
+ }
26
+ const { name, message } = error as { name?: unknown, message?: unknown }
27
+
28
+ return name === 'ChunkLoadError' || (typeof message === 'string' && CHUNK_FAILURE.test(message))
29
+ }
30
+
31
+ const pause = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms))
32
+
33
+ /** The module URL a Chromium or Firefox import failure names; Safari's message names none. */
34
+ const FAILED_URL = /dynamically imported module:?\s+(\S+)/i
35
+
36
+ /**
37
+ * The URL of the chunk a failed dynamic import tried to fetch, when the browser's error names it
38
+ * and it is an http(s) URL of this page's origin — never an arbitrary URL out of a message. `null`
39
+ * otherwise (Safari, a non-browser platform, another origin).
40
+ */
41
+ export const chunkUrlOf = (error: unknown): string | null => {
42
+ const message = (error as { message?: unknown } | null)?.message
43
+ const found = typeof message === 'string' ? FAILED_URL.exec(message)?.[1] : undefined
44
+ if (found == null) {
45
+ return null
46
+ }
47
+ try {
48
+ const url = new URL(found)
49
+ const origin = typeof location !== 'undefined' ? location.origin : undefined
50
+ if ((url.protocol !== 'https:' && url.protocol !== 'http:') || (origin != null && url.origin !== origin)) {
51
+ return null
52
+ }
53
+ return url.href
54
+ } catch {
55
+ return null
56
+ }
57
+ }
58
+
59
+ /**
60
+ * `href` with a fresh `t` parameter: a URL the browser has not seen, so it fetches the module
61
+ * again. `t` is the parameter Vite's dev server already gives module URLs; a static host ignores it.
62
+ */
63
+ export const cacheBustedUrl = (href: string, now: number = Date.now()): string => {
64
+ const url = new URL(href)
65
+ url.searchParams.set('t', String(now))
66
+ return url.href
67
+ }
68
+
69
+ const importUrl = (url: string): Promise<unknown> =>
70
+ import(/* @vite-ignore */ /* webpackIgnore: true */ url)
71
+
72
+ /**
73
+ * Run `load` — a dynamic `import()` — and, while it rejects with a chunk-load failure, run it again
74
+ * after a pause, `attempts` more times at most. Any other rejection is rethrown at once.
75
+ *
76
+ * This covers a TRANSIENT failure — a network blip, an edge answering 404 or 5xx for a moment.
77
+ * A browser remembers a failed module fetch for the document's lifetime (Chromium does) and
78
+ * rejects every later `import()` of that URL at once, so a retry imports the URL the error names
79
+ * with a cache-busting parameter instead (`bustCache`, default on): a new URL, fetched again. Where
80
+ * the error names no URL (Safari), `load` runs again as is; what always recovers is a new
81
+ * document, which `recoverFromChunkError` starts, once and guarded.
82
+ */
83
+ export const retryImport = async <M>(load: () => Promise<M>, opts?: RetryImportOptions): Promise<M> => {
84
+ const attempts = Math.max(0, opts?.attempts ?? DEF_IMPORT_RETRY_ATTEMPTS)
85
+ const delays = opts?.delaysMs ?? DEF_IMPORT_RETRY_DELAYS_MS
86
+ const shouldRetry = opts?.shouldRetry ?? isChunkLoadError
87
+ const bust = opts?.bustCache !== false
88
+ const importBusted = opts?.importUrl ?? importUrl
89
+
90
+ let next: () => Promise<M> = load
91
+ for (let attempt = 0; ; attempt++) {
92
+ try {
93
+ return await next()
94
+ } catch (error) {
95
+ if (attempt >= attempts || !shouldRetry(error)) {
96
+ throw error
97
+ }
98
+ const url = bust ? chunkUrlOf(error) : null
99
+ if (url != null) {
100
+ next = async () => await importBusted(cacheBustedUrl(url)) as M
101
+ }
102
+ await pause(delays[Math.min(attempt, delays.length - 1)] ?? 0)
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Reload the page, at most once per `windowMs` in this tab — the time of the last reload is kept
109
+ * in `sessionStorage` under `key`. Never while offline (the reload would land on the browser's own
110
+ * error page), and never without storage: with nowhere to keep the guard, a failure that survives
111
+ * the reload would reload forever. A platform with no page to reload does nothing. Answers whether
112
+ * a reload was started.
113
+ */
114
+ export const reloadOnce = (key: string, windowMs: number): boolean => {
115
+ const location = typeof window !== 'undefined' ? window.location : undefined
116
+ if (typeof location?.reload !== 'function') {
117
+ return false
118
+ }
119
+ if (typeof navigator !== 'undefined' && navigator.onLine === false) {
120
+ return false
121
+ }
122
+ try {
123
+ const now = Date.now()
124
+ if (now - Number(window.sessionStorage.getItem(key) ?? 0) < windowMs) {
125
+ return false
126
+ }
127
+ window.sessionStorage.setItem(key, String(now))
128
+ } catch {
129
+ return false
130
+ }
131
+ location.reload()
132
+
133
+ return true
134
+ }
135
+
136
+ /**
137
+ * The guarded reload a chunk that failed for good ends in: `reloadOnce` under one key every caller
138
+ * in the tab shares, so a lazy boundary and an application's own `vite:preloadError` listener
139
+ * never reload twice for one failure.
140
+ */
141
+ export const recoverFromChunkError = (): boolean => reloadOnce(CHUNK_RELOAD_KEY, CHUNK_RELOAD_WINDOW_MS)
package/src/lazy.tsx ADDED
@@ -0,0 +1,161 @@
1
+ import { lazy, Suspense, Component, useState } from 'react'
2
+ import type { ComponentType, ReactNode } from 'react'
3
+ import { handler } from './helper.js'
4
+ import { isChunkLoadError, recoverFromChunkError, retryImport } from './lazy-retry.js'
5
+ import type { HandledRenderer } from './utils/route.js'
6
+ import type {
7
+ ComponentExport, ExportProps, LazyComponent, LazyComponentOptions, LazyHandler
8
+ } from './types.js'
9
+
10
+ type Loaded = ComponentType<any>
11
+ type LazyModule = { default: Loaded }
12
+
13
+ /**
14
+ * Wrap an async module import as a stable lazily-loaded component with a `.preload()`.
15
+ *
16
+ * MUST be called at module scope, never inside a render, a hook body or an entrypoint handler
17
+ * factory — the OwlMeans route renderer (`utils/route.tsx`) remounts the whole route subtree on
18
+ * every navigation (a fresh component identity per render), so a lazy object created during
19
+ * render would re-suspend on every navigation instead of rendering instantly once loaded.
20
+ *
21
+ * The `Suspense` boundary sits inside the returned component, so the fallback replaces only this
22
+ * component — the layout around it stays mounted while the chunk loads.
23
+ *
24
+ * Once loaded, later renders resolve synchronously (no re-suspend), so a component whose chunk
25
+ * was already preloaded never shows its fallback. A load that fails to fetch is retried in place
26
+ * (`retryImport`, tuned or turned off by `opts.retry`); one that still fails is never cached: the
27
+ * internal `React.lazy` is recreated, so the next mount, `retry()` or `preload()` loads for real.
28
+ *
29
+ * The component always carries its own error boundary, so a chunk that failed for good never
30
+ * unmounts what is around it: `opts.error` renders in its place, or — without one — the guarded
31
+ * reload (`recoverFromChunkError`) starts and the fallback stays. Any other error without
32
+ * `opts.error` goes on to the nearest boundary above, as if there were none.
33
+ */
34
+ export const lazyComponent = <M, K extends ComponentExport<M>>(
35
+ load: () => Promise<M>, exportName: K, opts?: LazyComponentOptions
36
+ ): LazyComponent<ExportProps<M, K>> => {
37
+ let loaded: Loaded | undefined
38
+ let loading: Promise<Loaded> | undefined
39
+
40
+ const fetchModule = (): Promise<M> => opts?.retry === false ? load() : retryImport(load, opts?.retry)
41
+
42
+ const runLoad = (): Promise<Loaded> => loading ??= fetchModule().then(module => {
43
+ const Comp = (module as unknown as Record<string, Loaded | undefined>)[exportName]
44
+ if (Comp == null) {
45
+ throw new SyntaxError(`Lazy module has no component export "${exportName}"`)
46
+ }
47
+ return loaded = Comp
48
+ }).catch((error: unknown) => {
49
+ loading = undefined
50
+ current = createLazy()
51
+ throw error
52
+ })
53
+
54
+ const preload = (): Promise<Loaded> => loaded != null ? Promise.resolve(loaded) : runLoad()
55
+
56
+ const createLazy = () => lazy<Loaded>(() => {
57
+ const ready = loaded
58
+ if (ready != null) {
59
+ // Already resolved: hand React a thenable that settles synchronously, so its lazy
60
+ // initializer reads the module in the same tick and never suspends.
61
+ const settled = { then: (resolve: (module: LazyModule) => void) => resolve({ default: ready }) }
62
+ return settled as unknown as Promise<LazyModule>
63
+ }
64
+ return runLoad().then(Comp => ({ default: Comp }))
65
+ })
66
+
67
+ let current = createLazy()
68
+
69
+ const Lazy = (props: Record<string, unknown>) => {
70
+ // The lazy THIS instance renders, taken when it mounts: a failed load stays failed for it
71
+ // while React re-renders it to recover from the error, until `retry` swaps in the recreated
72
+ // lazy. A new mount — a navigation back, another place — takes the recreated one at once.
73
+ const [Current, setCurrent] = useState(() => current)
74
+ const fallback = (typeof opts?.fallback === 'function' ? opts.fallback(props) : opts?.fallback) ?? null
75
+
76
+ return <LazyErrorBoundary error={opts?.error} reload={opts?.reload === true} fallback={fallback} props={props}
77
+ onRetry={() => setCurrent(() => current)}>
78
+ <Suspense fallback={fallback}><Current {...props} /></Suspense>
79
+ </LazyErrorBoundary>
80
+ }
81
+ Lazy.displayName = `Lazy(${exportName})`
82
+
83
+ return Object.assign(Lazy, { preload }) as unknown as LazyComponent<ExportProps<M, K>>
84
+ }
85
+
86
+ interface LazyErrorBoundaryProps {
87
+ error: LazyComponentOptions['error']
88
+ /** Start the guarded reload for a chunk failure even though `error` is given. */
89
+ reload: boolean
90
+ fallback: ReactNode
91
+ props: Record<string, unknown>
92
+ /** Swap the failed lazy for the recreated one — batched with the boundary's own reset. */
93
+ onRetry: () => void
94
+ children: ReactNode
95
+ }
96
+
97
+ interface LazyErrorBoundaryState {
98
+ failed: boolean
99
+ error: unknown
100
+ /** A guarded reload has started: keep the fallback up until the new document replaces this one. */
101
+ reloading?: boolean
102
+ }
103
+
104
+ class LazyErrorBoundary extends Component<LazyErrorBoundaryProps, LazyErrorBoundaryState> {
105
+ override state: LazyErrorBoundaryState = { failed: false, error: undefined }
106
+
107
+ static getDerivedStateFromError(error: unknown): LazyErrorBoundaryState {
108
+ return { failed: true, error }
109
+ }
110
+
111
+ override componentDidCatch(error: unknown): void {
112
+ // A chunk that failed for good is recovered by a new document: always without a surface of its
113
+ // own, and with one when asked (`reload`) — a whole screen rather than a piece that degrades.
114
+ // The reload is guarded (once a minute per tab); when the guard refuses, the surface stays.
115
+ if (isChunkLoadError(error) && (this.props.error === undefined || this.props.reload)) {
116
+ if (recoverFromChunkError()) {
117
+ this.setState({ reloading: true })
118
+ }
119
+ }
120
+ }
121
+
122
+ /** Render the children again, over the recreated lazy, which loads anew. */
123
+ readonly retry = (): void => {
124
+ this.setState({ failed: false, error: undefined, reloading: false })
125
+ this.props.onRetry()
126
+ }
127
+
128
+ override render() {
129
+ if (!this.state.failed) {
130
+ return this.props.children
131
+ }
132
+ const { error, fallback, props } = this.props
133
+ if (this.state.reloading === true) {
134
+ return fallback
135
+ }
136
+ if (error !== undefined) {
137
+ return typeof error === 'function' ? error(props, this.state.error, this.retry) : error
138
+ }
139
+ if (isChunkLoadError(this.state.error)) {
140
+ return fallback
141
+ }
142
+ // Not a chunk failure and no surface for it: the application's own boundary decides.
143
+ throw this.state.error
144
+ }
145
+ }
146
+
147
+ /**
148
+ * `handler(lazyComponent(...))`, carrying `.preload()` through — bind it to a screen exactly like
149
+ * `handler(Component)`. The same module-scope rule applies: call it where the entrypoints are
150
+ * declared, never inside a render. A screen whose chunk still fails after its retries starts the
151
+ * guarded reload before showing its `error` (`reload` defaults to `true` here): a whole screen has
152
+ * nothing to degrade to, and in a dev-served app only a new document can load it again.
153
+ */
154
+ export const lazyHandler = <M, K extends ComponentExport<M>>(
155
+ load: () => Promise<M>, exportName: K, opts?: LazyComponentOptions
156
+ ): LazyHandler<ExportProps<M, K>> => {
157
+ const LazyComp = lazyComponent(load, exportName, { ...opts, reload: opts?.reload ?? true })
158
+ const refed = handler(LazyComp as unknown as HandledRenderer<{}>)
159
+
160
+ return Object.assign(refed, { preload: LazyComp.preload }) as unknown as LazyHandler<ExportProps<M, K>>
161
+ }
package/src/types.ts CHANGED
@@ -1,9 +1,9 @@
1
- import type { PropsWithChildren, FC, DependencyList } from 'react'
1
+ import type { PropsWithChildren, FC, DependencyList, ComponentType, ReactNode } from 'react'
2
2
  import type { AbstractRequest } from '@owlmeans/entrypoint'
3
3
  import type { ClientConfig, ClientContext as BasicClientContext } from '@owlmeans/client-context'
4
4
  import type { Criteria, ResourceRecord, Sort } from '@owlmeans/resource'
5
5
  import type { StateResourceAppend } from '@owlmeans/state'
6
- import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
6
+ import type { ClientEntrypoint, RefedEntrypointHandler } from '@owlmeans/client-entrypoint'
7
7
  import type { DebugServiceAppend, ModalServiceAppend } from './components/types.js'
8
8
  import type { ConfigResourceAppend } from '@owlmeans/config'
9
9
  import type { ConfigRecord, EntrypointReference } from '@owlmeans/context'
@@ -92,3 +92,78 @@ export interface UseStoreListOptions<T extends ResourceRecord = ResourceRecord>
92
92
  /** Which state resource to read; the context's default one when omitted. */
93
93
  resource?: string
94
94
  }
95
+
96
+ /** How `retryImport` retries a rejected dynamic import. */
97
+ export interface RetryImportOptions {
98
+ /** Loads after the first failure, at most; `0` loads once. Default 2. */
99
+ attempts?: number
100
+ /** The pause before each retry, in order; the last one repeats. Default 500 ms, then 1500 ms. */
101
+ delaysMs?: readonly number[]
102
+ /**
103
+ * Which rejections are retried. Default `isChunkLoadError` — a module that could not be fetched,
104
+ * never one that was fetched and then failed.
105
+ */
106
+ shouldRetry?: (error: unknown) => boolean
107
+ /**
108
+ * Retry a failed chunk from its own URL with a cache-busting `t` parameter when the browser's
109
+ * error names it (Chromium, Firefox). Default `true`: a browser that remembers a failed module
110
+ * fetch rejects every later `import()` of the same URL at once, so only a new URL is fetched
111
+ * again. `false` repeats `load` as is.
112
+ */
113
+ bustCache?: boolean
114
+ /** How a cache-busted URL is imported; a seam for tests. Default: a dynamic `import()`. */
115
+ importUrl?: (url: string) => Promise<unknown>
116
+ }
117
+
118
+ /**
119
+ * What a lazily-loaded piece renders once it failed: the props it was given, the error, and
120
+ * `retry`, which resets the piece's boundary so its chunk loads again.
121
+ */
122
+ export type LazyErrorRenderer = (props: any, error: unknown, retry: () => void) => ReactNode
123
+
124
+ /** How a lazily-loaded component behaves while its chunk loads and when the load fails. */
125
+ export interface LazyComponentOptions {
126
+ /** Shown while the chunk loads: a node, or a function of the props the component was given. */
127
+ fallback?: ReactNode | ((props: any) => ReactNode)
128
+ /**
129
+ * Rendered in place of the component when its chunk fails to load, or it throws: a node, or a
130
+ * `LazyErrorRenderer`. Omitted, a chunk failure starts the guarded reload
131
+ * (`recoverFromChunkError`) and leaves `fallback` in place, and any other error propagates to
132
+ * the nearest boundary above.
133
+ */
134
+ error?: ReactNode | LazyErrorRenderer
135
+ /**
136
+ * How a rejected load is retried before it counts as failed — `retryImport`'s options, its
137
+ * defaults when omitted. `false` loads once.
138
+ */
139
+ retry?: false | RetryImportOptions
140
+ /**
141
+ * When a chunk still fails after its retries, start the guarded page reload
142
+ * (`recoverFromChunkError`, once a minute per tab) before showing `error`; the fallback stays up
143
+ * while the reload starts, and `error` shows when the guard refuses. Without `error` a chunk
144
+ * failure always reloads. Default `false` for `lazyComponent` (a piece degrades in place), `true`
145
+ * for `lazyHandler` (a whole screen). A dev-served app needs it: its modules import themselves by
146
+ * their own URL (React Fast Refresh), so a browser that remembers the failed fetch can load that
147
+ * module again only in a new document.
148
+ */
149
+ reload?: boolean
150
+ }
151
+
152
+ export interface LazyPreload<P = {}> {
153
+ /** Start (or join) the chunk load; resolves to the component once it is ready. */
154
+ preload: () => Promise<ComponentType<P>>
155
+ }
156
+
157
+ /** A lazily-loaded component: renders like the real one, and can be preloaded ahead of use. */
158
+ export type LazyComponent<P = {}> = ComponentType<P> & LazyPreload<P>
159
+
160
+ /** An entrypoint handler over a lazily-loaded component, carrying its `.preload()`. */
161
+ export type LazyHandler<P = {}> = RefedEntrypointHandler<P> & LazyPreload<P>
162
+
163
+ /** Names of a module's exports that are React components. */
164
+ export type ComponentExport<M> = {
165
+ [K in keyof M]-?: M[K] extends ComponentType<any> ? K : never
166
+ }[keyof M] & string
167
+
168
+ /** The props of a module's component export. */
169
+ export type ExportProps<M, K extends keyof M> = M[K] extends ComponentType<infer P> ? P : never
@@ -0,0 +1,33 @@
1
+ import { createServer } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { dirname, resolve } from 'node:path'
5
+
6
+ const here = dirname(fileURLToPath(import.meta.url))
7
+
8
+ let url: string | null = null
9
+
10
+ /**
11
+ * Boot a Vite dev server serving `tests/harness/` — a real HTTP origin, because a lazily-loaded
12
+ * piece is a module request of its own and a failed one ends in a real page reload, neither of
13
+ * which a `data:` URL can host. One server per `bun test` process.
14
+ */
15
+ export const getHarnessUrl = async (): Promise<string> => {
16
+ if (url != null) return url
17
+ const server = await createServer({
18
+ configFile: false,
19
+ root: resolve(here, './harness'),
20
+ plugins: [react()],
21
+ // Single React instance so hooks work across the workspace-linked packages.
22
+ resolve: { dedupe: ['react', 'react-dom'] },
23
+ server: { port: 0 },
24
+ logLevel: 'warn'
25
+ })
26
+ await server.listen()
27
+ const local = server.resolvedUrls?.local?.[0]
28
+ if (local == null) throw new Error('vite did not expose a local URL')
29
+ url = local
30
+ return url
31
+ }
32
+
33
+ export const HARNESS_URL = await getHarnessUrl()
@@ -0,0 +1,11 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>@owlmeans/client lazy harness</title>
6
+ </head>
7
+ <body>
8
+ <div id="root"></div>
9
+ <script type="module" src="/mount.tsx"></script>
10
+ </body>
11
+ </html>