@webority/web-core 0.51.2 → 0.51.3
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
CHANGED
|
@@ -222,9 +222,14 @@ write returns `false`.
|
|
|
222
222
|
|
|
223
223
|
## Chunk reload
|
|
224
224
|
|
|
225
|
-
`importWithReload` reloads once when a lazy chunk from an older deploy fails,
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
`importWithReload` reloads once when a lazy chunk from an older deploy fails, and reloads the page
|
|
226
|
+
being opened (path, query and hash), not the page being left: the failed route renders once the
|
|
227
|
+
navigation commits and reloads from there. A form posted to that route reaches the route's error
|
|
228
|
+
element with the load error, and `useRouteErrorReport` reloads it once. `lazyWithReload` and
|
|
229
|
+
`useRouteErrorReport` reload the same way. The reload is claimed in sessionStorage for that page with a time stamp; if the page's
|
|
230
|
+
chunk still fails after its reload, inside 60 seconds, the route's error element shows it and
|
|
231
|
+
`useRouteErrorReport` warns once, with no further reload. An app's own error boundary shares the
|
|
232
|
+
claim: reload only when `!hasRecentlyReloaded() && markReloaded()`.
|
|
228
233
|
|
|
229
234
|
## Logger
|
|
230
235
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Logger as e}from"../logger/logger.js";import{lazy as t}from"react";var
|
|
1
|
+
import{Logger as e}from"../logger/logger.js";import{lazy as t,useEffect as n,useState as r}from"react";var i=`webority.chunkReload`,a=6e4,o=[/failed to fetch dynamically imported module/i,/error loading dynamically imported module/i,/importing a module script failed/i,/unable to preload css/i,/loading (css )?chunk [\w-]+ failed/i],s=e=>{if(!e||typeof e!=`object`)return!1;let{name:t,message:n}=e;return t===`ChunkLoadError`||typeof n==`string`&&o.some(e=>e.test(n))},c=()=>{let{pathname:e,search:t,hash:n}=globalThis.location;return`${e}${t}${n}`},l=e=>typeof e==`object`&&!!e&&typeof e.target==`string`&&typeof e.at==`number`,u=e=>{if(!e)return null;try{let t=JSON.parse(e);return l(t)?t:null}catch{return null}},d=()=>{try{return u(globalThis.sessionStorage.getItem(i))}catch{return`blocked`}},f=e=>{let t=d();return t===`blocked`?`blocked`:!t||t.target!==e||Date.now()-t.at>=a?`none`:t.at>globalThis.performance.timeOrigin?`pending`:`reloaded`},p=()=>f(c())!==`none`,m=()=>{try{let e={target:c(),at:Date.now()};return globalThis.sessionStorage.setItem(i,JSON.stringify(e)),!0}catch{return!1}},h=()=>{let e=d();if(!(!e||e===`blocked`||e.target===c()))try{globalThis.sessionStorage.removeItem(i)}catch{}},g=t=>{let n=f(c());return n===`pending`?`reloading`:n===`none`?m()?(e.warn(`[router] a code chunk failed to load; reloading once for the new build`,t),globalThis.location.reload(),`reloading`):`blocked`:n},_=e=>(h(),e),v=e=>({Component(){throw e}}),y=e=>({action(){throw e},Component(){let[t,i]=r(()=>f(c()));if(n(()=>{t===`none`&&g(e)===`blocked`&&i(`blocked`)},[t,e]),t===`reloaded`||t===`blocked`)throw e;return null}}),b=e=>()=>e().then(_,e=>s(e)?y(e):v(e)),x=e=>t(()=>e().then(_,e=>{if(s(e)&&g(e)===`reloading`)return new Promise(()=>void 0);throw e}));export{p as hasRecentlyReloaded,b as importWithReload,s as isChunkLoadError,x as lazyWithReload,m as markReloaded,g as reloadForChunkError};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Logger as e}from"../logger/logger.js";import{isChunkLoadError as t}from"./chunkReload.js";import{useEffect as
|
|
1
|
+
import{Logger as e}from"../logger/logger.js";import{isChunkLoadError as t,reloadForChunkError as n}from"./chunkReload.js";import{useEffect as r}from"react";import{isRouteErrorResponse as i,useRouteError as a}from"react-router";var o=e=>i(e)&&e.status===404?`not-found`:t(e)?`stale-build`:`error`,s=()=>{let t=a(),i=o(t);return r(()=>{if(i===`not-found`)e.breadcrumb(`Route not found`);else if(i===`stale-build`){let r=n(t);r===`reloaded`?e.warn(`[router] a code chunk failed to load after the reload`,t):r===`blocked`&&e.warn(`[router] a code chunk failed to load; sessionStorage is unavailable, so the page is not reloaded`,t)}else e.recordError(t,{source:`RouteError`})},[t,i]),{error:t,kind:i}};export{o as classifyRouteError,s as useRouteErrorReport};
|
|
@@ -2,29 +2,41 @@ import { type ComponentType, type LazyExoticComponent } from "react";
|
|
|
2
2
|
/** Whether `error` is a lazy chunk that failed to load, the signature of a tab older than the deploy. */
|
|
3
3
|
export declare const isChunkLoadError: (error: unknown) => boolean;
|
|
4
4
|
/**
|
|
5
|
-
* Whether a recovery reload
|
|
6
|
-
* asks this before reloading for a chunk error, so the
|
|
7
|
-
*
|
|
8
|
-
* way to stop a loop, so the answer is then `true`: never reload.
|
|
5
|
+
* Whether a recovery reload of the current page (path, query and hash) was claimed inside the
|
|
6
|
+
* window (60 s). An app's own error boundary asks this before reloading for a chunk error, so the
|
|
7
|
+
* router and the boundary share one claim and the page reloads at most once between them. Without
|
|
8
|
+
* sessionStorage there is no way to stop a loop, so the answer is then `true`: never reload.
|
|
9
9
|
*/
|
|
10
10
|
export declare const hasRecentlyReloaded: () => boolean;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Claim a recovery reload of the current page; call it right before `location.reload()`.
|
|
13
13
|
* Returns false when it could not be recorded, in which case the caller must not reload.
|
|
14
14
|
*/
|
|
15
15
|
export declare const markReloaded: () => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* What a chunk failure on the current page leads to: `reloading` (the one reload has started, or
|
|
18
|
+
* this document already started it), `reloaded` (the page already had its reload) or `blocked`
|
|
19
|
+
* (sessionStorage is unavailable, so the page is not reloaded).
|
|
20
|
+
*/
|
|
21
|
+
export type ChunkErrorOutcome = "reloading" | "reloaded" | "blocked";
|
|
22
|
+
/** Starts the one reload of the current page for a chunk error, unless it already had it. */
|
|
23
|
+
export declare const reloadForChunkError: (error: unknown) => ChunkErrorOutcome;
|
|
16
24
|
/**
|
|
17
25
|
* A route object's `lazy` (`lazy: importWithReload(() => import('./routes/projects'))`) that
|
|
18
|
-
* reloads the page once on a stale-deploy chunk failure.
|
|
26
|
+
* reloads the page being opened once on a stale-deploy chunk failure.
|
|
19
27
|
*
|
|
20
|
-
*
|
|
21
|
-
* route
|
|
22
|
-
*
|
|
28
|
+
* Every failure resolves to a route rather than rejecting: a rejected `lazy` leaves react-router
|
|
29
|
+
* holding the route as still lazy, and it renders nothing below the nearest HydrateFallback, a
|
|
30
|
+
* blank page. A chunk failure's route reloads when it renders; a failure it cannot recover, and
|
|
31
|
+
* any other error, is thrown by the route's Component, so the route's errorElement shows it.
|
|
23
32
|
*/
|
|
24
33
|
export declare const importWithReload: <T extends object>(importFn: () => Promise<T>) => (() => Promise<T | {
|
|
25
34
|
Component: ComponentType;
|
|
26
35
|
}>);
|
|
27
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* `React.lazy` with the same one reload: a lazy screen that recovers a tab opened before a deploy.
|
|
38
|
+
* It renders after the navigation has committed, so the reload is of the page it is on.
|
|
39
|
+
*/
|
|
28
40
|
export declare const lazyWithReload: <C extends ComponentType<any>>(importFn: () => Promise<{
|
|
29
41
|
default: C;
|
|
30
42
|
}>) => LazyExoticComponent<C>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What went wrong, in the terms the error screen speaks:
|
|
3
3
|
* - `not-found`: no route matched, or a loader threw a 404.
|
|
4
|
-
* - `stale-build`: a code chunk would not load
|
|
5
|
-
*
|
|
4
|
+
* - `stale-build`: a code chunk would not load; a newer build is live. The
|
|
5
|
+
* page reloads once by itself, and if the chunk still will not load after
|
|
6
|
+
* that, a reload by the person is the way out.
|
|
6
7
|
* - `error`: anything else.
|
|
7
8
|
*/
|
|
8
9
|
export type RouteErrorKind = "not-found" | "stale-build" | "error";
|
|
@@ -13,8 +14,11 @@ export interface RouteErrorReport {
|
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* For a route's `errorElement`: reads the error React Router caught, reports it
|
|
16
|
-
* once (a missing page as a breadcrumb, a stale build
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* once (a missing page as a breadcrumb, anything else but a stale build through
|
|
18
|
+
* `Logger.recordError`), and returns its kind for the error screen. A stale build
|
|
19
|
+
* reloads the page once, the page the navigation was going to, since the error
|
|
20
|
+
* element renders after the navigation has committed; only a failure after that
|
|
21
|
+
* reload is warned as one. The error itself is for the logs; the screen shows no
|
|
22
|
+
* error text.
|
|
19
23
|
*/
|
|
20
24
|
export declare const useRouteErrorReport: () => RouteErrorReport;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/web-core",
|
|
3
|
-
"version": "0.51.
|
|
3
|
+
"version": "0.51.3",
|
|
4
4
|
"description": "Platform layer for Webority React web portals: HTTP clients with single-flight token refresh, auth store, API error extraction, RTK Query base query, logger, formatters, storage, version check and route guards. No UI.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|