@@ -7,6 +7,9 @@ import { pathToFileURL } from 'node:url';
77
88import { createFilter } from '@rollup/pluginutils' ;
99import type { PostCSSLoaderOptions , Rspack } from '@rsbuild/core' ;
10+ import type { Processor } from 'postcss' ;
11+
12+ import { isSubsetOf } from './Set.prototype.isSubsetOf.js' ;
1013
1114/**
1215 * The options for {@link TailwindRspackPlugin}.
@@ -176,6 +179,11 @@ export type { TailwindRspackPluginOptions };
176179class TailwindRspackPluginImpl {
177180 name = 'TailwindRspackPlugin' ;
178181
182+ static #postcssProcessorCache = new Map <
183+ /** entryName */ string ,
184+ [ entryModules : ReadonlySet < string > , Processor ]
185+ > ( ) ;
186+
179187 constructor (
180188 private compiler : Rspack . Compiler ,
181189 private options : TailwindRspackPluginOptions ,
@@ -185,7 +193,6 @@ class TailwindRspackPluginImpl {
185193 resolve : compiler . options . context ! ,
186194 } ) ;
187195
188- const { RawSource } = compiler . webpack . sources ;
189196 compiler . hooks . thisCompilation . tap ( this . name , ( compilation ) => {
190197 compilation . hooks . processAssets . tapPromise ( this . name , async ( ) => {
191198 await Promise . all (
@@ -202,6 +209,20 @@ class TailwindRspackPluginImpl {
202209 return ;
203210 }
204211
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+
205226 // collect all the modules corresponding to specific entry
206227 const entryModules = new Set < string > ( ) ;
207228
@@ -213,6 +234,18 @@ class TailwindRspackPluginImpl {
213234 }
214235 }
215236
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+
216249 const [
217250 { default : postcss } ,
218251 { default : tailwindcss } ,
@@ -226,7 +259,7 @@ class TailwindRspackPluginImpl {
226259 ) ,
227260 ] ) ;
228261
229- const postcssTransform = postcss ( [
262+ const processor = postcss ( [
230263 ...( options . postcssOptions ?. plugins ?? [ ] ) ,
231264 // We use a config path to avoid performance issue of TailwindCSS
232265 // See: https://github.com/tailwindlabs/tailwindcss/issues/14229
@@ -235,30 +268,43 @@ class TailwindRspackPluginImpl {
235268 } ) ,
236269 ] ) ;
237270
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 ) ;
255277 } ,
256278 ) ,
257279 ) ;
258280 } ) ;
259281 } ) ;
260282 }
261283
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+
262308 async ensureTempDir ( entryName : string ) : Promise < string > {
263309 const prefix = path . join ( tmpdir ( ) , entryName ) ;
264310 await mkdir ( path . dirname ( prefix ) , { recursive : true } ) ;
0 commit comments