@@ -7,24 +7,36 @@ import type { Plugin, Rollup, Update, ViteDevServer } from 'vite'
7
7
export default function tailwindcss ( ) : Plugin [ ] {
8
8
let server : ViteDevServer | null = null
9
9
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 .
11
11
// 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
13
20
let minify = false
14
21
let cssPlugins : readonly Plugin [ ] = [ ]
15
22
16
23
// Trigger update to all CSS modules
17
- function updateCssModules ( ) {
24
+ function updateCssModules ( isSSR : boolean ) {
18
25
// If we're building then we don't need to update anything
19
26
if ( ! server ) return
20
27
21
28
let updates : Update [ ] = [ ]
22
29
for ( let id of Object . keys ( cssModules ) ) {
23
30
let cssModule = server . moduleGraph . getModuleById ( id )
24
31
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
+ }
28
40
continue
29
41
}
30
42
@@ -85,7 +97,7 @@ export default function tailwindcss(): Plugin[] {
85
97
try {
86
98
// Directly call the plugin's transform function to process the
87
99
// 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
89
101
// applied in transform.
90
102
let result = await transformHandler . call ( transformPluginContext , css , id )
91
103
if ( ! result ) continue
@@ -113,16 +125,21 @@ export default function tailwindcss(): Plugin[] {
113
125
114
126
async configResolved ( config ) {
115
127
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
+ } )
126
143
} ,
127
144
128
145
// Scan index.html for candidates
@@ -134,18 +151,18 @@ export default function tailwindcss(): Plugin[] {
134
151
// CSS update will cause an infinite loop. We only trigger if the
135
152
// candidates have been updated.
136
153
if ( updated ) {
137
- updateCssModules ( )
154
+ updateCssModules ( isSSR )
138
155
}
139
156
} ,
140
157
141
158
// Scan all non-CSS files for candidates
142
- transform ( src , id ) {
159
+ transform ( src , id , options ) {
143
160
if ( id . includes ( '/.vite/' ) ) return
144
161
let extension = getExtension ( id )
145
162
if ( extension === '' || extension === 'css' ) return
146
163
147
164
scan ( src , extension )
148
- updateCssModules ( )
165
+ updateCssModules ( options ?. ssr ?? false )
149
166
} ,
150
167
} ,
151
168
@@ -163,7 +180,7 @@ export default function tailwindcss(): Plugin[] {
163
180
if ( ! isTailwindCssFile ( id , src ) ) return
164
181
165
182
// In serve mode, we treat cssModules as a set, ignoring the value.
166
- cssModules [ id ] = ''
183
+ cssModules [ id ] = { content : '' , handled : true }
167
184
168
185
if ( ! options ?. ssr ) {
169
186
// Wait until all other files have been processed, so we can extract
@@ -184,15 +201,26 @@ export default function tailwindcss(): Plugin[] {
184
201
185
202
transform ( src , id ) {
186
203
if ( ! isTailwindCssFile ( id , src ) ) return
187
- cssModules [ id ] = src
204
+ cssModules [ id ] = { content : src , handled : false }
188
205
} ,
189
206
190
207
// renderChunk runs in the bundle generation stage after all transforms.
191
208
// We must run before `enforce: post` so the updated chunks are picked up
192
209
// by vite:css-post.
193
210
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
196
224
}
197
225
} ,
198
226
} ,
0 commit comments