Repository: vercel/next.js
Pull request: [dev-overlay] Inject `get*Stack` implementation

The stitched-errors module will live in a different layer than the dev-overlay.
In that future we need to pass the `get*Stack` implementations to the dev-overlay.

Right now it's not meaningful but makes future diffs smaller.

Changed files:
filename: packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx
patch: @@ -44,6 +44,7 @@ import type { DevIndicatorServerState } from '../../../../server/dev/dev-indicat
 import reportHmrLatency from '../utils/report-hmr-latency'
 import { TurbopackHmr } from '../utils/turbopack-hot-reloader-common'
 import { NEXT_HMR_REFRESH_HASH_COOKIE } from '../../app-router-headers'
+import { getComponentStack, getOwnerStack } from '../../errors/stitched-error'
 
 export interface Dispatcher {
   onBuildOk(): void
@@ -479,7 +480,11 @@ export default function HotReload({
   children: ReactNode
   globalError: [GlobalErrorComponent, React.ReactNode]
 }) {
-  const [state, dispatch] = useErrorOverlayReducer('app')
+  const [state, dispatch] = useErrorOverlayReducer(
+    'app',
+    getComponentStack,
+    getOwnerStack
+  )
 
   const dispatcher = useMemo<Dispatcher>(() => {
     return {

filename: packages/next/src/client/components/react-dev-overlay/pages/hooks.ts
patch: @@ -2,9 +2,14 @@ import React from 'react'
 import * as Bus from './bus'
 import { useErrorOverlayReducer } from '../shared'
 import { Router } from '../../../router'
+import { getComponentStack, getOwnerStack } from '../../errors/stitched-error'
 
 export const usePagesDevOverlay = () => {
-  const [state, dispatch] = useErrorOverlayReducer('pages')
+  const [state, dispatch] = useErrorOverlayReducer(
+    'pages',
+    getComponentStack,
+    getOwnerStack
+  )
 
   React.useEffect(() => {
     Bus.on(dispatch)

filename: packages/next/src/client/components/react-dev-overlay/shared.ts
patch: @@ -7,7 +7,6 @@ import type { DebugInfo } from './types'
 import type { DevIndicatorServerState } from '../../../server/dev/dev-indicator-server-state'
 import type { HMR_ACTION_TYPES } from '../../../server/dev/hot-reloader-types'
 import { parseStack } from './utils/parse-stack'
-import { getComponentStack, getOwnerStack } from '../errors/stitched-error'
 
 type FastRefreshState =
   /** No refresh in progress. */
@@ -130,38 +129,6 @@ function getStackIgnoringStrictMode(stack: string | undefined) {
   return stack?.split(REACT_ERROR_STACK_BOTTOM_FRAME_REGEX)[0]
 }
 
-function pushErrorFilterDuplicates(
-  events: SupportedErrorEvent[],
-  id: number,
-  error: Error
-): SupportedErrorEvent[] {
-  const componentStack = getComponentStack(error)
-  const componentStackFrames =
-    componentStack === undefined
-      ? undefined
-      : parseComponentStack(componentStack)
-  const ownerStack = getOwnerStack(error)
-  const frames = parseStack((error.stack || '') + (ownerStack || ''))
-  const pendingEvent: SupportedErrorEvent = {
-    id,
-    error,
-    frames,
-    componentStackFrames,
-  }
-  const pendingEvents = events.filter((event) => {
-    // Filter out duplicate errors
-    return (
-      (event.error.stack !== pendingEvent.error.stack &&
-        // TODO: Let ReactDevTools control deduping instead?
-        getStackIgnoringStrictMode(event.error.stack) !==
-          getStackIgnoringStrictMode(pendingEvent.error.stack)) ||
-      getOwnerStack(event.error) !== getOwnerStack(pendingEvent.error)
-    )
-  })
-  pendingEvents.push(pendingEvent)
-  return pendingEvents
-}
-
 const shouldDisableDevIndicator =
   process.env.__NEXT_DEV_INDICATOR?.toString() === 'false'
 
@@ -199,7 +166,43 @@ function getInitialState(
   }
 }
 
-export function useErrorOverlayReducer(routerType: 'pages' | 'app') {
+export function useErrorOverlayReducer(
+  routerType: 'pages' | 'app',
+  getComponentStack: (error: Error) => string | undefined,
+  getOwnerStack: (error: Error) => string | null | undefined
+) {
+  function pushErrorFilterDuplicates(
+    events: SupportedErrorEvent[],
+    id: number,
+    error: Error
+  ): SupportedErrorEvent[] {
+    const componentStack = getComponentStack(error)
+    const componentStackFrames =
+      componentStack === undefined
+        ? undefined
+        : parseComponentStack(componentStack)
+    const ownerStack = getOwnerStack(error)
+    const frames = parseStack((error.stack || '') + (ownerStack || ''))
+    const pendingEvent: SupportedErrorEvent = {
+      id,
+      error,
+      frames,
+      componentStackFrames,
+    }
+    const pendingEvents = events.filter((event) => {
+      // Filter out duplicate errors
+      return (
+        (event.error.stack !== pendingEvent.error.stack &&
+          // TODO: Let ReactDevTools control deduping instead?
+          getStackIgnoringStrictMode(event.error.stack) !==
+            getStackIgnoringStrictMode(pendingEvent.error.stack)) ||
+        getOwnerStack(event.error) !== getOwnerStack(pendingEvent.error)
+      )
+    })
+    pendingEvents.push(pendingEvent)
+    return pendingEvents
+  }
+
   return useReducer((state: OverlayState, action: BusEvent): OverlayState => {
     switch (action.type) {
       case ACTION_DEBUG_INFO: {