@@ -7,6 +7,9 @@ import { pathToFileURL } from 'node:url';
7
7
8
8
import { createFilter } from '@rollup/pluginutils' ;
9
9
import type { PostCSSLoaderOptions , Rspack } from '@rsbuild/core' ;
10
+ import type { Processor } from 'postcss' ;
11
+
12
+ import { isSubsetOf } from './Set.prototype.isSubsetOf.js' ;
10
13
11
14
/**
12
15
* The options for {@link TailwindRspackPlugin}.
@@ -176,6 +179,11 @@ export type { TailwindRspackPluginOptions };
176
179
class TailwindRspackPluginImpl {
177
180
name = 'TailwindRspackPlugin' ;
178
181
182
+ static #postcssProcessorCache = new Map <
183
+ /** entryName */ string ,
184
+ [ entryModules : ReadonlySet < string > , Processor ]
185
+ > ( ) ;
186
+
179
187
constructor (
180
188
private compiler : Rspack . Compiler ,
181
189
private options : TailwindRspackPluginOptions ,
@@ -185,7 +193,6 @@ class TailwindRspackPluginImpl {
185
193
resolve : compiler . options . context ! ,
186
194
} ) ;
187
195
188
- const { RawSource } = compiler . webpack . sources ;
189
196
compiler . hooks . thisCompilation . tap ( this . name , ( compilation ) => {
190
197
compilation . hooks . processAssets . tapPromise ( this . name , async ( ) => {
191
198
await Promise . all (
@@ -202,6 +209,20 @@ class TailwindRspackPluginImpl {
202
209
return ;
203
210
}
204
211
212
+ const cache =
213
+ TailwindRspackPluginImpl . #postcssProcessorCache. get ( entryName ) ;
214
+ if ( compiler . modifiedFiles && cache ) {
215
+ const [ cachedEntryModules , cachedPostcssProcessor ] = cache ;
216
+ if ( isSubsetOf ( compiler . modifiedFiles , cachedEntryModules ) ) {
217
+ await this . #transformCSSAssets(
218
+ compilation ,
219
+ cachedPostcssProcessor ,
220
+ cssFiles ,
221
+ ) ;
222
+ return ;
223
+ }
224
+ }
225
+
205
226
// collect all the modules corresponding to specific entry
206
227
const entryModules = new Set < string > ( ) ;
207
228
@@ -213,6 +234,18 @@ class TailwindRspackPluginImpl {
213
234
}
214
235
}
215
236
237
+ if ( compiler . modifiedFiles && cache ) {
238
+ const [ cachedEntryModules , cachedPostcssProcessor ] = cache ;
239
+ if ( isSubsetOf ( entryModules , cachedEntryModules ) ) {
240
+ await this . #transformCSSAssets(
241
+ compilation ,
242
+ cachedPostcssProcessor ,
243
+ cssFiles ,
244
+ ) ;
245
+ return ;
246
+ }
247
+ }
248
+
216
249
const [
217
250
{ default : postcss } ,
218
251
{ default : tailwindcss } ,
@@ -226,7 +259,7 @@ class TailwindRspackPluginImpl {
226
259
) ,
227
260
] ) ;
228
261
229
- const postcssTransform = postcss ( [
262
+ const processor = postcss ( [
230
263
...( options . postcssOptions ?. plugins ?? [ ] ) ,
231
264
// We use a config path to avoid performance issue of TailwindCSS
232
265
// See: https://github.com/tailwindlabs/tailwindcss/issues/14229
@@ -235,30 +268,43 @@ class TailwindRspackPluginImpl {
235
268
} ) ,
236
269
] ) ;
237
270
238
- // iterate all css asset in entry and inject entry modules into tailwind content
239
- await Promise . all (
240
- cssFiles . map ( async ( asset ) => {
241
- const content = asset . source . source ( ) ;
242
- // transform .css which contains tailwind mixin
243
- // FIXME: add custom postcss config
244
- const transformResult = await postcssTransform . process (
245
- content ,
246
- { from : asset . name , ...options . postcssOptions } ,
247
- ) ;
248
- // FIXME: add sourcemap support
249
- compilation . updateAsset (
250
- asset . name ,
251
- new RawSource ( transformResult . css ) ,
252
- ) ;
253
- } ) ,
254
- ) ;
271
+ TailwindRspackPluginImpl . #postcssProcessorCache. set ( entryName , [
272
+ entryModules ,
273
+ processor ,
274
+ ] ) ;
275
+
276
+ await this . #transformCSSAssets( compilation , processor , cssFiles ) ;
255
277
} ,
256
278
) ,
257
279
) ;
258
280
} ) ;
259
281
} ) ;
260
282
}
261
283
284
+ async #transformCSSAssets(
285
+ compilation : Rspack . Compilation ,
286
+ postcssProcessor : Processor ,
287
+ cssFiles : Array < Rspack . Asset > ,
288
+ ) {
289
+ const { RawSource } = this . compiler . webpack . sources ;
290
+
291
+ // iterate all css asset in entry and inject entry modules into tailwind content
292
+ await Promise . all (
293
+ cssFiles . map ( async ( asset ) => {
294
+ const content = asset . source . source ( ) ;
295
+ // transform .css which contains tailwind mixin
296
+ // FIXME: add custom postcss config
297
+ const transformResult = await postcssProcessor . process ( content , {
298
+ from : asset . name ,
299
+ ...this . options . postcssOptions ,
300
+ } ) ;
301
+ // FIXME: avoid `updateAsset` when no change is found.
302
+ // FIXME: add sourcemap support
303
+ compilation . updateAsset ( asset . name , new RawSource ( transformResult . css ) ) ;
304
+ } ) ,
305
+ ) ;
306
+ }
307
+
262
308
async ensureTempDir ( entryName : string ) : Promise < string > {
263
309
const prefix = path . join ( tmpdir ( ) , entryName ) ;
264
310
await mkdir ( path . dirname ( prefix ) , { recursive : true } ) ;
0 commit comments