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 {
|
||||
AppContext, WindowContext, AppExport, RouteItem, RuntimeModules, AppConfig, AssetsManifest, ClientAppRouterProps,
|
||||
ErrorStack,
|
||||
} from './types.js';
|
||||
import { createHistory as createHistorySingle } from './singleRouter.js';
|
||||
import { setHistory } from './history.js';
|
||||
|
|
@ -19,6 +20,7 @@ import ClientRouter from './ClientRouter.js';
|
|||
import addLeadingSlash from './utils/addLeadingSlash.js';
|
||||
import { AppContextProvider } from './AppContext.js';
|
||||
import { deprecatedHistory } from './utils/deprecatedHistory.js';
|
||||
import reportRecoverableError from './reportRecoverableError.js';
|
||||
|
||||
export interface RunClientAppOptions {
|
||||
app: AppExport;
|
||||
|
|
@ -32,6 +34,7 @@ export interface RunClientAppOptions {
|
|||
dataLoaderDecorator?: Function;
|
||||
}
|
||||
|
||||
|
||||
export default async function runClientApp(options: RunClientAppOptions) {
|
||||
const {
|
||||
app,
|
||||
|
|
@ -105,20 +108,14 @@ export default async function runClientApp(options: RunClientAppOptions) {
|
|||
|
||||
const needHydrate = hydrate && !downgrade && !documentOnly;
|
||||
if (needHydrate) {
|
||||
const defaultOnRecoverableError = typeof reportError === 'function' ? reportError
|
||||
: function (error: unknown) {
|
||||
console['error'](error);
|
||||
};
|
||||
runtime.setRender((container, element) => {
|
||||
const hydrateOptions = revalidate
|
||||
? {
|
||||
onRecoverableError(error: unknown) {
|
||||
// Ignore this error caused by router.revalidate
|
||||
if ((error as Error)?.message?.indexOf('This Suspense boundary received an update before it finished hydrating.') == -1) {
|
||||
defaultOnRecoverableError(error);
|
||||
}
|
||||
},
|
||||
} : {};
|
||||
const hydrateOptions: ReactDOM.HydrationOptions = {
|
||||
// @ts-ignore react-dom do not define the type of second argument of onRecoverableError.
|
||||
onRecoverableError: appConfig?.app?.onRecoverableError ||
|
||||
((error: unknown, errorInfo: ErrorStack) => {
|
||||
reportRecoverableError(error, errorInfo, { ignoreRuntimeWarning: revalidate });
|
||||
}),
|
||||
};
|
||||
return ReactDOM.hydrateRoot(container, element, hydrateOptions);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,14 @@ type App = Partial<{
|
|||
rootId: string;
|
||||
strict: boolean;
|
||||
errorBoundary: boolean;
|
||||
onRecoverableError: (error: unknown, errorInfo: ErrorStack) => void;
|
||||
} & Record<AppLifecycle, VoidFunction>>;
|
||||
|
||||
export interface ErrorStack {
|
||||
componentStack?: string;
|
||||
digest?: string;
|
||||
}
|
||||
|
||||
export type AppData = any;
|
||||
export type RouteData = any;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue