@@ -545,23 +545,64 @@ export async function build(
545545 } ,
546546 }
547547
548- const mergeRollupError = ( e : RollupError ) => {
548+ /**
549+ * The stack string usually contains a copy of the message at the start of the stack.
550+ * If the stack starts with the message, we remove it and just return the stack trace
551+ * portion. Otherwise the original stack trace is used.
552+ */
553+ function extractStack ( e : RollupError ) {
554+ const { stack, name = 'Error' , message } = e
555+
556+ // If we don't have a stack, not much we can do.
557+ if ( ! stack ) {
558+ return stack
559+ }
560+
561+ const expectedPrefix = `${ name } : ${ message } \n`
562+ if ( stack . startsWith ( expectedPrefix ) ) {
563+ return stack . slice ( expectedPrefix . length )
564+ }
565+
566+ return stack
567+ }
568+
569+ /**
570+ * Esbuild code frames have newlines at the start and end of the frame, rollup doesn't
571+ * This function normalizes the frame to match the esbuild format which has more pleasing padding
572+ */
573+ const normalizeCodeFrame = ( frame : string ) => {
574+ const trimmedPadding = frame . replace ( / ^ \n | \n $ / g, '' )
575+ return `\n${ trimmedPadding } \n`
576+ }
577+
578+ const enhanceRollupError = ( e : RollupError ) => {
579+ const stackOnly = extractStack ( e )
580+
549581 let msg = colors . red ( ( e . plugin ? `[${ e . plugin } ] ` : '' ) + e . message )
550582 if ( e . id ) {
551583 msg += `\nfile: ${ colors . cyan (
552584 e . id + ( e . loc ? `:${ e . loc . line } :${ e . loc . column } ` : '' ) ,
553585 ) } `
554586 }
555587 if ( e . frame ) {
556- msg += `\n` + colors . yellow ( e . frame )
588+ msg += `\n` + colors . yellow ( normalizeCodeFrame ( e . frame ) )
589+ }
590+
591+ e . message = msg
592+
593+ // We are rebuilding the stack trace to include the more detailed message at the top.
594+ // Previously this code was relying on mutating e.message changing the generated stack
595+ // when it was accessed, but we don't have any guarantees that the error we are working
596+ // with hasn't already had its stack accessed before we get here.
597+ if ( stackOnly !== undefined ) {
598+ e . stack = `${ e . message } \n${ stackOnly } `
557599 }
558- return msg
559600 }
560601
561602 const outputBuildError = ( e : RollupError ) => {
562- const msg = mergeRollupError ( e )
603+ enhanceRollupError ( e )
563604 clearLine ( )
564- config . logger . error ( msg , { error : e } )
605+ config . logger . error ( e . message , { error : e } )
565606 }
566607
567608 let bundle : RollupBuild | undefined
@@ -727,7 +768,7 @@ export async function build(
727768 )
728769 return Array . isArray ( outputs ) ? res : res [ 0 ]
729770 } catch ( e ) {
730- e . message = mergeRollupError ( e )
771+ enhanceRollupError ( e )
731772 clearLine ( )
732773 if ( startTime ) {
733774 config . logger . error (
0 commit comments