mirror of https://github.com/alibaba/ice.git
				
				
				
			feat: report detail recoverable error (#6412)
* feat: report detail recoverable error * chore: changelog * fix: optimize code * Update runClientApp.tsx
This commit is contained in:
		
							parent
							
								
									a96af97bd1
								
							
						
					
					
						commit
						d33f3e652a
					
				|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | '@ice/runtime': patch | ||||||
|  | --- | ||||||
|  | 
 | ||||||
|  | feat: report detail recoverable error | ||||||
|  | @ -0,0 +1,39 @@ | ||||||
|  | import type { ErrorStack } from './types.js'; | ||||||
|  | 
 | ||||||
|  | interface ErrorOptions { | ||||||
|  |   ignoreRuntimeWarning?: boolean; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function isRuntimeWarning(error: unknown) { | ||||||
|  |   return error instanceof Error ? [ | ||||||
|  |     'This Suspense boundary received an update before it finished hydrating.', | ||||||
|  |   ].some((message) => error?.message?.includes(message)) : false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export const defaultOnRecoverableError = typeof reportError === 'function' | ||||||
|  |   ? reportError | ||||||
|  |   : function (error: unknown) { | ||||||
|  |     console['error'](error); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const reportRecoverableError = (error: unknown, errorStack: ErrorStack, options?: ErrorOptions) => { | ||||||
|  |   const ignoreError = options?.ignoreRuntimeWarning && isRuntimeWarning(error); | ||||||
|  |   if (!ignoreError) { | ||||||
|  |     if (process.env.NODE_ENV === 'production') { | ||||||
|  |       // Report error stack in production by default.
 | ||||||
|  |       if (errorStack?.componentStack && error instanceof Error) { | ||||||
|  |         const detailError = new Error(error.message); | ||||||
|  |         detailError.name = error.name; | ||||||
|  |         detailError.stack = `${error.name}: ${error.message}${errorStack.componentStack}`; | ||||||
|  |         defaultOnRecoverableError(detailError); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     // Fallback to default error handler.
 | ||||||
|  |     defaultOnRecoverableError(error); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default reportRecoverableError; | ||||||
|  | @ -4,6 +4,7 @@ import { createHashHistory, createBrowserHistory, createMemoryHistory } from '@r | ||||||
| import type { History } from '@remix-run/router'; | import type { History } from '@remix-run/router'; | ||||||
| import type { | import type { | ||||||
|   AppContext, WindowContext, AppExport, RouteItem, RuntimeModules, AppConfig, AssetsManifest, ClientAppRouterProps, |   AppContext, WindowContext, AppExport, RouteItem, RuntimeModules, AppConfig, AssetsManifest, ClientAppRouterProps, | ||||||
|  |   ErrorStack, | ||||||
| } from './types.js'; | } from './types.js'; | ||||||
| import { createHistory as createHistorySingle } from './singleRouter.js'; | import { createHistory as createHistorySingle } from './singleRouter.js'; | ||||||
| import { setHistory } from './history.js'; | import { setHistory } from './history.js'; | ||||||
|  | @ -19,6 +20,7 @@ import ClientRouter from './ClientRouter.js'; | ||||||
| import addLeadingSlash from './utils/addLeadingSlash.js'; | import addLeadingSlash from './utils/addLeadingSlash.js'; | ||||||
| import { AppContextProvider } from './AppContext.js'; | import { AppContextProvider } from './AppContext.js'; | ||||||
| import { deprecatedHistory } from './utils/deprecatedHistory.js'; | import { deprecatedHistory } from './utils/deprecatedHistory.js'; | ||||||
|  | import reportRecoverableError from './reportRecoverableError.js'; | ||||||
| 
 | 
 | ||||||
| export interface RunClientAppOptions { | export interface RunClientAppOptions { | ||||||
|   app: AppExport; |   app: AppExport; | ||||||
|  | @ -32,6 +34,7 @@ export interface RunClientAppOptions { | ||||||
|   dataLoaderDecorator?: Function; |   dataLoaderDecorator?: Function; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| export default async function runClientApp(options: RunClientAppOptions) { | export default async function runClientApp(options: RunClientAppOptions) { | ||||||
|   const { |   const { | ||||||
|     app, |     app, | ||||||
|  | @ -105,20 +108,14 @@ export default async function runClientApp(options: RunClientAppOptions) { | ||||||
| 
 | 
 | ||||||
|   const needHydrate = hydrate && !downgrade && !documentOnly; |   const needHydrate = hydrate && !downgrade && !documentOnly; | ||||||
|   if (needHydrate) { |   if (needHydrate) { | ||||||
|     const defaultOnRecoverableError = typeof reportError === 'function' ? reportError |  | ||||||
|       : function (error: unknown) { |  | ||||||
|         console['error'](error); |  | ||||||
|       }; |  | ||||||
|     runtime.setRender((container, element) => { |     runtime.setRender((container, element) => { | ||||||
|       const hydrateOptions = revalidate |       const hydrateOptions: ReactDOM.HydrationOptions = { | ||||||
|         ? { |         // @ts-ignore react-dom do not define the type of second argument of onRecoverableError.
 | ||||||
|           onRecoverableError(error: unknown) { |         onRecoverableError: appConfig?.app?.onRecoverableError || | ||||||
|             // Ignore this error caused by router.revalidate
 |         ((error: unknown, errorInfo: ErrorStack) => { | ||||||
|             if ((error as Error)?.message?.indexOf('This Suspense boundary received an update before it finished hydrating.') == -1) { |           reportRecoverableError(error, errorInfo, { ignoreRuntimeWarning: revalidate }); | ||||||
|               defaultOnRecoverableError(error); |         }), | ||||||
|             } |       }; | ||||||
|           }, |  | ||||||
|         } : {}; |  | ||||||
|       return ReactDOM.hydrateRoot(container, element, hydrateOptions); |       return ReactDOM.hydrateRoot(container, element, hydrateOptions); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -14,8 +14,14 @@ type App = Partial<{ | ||||||
|   rootId: string; |   rootId: string; | ||||||
|   strict: boolean; |   strict: boolean; | ||||||
|   errorBoundary: boolean; |   errorBoundary: boolean; | ||||||
|  |   onRecoverableError: (error: unknown, errorInfo: ErrorStack) => void; | ||||||
| } & Record<AppLifecycle, VoidFunction>>; | } & Record<AppLifecycle, VoidFunction>>; | ||||||
| 
 | 
 | ||||||
|  | export interface ErrorStack { | ||||||
|  |   componentStack?: string; | ||||||
|  |   digest?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export type AppData = any; | export type AppData = any; | ||||||
| export type RouteData = any; | export type RouteData = any; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue