UNPKG

next

Version:

The React Framework

193 lines (192 loc) • 7.89 kB
import { getOriginalStackFrames as getOriginalStackFramesWebpack } from '../middleware-webpack'; import { getOriginalStackFrames as getOriginalStackFramesTurbopack } from '../middleware-turbopack'; import { dim } from '../../../lib/picocolors'; import { parseStack } from '../../lib/parse-stack'; import path from 'path'; import { LRUCache } from '../../lib/lru-cache'; // TODO: handle server vs browser error source mapping correctly export async function mapFramesUsingBundler(frames, ctx) { switch(ctx.bundler){ case 'webpack': { const { isServer, isEdgeServer, isAppDirectory, clientStats, serverStats, edgeServerStats, rootDirectory } = ctx; const res = await getOriginalStackFramesWebpack({ isServer, isEdgeServer, isAppDirectory, frames, clientStats, serverStats, edgeServerStats, rootDirectory }); return res; } case 'turbopack': { const { project, projectPath, isServer, isEdgeServer, isAppDirectory } = ctx; const res = await getOriginalStackFramesTurbopack({ project, projectPath, frames, isServer, isEdgeServer, isAppDirectory }); return res; } default: { return null; } } } // converts _next/static/chunks/... to file:///.next/static/chunks/... for parseStack // todo: where does next dev overlay handle this case and re-use that logic function preprocessStackTrace(stackTrace, distDir) { return stackTrace.split('\n').map((line)=>{ const match = line.match(/^(\s*at\s+.*?)\s+\(([^)]+)\)$/); if (match) { const [, prefix, location] = match; if (location.startsWith('_next/static/') && distDir) { const normalizedDistDir = distDir.replace(/\\/g, '/').replace(/\/$/, ''); const absolutePath = normalizedDistDir + '/' + location.slice('_next/'.length); const fileUrl = `file://${path.resolve(absolutePath)}`; return `${prefix} (${fileUrl})`; } } return line; }).join('\n'); } const cache = new LRUCache(25); async function getSourceMappedStackFramesInternal(stackTrace, ctx, distDir, ignore = true) { try { var _filteredFrames_find; const normalizedStack = preprocessStackTrace(stackTrace, distDir); const frames = parseStack(normalizedStack, distDir); if (frames.length === 0) { return { kind: 'stack', stack: stackTrace }; } const mappingResults = await mapFramesUsingBundler(frames, ctx); const processedFrames = mappingResults.map((result, index)=>({ result, originalFrame: frames[index] })).map(({ result, originalFrame })=>{ var _originalStackFrame_file; if (result.status === 'rejected') { return { kind: 'rejected', frameText: formatStackFrame(originalFrame), codeFrame: null }; } const { originalStackFrame, originalCodeFrame } = result.value; if ((originalStackFrame == null ? void 0 : originalStackFrame.ignored) && ignore) { return { kind: 'ignored' }; } // should we apply this generally to dev overlay (dev overlay does not ignore chrome-extension://) if (originalStackFrame == null ? void 0 : (_originalStackFrame_file = originalStackFrame.file) == null ? void 0 : _originalStackFrame_file.startsWith('chrome-extension://')) { return { kind: 'ignored' }; } return { kind: 'success', // invariant: if result is not rejected and not ignored, then original stack frame exists // verifiable by tracing `getOriginalStackFrame`. The invariant exists because of bad types frameText: formatStackFrame(originalStackFrame), codeFrame: originalCodeFrame }; }); const allIgnored = processedFrames.every((frame)=>frame.kind === 'ignored'); // we want to handle **all** ignored vs all/some rejected differently // if all are ignored we should show no frames // if all are rejected, we want to fallback to showing original stack frames if (allIgnored) { return { kind: 'all-ignored' }; } const filteredFrames = processedFrames.filter((frame)=>frame.kind !== 'ignored'); if (filteredFrames.length === 0) { return { kind: 'stack', stack: stackTrace }; } const stackOutput = filteredFrames.map((frame)=>frame.frameText).join('\n'); const firstFrameCode = (_filteredFrames_find = filteredFrames.find((frame)=>frame.codeFrame)) == null ? void 0 : _filteredFrames_find.codeFrame; if (firstFrameCode) { return { kind: 'with-frame-code', frameCode: firstFrameCode, stack: stackOutput, frames: filteredFrames }; } // i don't think this a real case, but good for exhaustion return { kind: 'mapped-stack', stack: stackOutput, frames: filteredFrames }; } catch (error) { return { kind: 'stack', stack: stackTrace }; } } // todo: cache the actual async call, not the wrapper with post processing export async function getSourceMappedStackFrames(stackTrace, ctx, distDir, ignore = true) { const cacheKey = `sm_${stackTrace}-${ctx.bundler}-${ctx.isAppDirectory}-${ctx.isEdgeServer}-${ctx.isServer}-${distDir}-${ignore}`; const cacheItem = cache.get(cacheKey); if (cacheItem) { return cacheItem; } const result = await getSourceMappedStackFramesInternal(stackTrace, ctx, distDir, ignore); cache.set(cacheKey, result); return result; } function formatStackFrame(frame) { const functionName = frame.methodName || '<anonymous>'; const location = frame.file && frame.line1 ? `${frame.file}:${frame.line1}${frame.column1 ? `:${frame.column1}` : ''}` : frame.file || '<unknown>'; return ` at ${functionName} (${location})`; } // appends the source mapped location of the console method export const withLocation = async ({ original, stack }, ctx, distDir, config)=>{ if (typeof config === 'object' && config.showSourceLocation === false) { return original; } if (!stack) { return original; } const res = await getSourceMappedStackFrames(stack, ctx, distDir); const location = getConsoleLocation(res); if (!location) { return original; } return [ ...original, dim(`(${location})`) ]; }; export const getConsoleLocation = (mapped)=>{ if (mapped.kind !== 'mapped-stack' && mapped.kind !== 'with-frame-code') { return null; } const first = mapped.frames.at(0); if (!first) { return null; } // we don't want to show the name of parent function (at <fn> thing in stack), just source location for minimal noise const match = first.frameText.match(/\(([^)]+)\)/); const locationText = match ? match[1] : first.frameText; return locationText; }; //# sourceMappingURL=source-map.js.map