@@ -498,11 +498,34 @@ async function bundleTsdown() {
498498 await copyFile ( join ( tsdownSourceDir , 'client.d.ts' ) , join ( projectDir , 'dist/tsdown/client.d.ts' ) ) ;
499499}
500500
501- // Ensure a bundled chunk imports the given ansis color helpers (e.g. `bold`,
502- // `red`) from the shared `main-*.js` chunk. tsdown's logger module does not
503- // import every color the Vite+ branding uses, so after the logger patches we
504- // add any missing ones, resolving their (minified) export aliases from main's
505- // own `export { ... }` map so the fix survives rolldown renaming them.
501+ // Whether `name` is already a top-level binding of this chunk. Depending on
502+ // rolldown's chunking, ansis is sometimes inlined into the logger chunk itself,
503+ // so the colors are local declarations (a single big
504+ // `const { ..., bold, ..., red, ... } = ...` destructure) instead of imports.
505+ // Those are already in scope, so no import must be added for them.
506+ function isDeclaredInChunk ( content : string , name : string ) : boolean {
507+ if ( new RegExp ( `(?:^|[;{}\\s])(?:const|let|var|function|class)\\s+${ name } \\b` , 'm' ) . test ( content ) ) {
508+ return true ;
509+ }
510+ const destructureRe = / (?: c o n s t | l e t | v a r ) \s * \{ ( [ ^ } ] * ) \} \s * = / g;
511+ for ( const [ , bindings ] of content . matchAll ( destructureRe ) ) {
512+ for ( const binding of bindings . split ( ',' ) ) {
513+ // `a`, `a: b` (local is `b`) and `a = fallback` all bind a local name.
514+ const local = binding . split ( ':' ) . pop ( ) ?. split ( '=' ) [ 0 ] ?. trim ( ) ;
515+ if ( local === name ) {
516+ return true ;
517+ }
518+ }
519+ }
520+ return false ;
521+ }
522+
523+ // Ensure a bundled chunk can reference the given ansis color helpers (e.g.
524+ // `bold`, `red`). tsdown's logger module does not import every color the Vite+
525+ // branding uses, so after the logger patches we add any that are neither
526+ // declared in the chunk nor already imported, resolving their (minified) export
527+ // aliases from the providing chunk's own `export { ... }` map so the fix
528+ // survives rolldown renaming them.
506529async function ensureAnsisImports (
507530 content : string ,
508531 names : string [ ] ,
@@ -515,9 +538,6 @@ async function ensureAnsisImports(
515538 // chunk actually re-exports it.
516539 const importRe = / i m p o r t \{ ( [ ^ } ] * ) \} f r o m " ( \. \/ [ ^ " ] + \. j s ) " ; / g;
517540 const imports = [ ...content . matchAll ( importRe ) ] ;
518- if ( imports . length === 0 ) {
519- throw new Error ( 'ensureAnsisImports: no relative chunk import found in branded logger chunk' ) ;
520- }
521541
522542 // Every binding already in scope across all imports (its local name).
523543 const localNames = new Set < string > ( ) ;
@@ -531,10 +551,15 @@ async function ensureAnsisImports(
531551 localNames . add ( aliased ? aliased [ 1 ] : trimmed ) ;
532552 }
533553 }
534- const missing = names . filter ( ( name ) => ! localNames . has ( name ) ) ;
554+ const missing = names . filter (
555+ ( name ) => ! localNames . has ( name ) && ! isDeclaredInChunk ( content , name ) ,
556+ ) ;
535557 if ( missing . length === 0 ) {
536558 return content ;
537559 }
560+ if ( imports . length === 0 ) {
561+ throw new Error ( 'ensureAnsisImports: no relative chunk import found in branded logger chunk' ) ;
562+ }
538563
539564 // Group missing colors by the imported chunk that re-exports them. Chunks
540565 // re-export colors as `<local> as <alias>` (e.g. `bold as i`); the consumer
0 commit comments