From 22dc19601e2d83e3db3ec86eb2a274934284ac05 Mon Sep 17 00:00:00 2001 From: Adam Hines Date: Wed, 8 May 2024 23:00:40 -0600 Subject: [PATCH] fix(error-logging): rollup errors weren't displaying id and codeframe (#16540) --- packages/vite/src/node/build.ts | 53 +++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 4bc57ce58f76aa..0d849624f4d25f 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -545,7 +545,39 @@ export async function build( }, } - const mergeRollupError = (e: RollupError) => { + /** + * The stack string usually contains a copy of the message at the start of the stack. + * If the stack starts with the message, we remove it and just return the stack trace + * portion. Otherwise the original stack trace is used. + */ + function extractStack(e: RollupError) { + const { stack, name = 'Error', message } = e + + // If we don't have a stack, not much we can do. + if (!stack) { + return stack + } + + const expectedPrefix = `${name}: ${message}\n` + if (stack.startsWith(expectedPrefix)) { + return stack.slice(expectedPrefix.length) + } + + return stack + } + + /** + * Esbuild code frames have newlines at the start and end of the frame, rollup doesn't + * This function normalizes the frame to match the esbuild format which has more pleasing padding + */ + const normalizeCodeFrame = (frame: string) => { + const trimmedPadding = frame.replace(/^\n|\n$/g, '') + return `\n${trimmedPadding}\n` + } + + const enhanceRollupError = (e: RollupError) => { + const stackOnly = extractStack(e) + let msg = colors.red((e.plugin ? `[${e.plugin}] ` : '') + e.message) if (e.id) { msg += `\nfile: ${colors.cyan( @@ -553,15 +585,24 @@ export async function build( )}` } if (e.frame) { - msg += `\n` + colors.yellow(e.frame) + msg += `\n` + colors.yellow(normalizeCodeFrame(e.frame)) + } + + e.message = msg + + // We are rebuilding the stack trace to include the more detailed message at the top. + // Previously this code was relying on mutating e.message changing the generated stack + // when it was accessed, but we don't have any guarantees that the error we are working + // with hasn't already had its stack accessed before we get here. + if (stackOnly !== undefined) { + e.stack = `${e.message}\n${stackOnly}` } - return msg } const outputBuildError = (e: RollupError) => { - const msg = mergeRollupError(e) + enhanceRollupError(e) clearLine() - config.logger.error(msg, { error: e }) + config.logger.error(e.message, { error: e }) } let bundle: RollupBuild | undefined @@ -727,7 +768,7 @@ export async function build( ) return Array.isArray(outputs) ? res : res[0] } catch (e) { - e.message = mergeRollupError(e) + enhanceRollupError(e) clearLine() if (startTime) { config.logger.error(