@@ -24,8 +24,6 @@ export default function tailwindcss(): Plugin[] {
24
24
// to manually rebuild the css file after the compilation is done.
25
25
let cssPlugins : readonly Plugin [ ] = [ ]
26
26
27
- let roots : DefaultMap < string , Root > = new DefaultMap ( ( id ) => new Root ( id ) )
28
-
29
27
// The Vite extension has two types of sources for candidates:
30
28
//
31
29
// 1. The module graph: These are all modules that vite transforms and we want
@@ -45,164 +43,9 @@ export default function tailwindcss(): Plugin[] {
45
43
let moduleGraphCandidates = new Set < string > ( )
46
44
let moduleGraphScanner = new Scanner ( { } )
47
45
48
- class Root {
49
- // Content is only used in serve mode where we need to capture the initial
50
- // contents of the root file so that we can restore it during the
51
- // `renderStart` hook.
52
- public lastContent : string = ''
53
-
54
- // The lazily-initialized Tailwind compiler components. These are persisted
55
- // throughout rebuilds but will be re-initialized if the rebuild strategy is
56
- // set to `full`.
57
- private compiler ?: Awaited < ReturnType < typeof compile > >
58
-
59
- private rebuildStrategy : 'full' | 'incremental' = 'full'
60
-
61
- // This is the compiler-specific scanner instance that is used only to scan
62
- // files for custom @source paths. All other modules we scan for candidates
63
- // will use the shared moduleGraphScanner instance.
64
- private scanner ?: Scanner
65
-
66
- // List of all candidates that were being returned by the root scanner
67
- // during the lifetime of the root.
68
- private candidates : Set < string > = new Set < string > ( )
69
-
70
- // List of all file dependencies that were captured while generating the
71
- // root. These are retained so we can clear the require cache when we
72
- // rebuild the root.
73
- private dependencies = new Set < string > ( )
74
-
75
- constructor ( private id : string ) { }
76
-
77
- // Generate the CSS for the root file. This can return false if the file is
78
- // not considered a Tailwind root. When this happened, the root can be GCed.
79
- public async generate (
80
- content : string ,
81
- addWatchFile : ( file : string ) => void ,
82
- ) : Promise < string | false > {
83
- await import ( '@tailwindcss/node/esm-cache-hook' )
84
-
85
- this . lastContent = content
86
-
87
- let inputPath = idToPath ( this . id )
88
- let inputBase = path . dirname ( path . resolve ( inputPath ) )
89
-
90
- if ( this . compiler === null || this . scanner === null || this . rebuildStrategy === 'full' ) {
91
- this . rebuildStrategy = 'incremental'
92
- clearRequireCache ( Array . from ( this . dependencies ) )
93
- this . dependencies = new Set ( [ idToPath ( inputPath ) ] )
94
-
95
- let postcssCompiled = await postcss ( [
96
- postcssImport ( {
97
- load : ( path ) => {
98
- this . dependencies . add ( path )
99
- addWatchFile ( path )
100
- return fs . readFile ( path , 'utf8' )
101
- } ,
102
- } ) ,
103
- fixRelativePathsPlugin ( ) ,
104
- ] ) . process ( content , {
105
- from : inputPath ,
106
- to : inputPath ,
107
- } )
108
- let css = postcssCompiled . css
109
-
110
- // This is done inside the Root#generate() method so that we can later
111
- // use information from the Tailwind compiler to determine if the file
112
- // is a CSS root (necessary because we will probably inline the
113
- // `@import` resolution at some point).
114
- if ( ! isCssRootFile ( css ) ) {
115
- return false
116
- }
117
-
118
- this . compiler = await compile ( css , {
119
- loadPlugin : async ( pluginPath ) => {
120
- if ( pluginPath [ 0 ] !== '.' ) {
121
- return import ( pluginPath ) . then ( ( m ) => m . default ?? m )
122
- }
123
-
124
- let resolvedPath = path . resolve ( inputBase , pluginPath )
125
- let [ module , moduleDependencies ] = await Promise . all ( [
126
- import ( pathToFileURL ( resolvedPath ) . href + '?id=' + Date . now ( ) ) ,
127
- getModuleDependencies ( resolvedPath ) ,
128
- ] )
129
- addWatchFile ( resolvedPath )
130
- this . dependencies . add ( resolvedPath )
131
- for ( let file of moduleDependencies ) {
132
- addWatchFile ( file )
133
- this . dependencies . add ( file )
134
- }
135
- return module . default ?? module
136
- } ,
137
-
138
- loadConfig : async ( configPath ) => {
139
- if ( configPath [ 0 ] !== '.' ) {
140
- return import ( configPath ) . then ( ( m ) => m . default ?? m )
141
- }
142
-
143
- let resolvedPath = path . resolve ( inputBase , configPath )
144
- let [ module , moduleDependencies ] = await Promise . all ( [
145
- import ( pathToFileURL ( resolvedPath ) . href + '?id=' + Date . now ( ) ) ,
146
- getModuleDependencies ( resolvedPath ) ,
147
- ] )
148
-
149
- addWatchFile ( resolvedPath )
150
- this . dependencies . add ( resolvedPath )
151
- for ( let file of moduleDependencies ) {
152
- addWatchFile ( file )
153
- this . dependencies . add ( file )
154
- }
155
- return module . default ?? module
156
- } ,
157
- } )
158
- this . scanner = new Scanner ( {
159
- sources : this . compiler . globs . map ( ( pattern ) => ( {
160
- base : inputBase , // Globs are relative to the input.css file
161
- pattern,
162
- } ) ) ,
163
- } )
164
- }
165
-
166
- if ( ! this . scanner || ! this . compiler ) {
167
- // TypeScript does not properly refine the scanner and compiler
168
- // properties (even when extracted into a variable)
169
- throw new Error ( 'Tailwind CSS compiler is not initialized.' )
170
- }
171
-
172
- // This should not be here, but right now the Vite plugin is setup where
173
- // we setup a new scanner and compiler every time we request the CSS file
174
- // (regardless whether it actually changed or not).
175
- for ( let candidate of this . scanner . scan ( ) ) {
176
- this . candidates . add ( candidate )
177
- }
178
-
179
- // Watch individual files found via custom `@source` paths
180
- for ( let file of this . scanner . files ) {
181
- addWatchFile ( file )
182
- }
183
-
184
- // Watch globs found via custom `@source` paths
185
- for ( let glob of this . scanner . globs ) {
186
- if ( glob . pattern [ 0 ] === '!' ) continue
187
-
188
- let relative = path . relative ( config ! . base , glob . base )
189
- if ( relative [ 0 ] !== '.' ) {
190
- relative = './' + relative
191
- }
192
- // Ensure relative is a posix style path since we will merge it with the
193
- // glob.
194
- relative = normalizePath ( relative )
195
-
196
- addWatchFile ( path . posix . join ( relative , glob . pattern ) )
197
- }
198
-
199
- return this . compiler . build ( [ ...moduleGraphCandidates , ...this . candidates ] )
200
- }
201
-
202
- public invalidate ( ) {
203
- this . rebuildStrategy = 'full'
204
- }
205
- }
46
+ let roots : DefaultMap < string , Root > = new DefaultMap (
47
+ ( id ) => new Root ( id , ( ) => moduleGraphCandidates , config ! . base ) ,
48
+ )
206
49
207
50
function scanFile ( id : string , content : string , extension : string , isSSR : boolean ) {
208
51
let updated = false
@@ -487,3 +330,166 @@ class DefaultMap<K, V> extends Map<K, V> {
487
330
return value
488
331
}
489
332
}
333
+
334
+ class Root {
335
+ // Content is only used in serve mode where we need to capture the initial
336
+ // contents of the root file so that we can restore it during the
337
+ // `renderStart` hook.
338
+ public lastContent : string = ''
339
+
340
+ // The lazily-initialized Tailwind compiler components. These are persisted
341
+ // throughout rebuilds but will be re-initialized if the rebuild strategy is
342
+ // set to `full`.
343
+ private compiler ?: Awaited < ReturnType < typeof compile > >
344
+
345
+ private rebuildStrategy : 'full' | 'incremental' = 'full'
346
+
347
+ // This is the compiler-specific scanner instance that is used only to scan
348
+ // files for custom @source paths. All other modules we scan for candidates
349
+ // will use the shared moduleGraphScanner instance.
350
+ private scanner ?: Scanner
351
+
352
+ // List of all candidates that were being returned by the root scanner during
353
+ // the lifetime of the root.
354
+ private candidates : Set < string > = new Set < string > ( )
355
+
356
+ // List of all file dependencies that were captured while generating the root.
357
+ // These are retained so we can clear the require cache when we rebuild the
358
+ // root.
359
+ private dependencies = new Set < string > ( )
360
+
361
+ constructor (
362
+ private id : string ,
363
+ private getSharedCandidates : ( ) => Set < string > ,
364
+ private base : string ,
365
+ ) { }
366
+
367
+ // Generate the CSS for the root file. This can return false if the file is
368
+ // not considered a Tailwind root. When this happened, the root can be GCed.
369
+ public async generate (
370
+ content : string ,
371
+ addWatchFile : ( file : string ) => void ,
372
+ ) : Promise < string | false > {
373
+ await import ( '@tailwindcss/node/esm-cache-hook' )
374
+
375
+ this . lastContent = content
376
+
377
+ let inputPath = idToPath ( this . id )
378
+ let inputBase = path . dirname ( path . resolve ( inputPath ) )
379
+
380
+ if ( this . compiler === null || this . scanner === null || this . rebuildStrategy === 'full' ) {
381
+ this . rebuildStrategy = 'incremental'
382
+ clearRequireCache ( Array . from ( this . dependencies ) )
383
+ this . dependencies = new Set ( [ idToPath ( inputPath ) ] )
384
+
385
+ let postcssCompiled = await postcss ( [
386
+ postcssImport ( {
387
+ load : ( path ) => {
388
+ this . dependencies . add ( path )
389
+ addWatchFile ( path )
390
+ return fs . readFile ( path , 'utf8' )
391
+ } ,
392
+ } ) ,
393
+ fixRelativePathsPlugin ( ) ,
394
+ ] ) . process ( content , {
395
+ from : inputPath ,
396
+ to : inputPath ,
397
+ } )
398
+ let css = postcssCompiled . css
399
+
400
+ // This is done inside the Root#generate() method so that we can later use
401
+ // information from the Tailwind compiler to determine if the file is a
402
+ // CSS root (necessary because we will probably inline the `@import`
403
+ // resolution at some point).
404
+ if ( ! isCssRootFile ( css ) ) {
405
+ return false
406
+ }
407
+
408
+ this . compiler = await compile ( css , {
409
+ loadPlugin : async ( pluginPath ) => {
410
+ if ( pluginPath [ 0 ] !== '.' ) {
411
+ return import ( pluginPath ) . then ( ( m ) => m . default ?? m )
412
+ }
413
+
414
+ let resolvedPath = path . resolve ( inputBase , pluginPath )
415
+ let [ module , moduleDependencies ] = await Promise . all ( [
416
+ import ( pathToFileURL ( resolvedPath ) . href + '?id=' + Date . now ( ) ) ,
417
+ getModuleDependencies ( resolvedPath ) ,
418
+ ] )
419
+ addWatchFile ( resolvedPath )
420
+ this . dependencies . add ( resolvedPath )
421
+ for ( let file of moduleDependencies ) {
422
+ addWatchFile ( file )
423
+ this . dependencies . add ( file )
424
+ }
425
+ return module . default ?? module
426
+ } ,
427
+
428
+ loadConfig : async ( configPath ) => {
429
+ if ( configPath [ 0 ] !== '.' ) {
430
+ return import ( configPath ) . then ( ( m ) => m . default ?? m )
431
+ }
432
+
433
+ let resolvedPath = path . resolve ( inputBase , configPath )
434
+ let [ module , moduleDependencies ] = await Promise . all ( [
435
+ import ( pathToFileURL ( resolvedPath ) . href + '?id=' + Date . now ( ) ) ,
436
+ getModuleDependencies ( resolvedPath ) ,
437
+ ] )
438
+
439
+ addWatchFile ( resolvedPath )
440
+ this . dependencies . add ( resolvedPath )
441
+ for ( let file of moduleDependencies ) {
442
+ addWatchFile ( file )
443
+ this . dependencies . add ( file )
444
+ }
445
+ return module . default ?? module
446
+ } ,
447
+ } )
448
+ this . scanner = new Scanner ( {
449
+ sources : this . compiler . globs . map ( ( pattern ) => ( {
450
+ base : inputBase , // Globs are relative to the input.css file
451
+ pattern,
452
+ } ) ) ,
453
+ } )
454
+ }
455
+
456
+ if ( ! this . scanner || ! this . compiler ) {
457
+ // TypeScript does not properly refine the scanner and compiler properties
458
+ // (even when extracted into a variable)
459
+ throw new Error ( 'Tailwind CSS compiler is not initialized.' )
460
+ }
461
+
462
+ // This should not be here, but right now the Vite plugin is setup where we
463
+ // setup a new scanner and compiler every time we request the CSS file
464
+ // (regardless whether it actually changed or not).
465
+ for ( let candidate of this . scanner . scan ( ) ) {
466
+ this . candidates . add ( candidate )
467
+ }
468
+
469
+ // Watch individual files found via custom `@source` paths
470
+ for ( let file of this . scanner . files ) {
471
+ addWatchFile ( file )
472
+ }
473
+
474
+ // Watch globs found via custom `@source` paths
475
+ for ( let glob of this . scanner . globs ) {
476
+ if ( glob . pattern [ 0 ] === '!' ) continue
477
+
478
+ let relative = path . relative ( this . base , glob . base )
479
+ if ( relative [ 0 ] !== '.' ) {
480
+ relative = './' + relative
481
+ }
482
+ // Ensure relative is a posix style path since we will merge it with the
483
+ // glob.
484
+ relative = normalizePath ( relative )
485
+
486
+ addWatchFile ( path . posix . join ( relative , glob . pattern ) )
487
+ }
488
+
489
+ return this . compiler . build ( [ ...this . getSharedCandidates ( ) , ...this . candidates ] )
490
+ }
491
+
492
+ public invalidate ( ) {
493
+ this . rebuildStrategy = 'full'
494
+ }
495
+ }
0 commit comments