Skip to content

Commit

Permalink
pass down ignored field to error overlay
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi committed Nov 18, 2024
1 parent 997105d commit 886dedb
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 71 deletions.
9 changes: 1 addition & 8 deletions packages/next/src/build/webpack/config/blocks/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,7 @@ import { COMPILER_NAMES } from '../../../../shared/lib/constants'
import type { ConfigurationContext } from '../utils'
import DevToolsIgnorePlugin from '../../plugins/devtools-ignore-list-plugin'
import EvalSourceMapDevToolPlugin from '../../plugins/eval-source-map-dev-tool-plugin'

function shouldIgnorePath(modulePath: string): boolean {
return (
modulePath.includes('node_modules') ||
// Only relevant for when Next.js is symlinked e.g. in the Next.js monorepo
modulePath.includes('next/dist')
)
}
import { shouldIgnorePath } from '../ignore-list'

export const base = curry(function base(
ctx: ConfigurationContext,
Expand Down
7 changes: 7 additions & 0 deletions packages/next/src/build/webpack/config/ignore-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function shouldIgnorePath(modulePath: string): boolean {
return (
modulePath.includes('node_modules') ||
// Only relevant for when Next.js is symlinked e.g. in the Next.js monorepo
modulePath.includes('next/dist')
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const CallStackFrame: React.FC<{

return (
<div data-nextjs-call-stack-frame>
<h3 data-nextjs-frame-expanded={Boolean(frame.expanded)}>
<h3 data-nextjs-frame-expanded={!frame.ignored}>
<HotlinkedText text={formattedMethod} />
</h3>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,29 @@ import { GroupedStackFrames } from './GroupedStackFrames'
export type RuntimeErrorProps = { error: ReadyRuntimeError }

export function RuntimeError({ error }: RuntimeErrorProps) {
const { frames } = error
const { firstFrame, allLeadingFrames, allCallStackFrames } =
React.useMemo(() => {
const filteredFrames = error.frames
// Filter out nodejs internal frames since you can't do anything about them.
// e.g. node:internal/timers shows up pretty often due to timers, but not helpful to users.
// Only present the last line before nodejs internal trace.
.filter((f) => !f.sourceStackFrame.file?.startsWith('node:'))

const firstFirstPartyFrameIndex = filteredFrames.findIndex(
const firstFirstPartyFrameIndex = frames.findIndex(
(entry) =>
entry.expanded &&
!entry.ignored &&
Boolean(entry.originalCodeFrame) &&
Boolean(entry.originalStackFrame)
)

return {
firstFrame: filteredFrames[firstFirstPartyFrameIndex] ?? null,
firstFrame: frames[firstFirstPartyFrameIndex] ?? null,
allLeadingFrames:
firstFirstPartyFrameIndex < 0
? []
: filteredFrames.slice(0, firstFirstPartyFrameIndex),
allCallStackFrames: filteredFrames.slice(firstFirstPartyFrameIndex + 1),
: frames.slice(0, firstFirstPartyFrameIndex),
allCallStackFrames: frames.slice(firstFirstPartyFrameIndex + 1),
}
}, [error.frames])
}, [frames])

const { leadingFramesGroupedByFramework, stackFramesGroupedByFramework } =
React.useMemo(() => {
const leadingFrames = allLeadingFrames.filter((f) => f.expanded)
const leadingFrames = allLeadingFrames.filter((f) => !f.ignored)

return {
stackFramesGroupedByFramework:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface OriginalStackFrame extends OriginalStackFrameResponse {
error: boolean
reason: string | null
external: boolean
expanded: boolean
ignored: boolean
sourceStackFrame: StackFrame
}

Expand Down Expand Up @@ -49,20 +49,15 @@ function getOriginalStackFrame(
error: false,
reason: null,
external: false,
expanded: !Boolean(
/* collapsed */
(source.file?.includes('node_modules') ||
body.originalStackFrame?.file?.includes('node_modules') ||
body.originalStackFrame?.file?.startsWith('[turbopack]/')) ??
true
),
sourceStackFrame: source,
originalStackFrame: body.originalStackFrame,
originalCodeFrame: body.originalCodeFrame || null,
sourcePackage: body.sourcePackage,
ignored: body.originalStackFrame?.ignored || false,
}
}

// TODO: merge this section into ignoredList handling
if (
source.file === '<anonymous>' ||
source.file === 'file://' ||
Expand All @@ -73,23 +68,23 @@ function getOriginalStackFrame(
error: false,
reason: null,
external: true,
expanded: false,
sourceStackFrame: source,
originalStackFrame: null,
originalCodeFrame: null,
sourcePackage: null,
ignored: true,
})
}

return _getOriginalStackFrame().catch((err: Error) => ({
error: true,
reason: err?.message ?? err?.toString() ?? 'Unknown Error',
external: false,
expanded: false,
sourceStackFrame: source,
originalStackFrame: null,
originalCodeFrame: null,
sourcePackage: null,
ignored: false,
}))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,29 @@ import type { Project, TurbopackStackFrame } from '../../../../build/swc/types'
import { getSourceMapFromFile } from '../internal/helpers/get-source-map-from-file'
import { findSourceMap } from 'node:module'

type IgnorableStackFrame = StackFrame & { ignored: boolean }

const currentSourcesByFile: Map<string, Promise<string | null>> = new Map()
export async function batchedTraceSource(
project: Project,
frame: TurbopackStackFrame
): Promise<{ frame: StackFrame; source: string | null } | undefined> {
): Promise<{ frame: IgnorableStackFrame; source: string | null } | undefined> {
const file = frame.file ? decodeURIComponent(frame.file) : undefined
if (!file) return

const sourceFrame = await project.traceSource(frame)
if (!sourceFrame) return

let source = null
let ignored = true
// Don't look up source for node_modules or internals. These can often be large bundled files.
if (
sourceFrame.file &&
!(sourceFrame.file.includes('node_modules') || sourceFrame.isInternal)
!(
sourceFrame.file.includes('node_modules') ||
// isInternal means resource starts with turbopack://[turbopack]
sourceFrame.isInternal
)
) {
let sourcePromise = currentSourcesByFile.get(sourceFrame.file)
if (!sourcePromise) {
Expand All @@ -46,18 +53,22 @@ export async function batchedTraceSource(
currentSourcesByFile.delete(sourceFrame.file!)
}, 100)
}

ignored = false
source = await sourcePromise
}

// TODO: get ignoredList from turbopack source map
const ignorableFrame = {
file: sourceFrame.file,
lineNumber: sourceFrame.line ?? 0,
column: sourceFrame.column ?? 0,
methodName: sourceFrame.methodName ?? frame.methodName ?? '<unknown>',
ignored,
arguments: [],
}

return {
frame: {
file: sourceFrame.file,
lineNumber: sourceFrame.line ?? 0,
column: sourceFrame.column ?? 0,
methodName: sourceFrame.methodName ?? frame.methodName ?? '<unknown>',
arguments: [],
},
frame: ignorableFrame,
source,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,36 @@ export { getSourceMapFromFile }

import type { IncomingMessage, ServerResponse } from 'http'
import type webpack from 'webpack'
import type { RawSourceMap } from 'next/dist/compiled/source-map08'
import type {
NullableMappedPosition,
RawSourceMap,
} from 'next/dist/compiled/source-map08'
import { formatFrameSourceFile } from '../internal/helpers/webpack-module-path'
import { shouldIgnorePath } from '../../../../build/webpack/config/ignore-list'
import type { MappedPosition } from 'source-map'

interface ModernRawSourceMap extends RawSourceMap {
ignoreList?: number[]
}

export interface IgnorableStackFrame extends StackFrame {
ignored: boolean
}

type SourceAttributes = {
sourcePosition: NullableMappedPosition
sourceContent: string | null
}

type Source =
| {
type: 'file'
sourceMap: RawSourceMap
sourceMap: ModernRawSourceMap
modulePath: string
}
| {
type: 'bundle'
sourceMap: RawSourceMap
sourceMap: ModernRawSourceMap
compilation: webpack.Compilation
moduleId: string
modulePath: string
Expand All @@ -56,9 +74,9 @@ function getSourcePath(source: string) {
}

async function findOriginalSourcePositionAndContent(
sourceMap: RawSourceMap,
sourceMap: ModernRawSourceMap,
position: { line: number; column: number | null }
) {
): Promise<SourceAttributes | null> {
const consumer = await new SourceMapConsumer(sourceMap)
try {
const sourcePosition = consumer.originalPositionFor({
Expand All @@ -85,9 +103,23 @@ async function findOriginalSourcePositionAndContent(
}
}

function isIgnoredSource(
source: Source,
sourcePosition: MappedPosition | NullableMappedPosition
) {
if (sourcePosition.source == null) {
return true
}
const sourceIndex = source.sourceMap.sources.indexOf(sourcePosition.source)
const ignored = source.sourceMap.ignoreList?.includes(sourceIndex) ?? false

return ignored
}

function createStackFrame(searchParams: URLSearchParams) {
const file = searchParams.get('file') as string
return {
file: searchParams.get('file') as string,
file,
methodName: searchParams.get('methodName') as string,
lineNumber: parseInt(searchParams.get('lineNumber') ?? '0', 10) || 0,
column: parseInt(searchParams.get('column') ?? '0', 10) || 0,
Expand All @@ -99,7 +131,7 @@ function findOriginalSourcePositionAndContentFromCompilation(
moduleId: string | undefined,
importedModule: string,
compilation: webpack.Compilation
) {
): SourceAttributes | null {
const module = getModuleById(moduleId, compilation)
return module?.buildInfo?.importLocByPath?.get(importedModule) ?? null
}
Expand Down Expand Up @@ -136,17 +168,22 @@ export async function createOriginalStackFrame({
})
})()

if (!result?.sourcePosition.source) {
if (!result) {
return null
}

const { sourcePosition, sourceContent } = result

if (!sourcePosition.source) {
return null
}

const ignored = isIgnoredSource(source, sourcePosition)

const filePath = path.resolve(
rootDirectory,
getSourcePath(
// When sourcePosition.source is the loader path the modulePath is generally better.
(sourcePosition.source.includes('|')
(sourcePosition.source!.includes('|')
? source.modulePath
: sourcePosition.source) || source.modulePath
)
Expand All @@ -156,7 +193,7 @@ export async function createOriginalStackFrame({
? path.relative(rootDirectory, filePath)
: sourcePosition.source

const traced = {
const traced: IgnorableStackFrame = {
file: resolvedFilePath,
lineNumber: sourcePosition.line,
column: (sourcePosition.column ?? 0) + 1,
Expand All @@ -168,7 +205,8 @@ export async function createOriginalStackFrame({
?.replace('__WEBPACK_DEFAULT_EXPORT__', 'default')
?.replace('__webpack_exports__.', ''),
arguments: [],
} satisfies StackFrame
ignored,
}

return {
originalStackFrame: traced,
Expand Down Expand Up @@ -201,7 +239,7 @@ export async function getSourceMapFromCompilation(
}
}

export async function getSource(
async function getSource(
filename: string,
options: {
getCompilations: () => webpack.Compilation[]
Expand Down Expand Up @@ -241,9 +279,30 @@ export async function getSource(
for (const compilation of getCompilations()) {
// TODO: `ignoreList`
const sourceMap = await getSourceMapFromCompilation(moduleId, compilation)
const ignoreList = []
const moduleFilenames = sourceMap?.sources ?? []

// console.log('moduleFilenames', moduleFilenames)
for (let index = 0; index < moduleFilenames.length; index++) {
// bundlerFilePath case: webpack://./app/page.tsx
const bundlerFilePath = moduleFilenames[index]
// Format the path to the normal file path
const formattedFilePath = formatFrameSourceFile(bundlerFilePath)
if (shouldIgnorePath(formattedFilePath)) {
ignoreList.push(index)
}
}

if (sourceMap) {
return { type: 'bundle', sourceMap, compilation, moduleId, modulePath }
const modernSourceMap = sourceMap as ModernRawSourceMap
modernSourceMap.ignoreList = ignoreList
return {
type: 'bundle',
sourceMap: modernSourceMap,
compilation,
moduleId,
modulePath,
}
}
}

Expand All @@ -270,24 +329,11 @@ export function getOverlayMiddleware(options: {
const isEdgeServer = searchParams.get('isEdgeServer') === 'true'
const isAppDirectory = searchParams.get('isAppDirectory') === 'true'
const frame = createStackFrame(searchParams)

let sourcePackage = findSourcePackage(frame)

if (
!(
/^(rsc:\/\/React\/[^/]+\/)?(webpack-internal:\/\/\/|(file|webpack):\/\/)/.test(
frame.file
) && frame.lineNumber
)
) {
if (sourcePackage) return json(res, { sourcePackage })
return badRequest(res)
}

const formattedFilePath = formatFrameSourceFile(frame.file)
const filePath = path.join(rootDirectory, formattedFilePath)
const isNextjsSource = filePath.startsWith(NEXT_PROJECT_ROOT)

let sourcePackage = findSourcePackage(frame)
let source: Source | undefined

if (isNextjsSource) {
Expand Down
Loading

0 comments on commit 886dedb

Please sign in to comment.