diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 39b3e4fb15acf4..b0cf31ebaad9b4 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -541,7 +541,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( @@ -549,15 +581,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 @@ -711,7 +752,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(