@@ -7,24 +7,36 @@ import type { Plugin, Rollup, Update, ViteDevServer } from 'vite'
77export default function tailwindcss ( ) : Plugin [ ] {
88 let server : ViteDevServer | null = null
99 let candidates = new Set < string > ( )
10- // In serve mode, we treat this as a set, storing storing empty strings .
10+ // In serve mode this is treated as a set — the content doesn't matter .
1111 // In build mode, we store file contents to use them in renderChunk.
12- let cssModules : Record < string , string > = { }
12+ let cssModules : Record <
13+ string ,
14+ {
15+ content : string
16+ handled : boolean
17+ }
18+ > = { }
19+ let isSSR = false
1320 let minify = false
1421 let cssPlugins : readonly Plugin [ ] = [ ]
1522
1623 // Trigger update to all CSS modules
17- function updateCssModules ( ) {
24+ function updateCssModules ( isSSR : boolean ) {
1825 // If we're building then we don't need to update anything
1926 if ( ! server ) return
2027
2128 let updates : Update [ ] = [ ]
2229 for ( let id of Object . keys ( cssModules ) ) {
2330 let cssModule = server . moduleGraph . getModuleById ( id )
2431 if ( ! cssModule ) {
25- // It is safe to remove the item here since we're iterating on a copy of
26- // the keys.
27- delete cssModules [ id ]
32+ // Note: Removing this during SSR is not safe and will produce
33+ // inconsistent results based on the timing of the removal and
34+ // the order / timing of transforms.
35+ if ( ! isSSR ) {
36+ // It is safe to remove the item here since we're iterating on a copy
37+ // of the keys.
38+ delete cssModules [ id ]
39+ }
2840 continue
2941 }
3042
@@ -85,7 +97,7 @@ export default function tailwindcss(): Plugin[] {
8597 try {
8698 // Directly call the plugin's transform function to process the
8799 // generated CSS. In build mode, this updates the chunks later used to
88- // generate the bundle. In serve mode, the transformed souce should be
100+ // generate the bundle. In serve mode, the transformed source should be
89101 // applied in transform.
90102 let result = await transformHandler . call ( transformPluginContext , css , id )
91103 if ( ! result ) continue
@@ -113,16 +125,21 @@ export default function tailwindcss(): Plugin[] {
113125
114126 async configResolved ( config ) {
115127 minify = config . build . cssMinify !== false
116- // Apply the vite:css plugin to generated CSS for transformations like
117- // URL path rewriting and image inlining.
118- //
119- // In build mode, since renderChunk runs after all transformations, we
120- // need to also apply vite:css-post.
121- cssPlugins = config . plugins . filter ( ( plugin ) =>
122- [ 'vite:css' , ...( config . command === 'build' ? [ 'vite:css-post' ] : [ ] ) ] . includes (
123- plugin . name ,
124- ) ,
125- )
128+ isSSR = config . build . ssr !== false && config . build . ssr !== undefined
129+
130+ let allowedPlugins = [
131+ // Apply the vite:css plugin to generated CSS for transformations like
132+ // URL path rewriting and image inlining.
133+ 'vite:css' ,
134+
135+ // In build mode, since renderChunk runs after all transformations, we
136+ // need to also apply vite:css-post.
137+ ...( config . command === 'build' ? [ 'vite:css-post' ] : [ ] ) ,
138+ ]
139+
140+ cssPlugins = config . plugins . filter ( ( plugin ) => {
141+ return allowedPlugins . includes ( plugin . name )
142+ } )
126143 } ,
127144
128145 // Scan index.html for candidates
@@ -134,18 +151,18 @@ export default function tailwindcss(): Plugin[] {
134151 // CSS update will cause an infinite loop. We only trigger if the
135152 // candidates have been updated.
136153 if ( updated ) {
137- updateCssModules ( )
154+ updateCssModules ( isSSR )
138155 }
139156 } ,
140157
141158 // Scan all non-CSS files for candidates
142- transform ( src , id ) {
159+ transform ( src , id , options ) {
143160 if ( id . includes ( '/.vite/' ) ) return
144161 let extension = getExtension ( id )
145162 if ( extension === '' || extension === 'css' ) return
146163
147164 scan ( src , extension )
148- updateCssModules ( )
165+ updateCssModules ( options ?. ssr ?? false )
149166 } ,
150167 } ,
151168
@@ -163,7 +180,7 @@ export default function tailwindcss(): Plugin[] {
163180 if ( ! isTailwindCssFile ( id , src ) ) return
164181
165182 // In serve mode, we treat cssModules as a set, ignoring the value.
166- cssModules [ id ] = ''
183+ cssModules [ id ] = { content : '' , handled : true }
167184
168185 if ( ! options ?. ssr ) {
169186 // Wait until all other files have been processed, so we can extract
@@ -184,15 +201,26 @@ export default function tailwindcss(): Plugin[] {
184201
185202 transform ( src , id ) {
186203 if ( ! isTailwindCssFile ( id , src ) ) return
187- cssModules [ id ] = src
204+ cssModules [ id ] = { content : src , handled : false }
188205 } ,
189206
190207 // renderChunk runs in the bundle generation stage after all transforms.
191208 // We must run before `enforce: post` so the updated chunks are picked up
192209 // by vite:css-post.
193210 async renderChunk ( _code , _chunk ) {
194- for ( let [ cssFile , css ] of Object . entries ( cssModules ) ) {
195- await transformWithPlugins ( this , cssFile , generateOptimizedCss ( css ) )
211+ for ( let [ id , file ] of Object . entries ( cssModules ) ) {
212+ if ( file . handled ) {
213+ continue
214+ }
215+
216+ let css = generateOptimizedCss ( file . content )
217+
218+ // These plugins have side effects which, during build, results in CSS
219+ // being written to the output dir. We need to run them here to ensure
220+ // the CSS is written before the bundle is generated.
221+ await transformWithPlugins ( this , id , css )
222+
223+ file . handled = true
196224 }
197225 } ,
198226 } ,
0 commit comments