-
Notifications
You must be signed in to change notification settings - Fork 27.2k
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
Code frame and sourcemapped error support for Turbopack #56727
Changes from 3 commits
998c03d
b6f5e22
ed6c1ad
e193bfa
964b756
75d6d32
da8d778
14f9129
ede8ad8
f2069e4
8d93a00
d70a8f6
a3d8856
f76097e
f2da815
ced85d6
a4ad562
3fb2a2c
fe432d3
8af63bc
3307238
a9da295
002571d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,57 +9,88 @@ import { codeFrameColumns } from '@babel/code-frame' | |
import { launchEditor } from './internal/helpers/launchEditor' | ||
|
||
interface Project { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure how to import these from the |
||
traceSource(stackFrame: RustStackFrame): Promise< | ||
| { | ||
frame: RustStackFrame | ||
source: string | ||
} | ||
| undefined | ||
> | ||
getSourceForAsset(filePath: string): Promise<string | null> | ||
traceSource(stackFrame: RustStackFrame): Promise<RustStackFrame | null> | ||
} | ||
|
||
interface RustStackFrame { | ||
file: string | ||
methodName: string | undefined | ||
methodName: string | null | ||
line: number | ||
column: number | undefined | ||
column: number | null | ||
} | ||
|
||
export async function createOriginalStackFrame( | ||
project: Project, | ||
frame: StackFrame | ||
): Promise<OriginalStackFrameResponse | null> { | ||
const source = await project.traceSource({ | ||
file: frame.file ?? '<unknown>', | ||
const currentSourcesByFile: Map<string, Promise<string | null>> = new Map() | ||
async function batchedTraceSource(project: Project, frame: StackFrame) { | ||
const file = frame.file | ||
if (!file) { | ||
return | ||
} | ||
|
||
const rustStackFrame = { | ||
file, | ||
methodName: frame.methodName, | ||
line: frame.lineNumber ?? 0, | ||
column: frame.column ?? undefined, | ||
}) | ||
column: frame.column, | ||
} | ||
|
||
if (!source) { | ||
return null | ||
const sourceFrame = await project.traceSource(rustStackFrame) | ||
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')) { | ||
let sourcePromise = currentSourcesByFile.get(sourceFrame.file) | ||
if (!sourcePromise) { | ||
sourcePromise = new Promise((resolve) => | ||
// Batch reading sources content as this can be quite large, and stacks often reference the same files | ||
setTimeout(resolve, 100) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we could change this to immediately execute, and keep in in memory for 100ms after it resolves? That way we don't add a 100ms delay to the actual tracing. |
||
).then(() => project.getSourceForAsset(sourceFrame.file)) | ||
currentSourcesByFile.set(sourceFrame.file, sourcePromise) | ||
} | ||
|
||
source = await sourcePromise | ||
currentSourcesByFile.delete(sourceFrame.file) | ||
} | ||
|
||
return { | ||
originalStackFrame: { | ||
file: source.frame.file, | ||
lineNumber: source.frame.line, | ||
column: source.frame.column ?? null, | ||
methodName: source.frame.methodName ?? frame.methodName, | ||
frame: { | ||
file, | ||
lineNumber: sourceFrame.line, | ||
column: sourceFrame.column, | ||
methodName: sourceFrame.methodName ?? frame.methodName, | ||
arguments: [], | ||
}, | ||
originalCodeFrame: source.frame.file.includes('node_modules') | ||
? null | ||
: codeFrameColumns( | ||
source.source, | ||
{ | ||
start: { | ||
line: source.frame.line, | ||
column: source.frame.column ?? 0, | ||
source: source ?? null, | ||
} | ||
} | ||
|
||
export async function createOriginalStackFrame( | ||
project: Project, | ||
frame: StackFrame | ||
): Promise<OriginalStackFrameResponse | null> { | ||
const traced = await batchedTraceSource(project, frame) | ||
if (!traced) { | ||
return null | ||
} | ||
|
||
return { | ||
originalStackFrame: traced.frame, | ||
originalCodeFrame: | ||
traced.source === null || traced.frame.file.includes('node_modules') | ||
? null | ||
: codeFrameColumns( | ||
traced.source, | ||
{ | ||
start: { | ||
line: traced.frame.lineNumber, | ||
column: traced.frame.column ?? 0, | ||
}, | ||
}, | ||
}, | ||
{ forceColor: true } | ||
), | ||
{ forceColor: true } | ||
), | ||
} | ||
} | ||
|
||
|
@@ -78,11 +109,7 @@ function stackFrameFromQuery(query: ParsedUrlQuery): StackFrame { | |
} | ||
|
||
export function getOverlayMiddleware(project: Project) { | ||
return async function ( | ||
req: IncomingMessage, | ||
res: ServerResponse, | ||
next: (error?: Error) => unknown | ||
) { | ||
return async function (req: IncomingMessage, res: ServerResponse) { | ||
const { pathname, query } = url.parse(req.url!, true) | ||
if (pathname === '/__nextjs_original-stack-frame') { | ||
const frame = stackFrameFromQuery(query) | ||
|
@@ -92,27 +119,31 @@ export function getOverlayMiddleware(project: Project) { | |
} catch (e: any) { | ||
res.statusCode = 500 | ||
res.write(e.message) | ||
return res.end() | ||
res.end() | ||
return | ||
} | ||
|
||
if (originalStackFrame === null) { | ||
res.statusCode = 404 | ||
res.write('Unable to resolve sourcemap') | ||
return res.end() | ||
res.end() | ||
return | ||
} | ||
|
||
res.statusCode = 200 | ||
res.setHeader('Content-Type', 'application/json') | ||
res.write(Buffer.from(JSON.stringify(originalStackFrame))) | ||
return res.end() | ||
res.end() | ||
return | ||
} else if (pathname === '/__nextjs_launch-editor') { | ||
const frame = stackFrameFromQuery(query) | ||
|
||
const filePath = frame.file?.toString() | ||
if (filePath === undefined) { | ||
res.statusCode = 400 | ||
res.write('Bad Request') | ||
return res.end() | ||
res.end() | ||
return | ||
} | ||
|
||
const fileExists = await fs.access(filePath, FS.F_OK).then( | ||
|
@@ -122,7 +153,8 @@ export function getOverlayMiddleware(project: Project) { | |
if (!fileExists) { | ||
res.statusCode = 204 | ||
res.write('No Content') | ||
return res.end() | ||
res.end() | ||
return | ||
} | ||
|
||
try { | ||
|
@@ -131,13 +163,12 @@ export function getOverlayMiddleware(project: Project) { | |
console.log('Failed to launch editor:', err) | ||
res.statusCode = 500 | ||
res.write('Internal Server Error') | ||
return res.end() | ||
res.end() | ||
return | ||
} | ||
|
||
res.statusCode = 204 | ||
return res.end() | ||
res.end() | ||
} | ||
|
||
return next() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be derived form somewhere from wherever has these names set, instead of us hardcoding again?