Skip to content

Commit

Permalink
refactor(error-overlay): improve server code for webpack/Turbopack mi…
Browse files Browse the repository at this point in the history
…ddleware (#62396)
  • Loading branch information
balazsorban44 committed Mar 6, 2024
1 parent 1ad3963 commit 4c62c30
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 328 deletions.
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

0 comments on commit 4c62c30

Please sign in to comment.