Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(error-overlay): improve server code for webpack/Turbopack middleware #62396

Merged
merged 46 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
595c340
refactor(error-overlay): improve server code for webpack/turbopack mi…
balazsorban44 Feb 22, 2024
d0ff9bf
update snapshots
balazsorban44 Feb 22, 2024
02d2624
tweak URL parsing
balazsorban44 Feb 22, 2024
0502163
don't look up code frames for `node_modules`
balazsorban44 Feb 22, 2024
24e60b1
revert snapshots
balazsorban44 Feb 22, 2024
82cbbe8
increase color contrast for better a11y
balazsorban44 Feb 23, 2024
ad54559
prefer `display: flex` + `gap`
balazsorban44 Feb 23, 2024
e7ef697
more refactor
balazsorban44 Feb 23, 2024
8c181c4
don't generate code frames for internals
balazsorban44 Feb 23, 2024
15a555c
Merge branch 'canary' into chore/cleanup-error-overlay-server
balazsorban44 Feb 23, 2024
e47e034
revert client changes
balazsorban44 Feb 23, 2024
5b26026
drop _
balazsorban44 Feb 23, 2024
6fa7fdc
unify StackFrame interface
balazsorban44 Feb 23, 2024
9e06b86
fix type errors
balazsorban44 Feb 23, 2024
a507bdd
Merge branch 'canary' into chore/cleanup-error-overlay-server
balazsorban44 Feb 26, 2024
8af0c1f
fix condition
balazsorban44 Feb 26, 2024
5f9ebae
move more to shared
balazsorban44 Feb 26, 2024
a76733b
fix condition, lineNumber<>line inconsistency
balazsorban44 Feb 26, 2024
cace243
Merge branch 'canary' into chore/cleanup-error-overlay-server
balazsorban44 Feb 26, 2024
ceeb547
filter `''` from arguments
balazsorban44 Feb 26, 2024
d4e399c
Merge branch 'chore/cleanup-error-overlay-server' of https://github.c…
balazsorban44 Feb 26, 2024
eae8cf7
line -> lineNumber
balazsorban44 Feb 26, 2024
14b4c13
Merge branch 'canary' into chore/cleanup-error-overlay-server
balazsorban44 Feb 26, 2024
be853e2
`lineNumber`
balazsorban44 Feb 27, 2024
941ae2f
correctly propagate error message from dev bundler
balazsorban44 Feb 27, 2024
3ffb9ba
Merge branch 'canary' into chore/cleanup-error-overlay-server
balazsorban44 Feb 27, 2024
c0896f3
fix import
balazsorban44 Feb 27, 2024
f73a987
fix type lint error
balazsorban44 Feb 27, 2024
43c1c52
fix interface
balazsorban44 Feb 27, 2024
73438d6
Merge branch 'canary' into chore/cleanup-error-overlay-server
balazsorban44 Mar 4, 2024
5f74cf5
fix type and import
balazsorban44 Mar 4, 2024
ba63cc1
separate turbopack stackframe interface
balazsorban44 Mar 5, 2024
91857b9
fix `line`<>`lineNumber` inconsistencies
balazsorban44 Mar 5, 2024
153de03
fix build
balazsorban44 Mar 5, 2024
d14fd7a
fix types
balazsorban44 Mar 5, 2024
d6afaa0
more type fix
balazsorban44 Mar 5, 2024
3bd3595
address review
balazsorban44 Mar 6, 2024
b50405f
address review
balazsorban44 Mar 6, 2024
fb1ac00
revert
balazsorban44 Mar 6, 2024
2d5fc79
revert
balazsorban44 Mar 6, 2024
128a5b3
import `StackFrame` from directly from `next/dist/compiled/stacktrace…
balazsorban44 Mar 6, 2024
292962f
move
balazsorban44 Mar 6, 2024
1ead129
better match rust struct
balazsorban44 Mar 6, 2024
7d5c121
move import
balazsorban44 Mar 6, 2024
ad2fee7
fix import
balazsorban44 Mar 6, 2024
a9d7101
fix build
balazsorban44 Mar 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions packages/next/src/build/swc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,12 +600,16 @@ export interface HmrIdentifiers {
identifiers: string[]
}

interface TurbopackStackFrame {
column: number | null
file: string
/** @see https://github.com/vercel/next.js/blob/415cd74b9a220b6f50da64da68c13043e9b02995/packages/next-swc/crates/napi/src/next_api/project.rs#L824-L833 */
export interface TurbopackStackFrame {
isServer: boolean
line: number
methodName: string | null
isInternal?: boolean
file: string
/** 1-indexed, unlike source map tokens */
line?: number
/** 1-indexed, unlike source map tokens */
column?: number | null
methodName?: string
}

export type UpdateMessage =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,21 @@ async function getSourceFrame(
compilation: any
): Promise<{ frame: string; lineNumber: string; column: string }> {
try {
const loc = input.loc
? input.loc
: input.dependencies.map((d: any) => d.loc).filter(Boolean)[0]
const loc =
input.loc || input.dependencies.map((d: any) => d.loc).filter(Boolean)[0]
const originalSource = input.module.originalSource()

const result = await createOriginalStackFrame({
line: loc.start.line,
column: loc.start.column,
source: originalSource,
rootDirectory: compilation.options.context!,
modulePath: fileName,
frame: {},
frame: {
arguments: [],
file: fileName,
methodName: '',
lineNumber: loc.start.line,
column: loc.start.column,
},
})

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react'
import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'
import {
getFrameSource,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,55 +1,44 @@
import type { IncomingMessage, ServerResponse } from 'http'
import { findSourcePackage, type OriginalStackFrameResponse } from './shared'
import {
badRequest,
findSourcePackage,
getOriginalCodeFrame,
internalServerError,
json,
noContent,
type OriginalStackFrameResponse,
} from './shared'

import fs, { constants as FS } from 'fs/promises'
import { codeFrameColumns } from 'next/dist/compiled/babel/code-frame'
import { launchEditor } from '../internal/helpers/launchEditor'

interface Project {
getSourceForAsset(filePath: string): Promise<string | null>
traceSource(
stackFrame: TurbopackStackFrame
): Promise<TurbopackStackFrame | null>
}

interface TurbopackStackFrame {
// 1-based
column: number | null
// 1-based
file: string
isServer: boolean
line: number | null
methodName: string | null
isInternal?: boolean
}
import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'
import type { Project, TurbopackStackFrame } from '../../../../build/swc'

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

const sourceFrame = await project.traceSource(frame)

if (!sourceFrame) {
return
}

let source
// Don't show code frames for node_modules. These can also often be large bundled files.
if (!sourceFrame.file.includes('node_modules') && !sourceFrame.isInternal) {
if (!sourceFrame) return

let source = null
// 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)
) {
let sourcePromise = currentSourcesByFile.get(sourceFrame.file)
if (!sourcePromise) {
sourcePromise = project.getSourceForAsset(sourceFrame.file)
currentSourcesByFile.set(sourceFrame.file, sourcePromise)
setTimeout(() => {
// Cache file reads for 100ms, as frames will often reference the same
// files and can be large.
currentSourcesByFile.delete(sourceFrame.file)
currentSourcesByFile.delete(sourceFrame.file!)
}, 100)
}

Expand All @@ -59,12 +48,12 @@ export async function batchedTraceSource(
return {
frame: {
file: sourceFrame.file,
lineNumber: sourceFrame.line,
column: sourceFrame.column,
lineNumber: sourceFrame.line ?? 0,
column: sourceFrame.column ?? 0,
methodName: sourceFrame.methodName ?? frame.methodName ?? '<unknown>',
arguments: [],
},
source: source ?? null,
source,
}
}

Expand All @@ -74,29 +63,15 @@ export async function createOriginalStackFrame(
): Promise<OriginalStackFrameResponse | null> {
const traced = await batchedTraceSource(project, frame)
if (!traced) {
const sourcePackage = findSourcePackage(frame.file)
const sourcePackage = findSourcePackage(frame)
if (sourcePackage) return { sourcePackage }
return null
}

return {
originalStackFrame: traced.frame,
originalCodeFrame:
traced.source === null
? null
: codeFrameColumns(
traced.source,
{
start: {
// 1-based, but -1 means start line without highlighting
line: traced.frame.lineNumber ?? -1,
// 1-based, but 0 means whole line without column highlighting
column: traced.frame.column ?? 0,
},
},
{ forceColor: true }
),
sourcePackage: findSourcePackage(traced.frame.file),
originalCodeFrame: getOriginalCodeFrame(traced.frame, traced.source),
sourcePackage: findSourcePackage(traced.frame),
}
}

Expand All @@ -106,7 +81,7 @@ export function getOverlayMiddleware(project: Project) {

const frame = {
file: searchParams.get('file') as string,
methodName: searchParams.get('methodName'),
methodName: searchParams.get('methodName') ?? '<unknown>',
line: parseInt(searchParams.get('lineNumber') ?? '0', 10) || 0,
column: parseInt(searchParams.get('column') ?? '0', 10) || 0,
isServer: searchParams.get('isServer') === 'true',
Expand All @@ -117,55 +92,32 @@ export function getOverlayMiddleware(project: Project) {
try {
originalStackFrame = await createOriginalStackFrame(project, frame)
} catch (e: any) {
res.statusCode = 500
res.write(e.message)
res.end()
return
return internalServerError(res, e.message)
}

if (originalStackFrame === null) {
if (!originalStackFrame) {
res.statusCode = 404
res.write('Unable to resolve sourcemap')
res.end()
return
return res.end('Unable to resolve sourcemap')
}

res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.write(Buffer.from(JSON.stringify(originalStackFrame)))
res.end()
return
return json(res, originalStackFrame)
} else if (pathname === '/__nextjs_launch-editor') {
if (!frame.file) {
res.statusCode = 400
res.write('Bad Request')
res.end()
return
}
if (!frame.file) return badRequest(res)

const fileExists = await fs.access(frame.file, FS.F_OK).then(
() => true,
() => false
)
if (!fileExists) {
res.statusCode = 204
res.write('No Content')
res.end()
return
}
if (!fileExists) return noContent(res)

try {
launchEditor(frame.file, frame.line ?? 1, frame.column ?? 1)
} catch (err) {
console.log('Failed to launch editor:', err)
res.statusCode = 500
res.write('Internal Server Error')
res.end()
return
return internalServerError(res)
}

res.statusCode = 204
res.end()
noContent(res)
}
}
}
Loading
Loading