@@ -10,6 +10,7 @@ import type {
1010 OutputAsset ,
1111 OutputChunk ,
1212 RenderedChunk ,
13+ RenderedModule ,
1314 RollupError ,
1415 SourceMapInput ,
1516} from 'rollup'
@@ -452,69 +453,12 @@ export function cssPlugin(config: ResolvedConfig): Plugin {
452453 return plugin
453454}
454455
455- const createStyleContentMap = ( ) => {
456- const contents = new Map < string , string > ( ) // css id -> css content
457- const scopedIds = new Set < string > ( ) // ids of css that are scoped
458- const relations = new Map <
459- /* the id of the target for which css is scoped to */ string ,
460- Array < {
461- /** css id */ id : string
462- /** export name */ exp : string | undefined
463- } >
464- > ( )
465-
466- return {
467- putContent (
468- id : string ,
469- content : string ,
470- scopeTo : CustomPluginOptionsVite [ 'cssScopeTo' ] | undefined ,
471- ) {
472- contents . set ( id , content )
473- if ( scopeTo ) {
474- const [ scopedId , exp ] = scopeTo
475- if ( ! relations . has ( scopedId ) ) {
476- relations . set ( scopedId , [ ] )
477- }
478- relations . get ( scopedId ) ! . push ( { id, exp } )
479- scopedIds . add ( id )
480- }
481- } ,
482- hasContentOfNonScoped ( id : string ) {
483- return ! scopedIds . has ( id ) && contents . has ( id )
484- } ,
485- getContentOfNonScoped ( id : string ) {
486- if ( scopedIds . has ( id ) ) return
487- return contents . get ( id )
488- } ,
489- hasContentsScopedTo ( id : string ) {
490- return ( relations . get ( id ) ?? [ ] ) ?. length > 0
491- } ,
492- getContentsScopedTo ( id : string , importedIds : readonly string [ ] ) {
493- const values = ( relations . get ( id ) ?? [ ] ) . map (
494- ( { id, exp } ) =>
495- [
496- id ,
497- {
498- content : contents . get ( id ) ?? '' ,
499- exp,
500- } ,
501- ] as const ,
502- )
503- const styleIdToValue = new Map ( values )
504- // get a sorted output by import order to make output deterministic
505- return importedIds
506- . filter ( ( id ) => styleIdToValue . has ( id ) )
507- . map ( ( id ) => styleIdToValue . get ( id ) ! )
508- } ,
509- }
510- }
511-
512456/**
513457 * Plugin applied after user plugins
514458 */
515459export function cssPostPlugin ( config : ResolvedConfig ) : Plugin {
516460 // styles initialization in buildStart causes a styling loss in watch
517- const styles = createStyleContentMap ( )
461+ const styles = new Map < string , string > ( )
518462 // queue to emit css serially to guarantee the files are emitted in a deterministic order
519463 let codeSplitEmitQueue = createSerialPromiseQueue < string > ( )
520464 const urlEmitQueue = createSerialPromiseQueue < unknown > ( )
@@ -663,19 +607,9 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
663607
664608 // build CSS handling ----------------------------------------------------
665609
666- const cssScopeTo =
667- // NOTE: `this.getModuleInfo` can be undefined when the plugin is called directly
668- // adding `?.` temporary to avoid unocss from breaking
669- // TODO: remove `?.` after `this.getModuleInfo` in Vite 7
670- (
671- this . getModuleInfo ?.( id ) ?. meta ?. vite as
672- | CustomPluginOptionsVite
673- | undefined
674- ) ?. cssScopeTo
675-
676610 // record css
677611 if ( ! inlined ) {
678- styles . putContent ( id , css , cssScopeTo )
612+ styles . set ( id , css )
679613 }
680614
681615 let code : string
@@ -697,41 +631,49 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
697631 map : { mappings : '' } ,
698632 // avoid the css module from being tree-shaken so that we can retrieve
699633 // it in renderChunk()
700- moduleSideEffects :
701- modulesCode || inlined || cssScopeTo ? false : 'no-treeshake' ,
634+ moduleSideEffects : modulesCode || inlined ? false : 'no-treeshake' ,
702635 }
703636 } ,
704637 } ,
705638
706- async renderChunk ( code , chunk , opts ) {
639+ async renderChunk ( code , chunk , opts , meta ) {
707640 let chunkCSS = ''
641+ const renderedModules = Object . fromEntries (
642+ Object . values ( meta . chunks ) . flatMap ( ( chunk ) =>
643+ Object . entries ( chunk . modules ) ,
644+ ) ,
645+ )
708646 // the chunk is empty if it's a dynamic entry chunk that only contains a CSS import
709647 const isJsChunkEmpty = code === '' && ! chunk . isEntry
710648 let isPureCssChunk = chunk . exports . length === 0
711649 const ids = Object . keys ( chunk . modules )
712650 for ( const id of ids ) {
713- if ( styles . hasContentOfNonScoped ( id ) ) {
651+ if ( styles . has ( id ) ) {
714652 // ?transform-only is used for ?url and shouldn't be included in normal CSS chunks
715- if ( ! transformOnlyRE . test ( id ) ) {
716- chunkCSS += styles . getContentOfNonScoped ( id )
717- // a css module contains JS, so it makes this not a pure css chunk
718- if ( cssModuleRE . test ( id ) ) {
719- isPureCssChunk = false
720- }
653+ if ( transformOnlyRE . test ( id ) ) {
654+ continue
721655 }
722- } else if ( styles . hasContentsScopedTo ( id ) ) {
723- const renderedExports = chunk . modules [ id ] ! . renderedExports
724- const importedIds = this . getModuleInfo ( id ) ?. importedIds ?? [ ]
725- // If this module has scoped styles, check for the rendered exports
726- // and include the corresponding CSS.
727- for ( const { exp, content } of styles . getContentsScopedTo (
728- id ,
729- importedIds ,
730- ) ) {
731- if ( exp === undefined || renderedExports . includes ( exp ) ) {
732- chunkCSS += content
733- }
656+
657+ // If this CSS is scoped to its importers exports, check if those importers exports
658+ // are rendered in the chunks. If they are not, we can skip bundling this CSS.
659+ const cssScopeTo = (
660+ this . getModuleInfo ( id ) ?. meta ?. vite as
661+ | CustomPluginOptionsVite
662+ | undefined
663+ ) ?. cssScopeTo
664+ if (
665+ cssScopeTo &&
666+ ! isCssScopeToRendered ( cssScopeTo , renderedModules )
667+ ) {
668+ continue
669+ }
670+
671+ // a css module contains JS, so it makes this not a pure css chunk
672+ if ( cssModuleRE . test ( id ) ) {
673+ isPureCssChunk = false
734674 }
675+
676+ chunkCSS += styles . get ( id )
735677 } else if ( ! isJsChunkEmpty ) {
736678 // if the module does not have a style, then it's not a pure css chunk.
737679 // this is true because in the `transform` hook above, only modules
@@ -826,13 +768,13 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
826768 path . basename ( originalFileName ) ,
827769 '.css' ,
828770 )
829- if ( ! styles . hasContentOfNonScoped ( id ) ) {
771+ if ( ! styles . has ( id ) ) {
830772 throw new Error (
831773 `css content for ${ JSON . stringify ( id ) } was not found` ,
832774 )
833775 }
834776
835- let cssContent = styles . getContentOfNonScoped ( id ) !
777+ let cssContent = styles . get ( id ) !
836778
837779 cssContent = resolveAssetUrlsInCss ( cssContent , cssAssetName )
838780
@@ -1201,6 +1143,17 @@ export function cssAnalysisPlugin(config: ResolvedConfig): Plugin {
12011143 }
12021144}
12031145
1146+ function isCssScopeToRendered (
1147+ cssScopeTo : Exclude < CustomPluginOptionsVite [ 'cssScopeTo' ] , undefined > ,
1148+ renderedModules : Record < string , RenderedModule | undefined > ,
1149+ ) {
1150+ const [ importerId , exp ] = cssScopeTo
1151+ const importer = renderedModules [ importerId ]
1152+ return (
1153+ importer && ( exp === undefined || importer . renderedExports . includes ( exp ) )
1154+ )
1155+ }
1156+
12041157/**
12051158 * Create a replacer function that takes code and replaces given pure CSS chunk imports
12061159 * @param pureCssChunkNames The chunks that only contain pure CSS and should be replaced
0 commit comments