@@ -24,8 +24,6 @@ export default function tailwindcss(): Plugin[] {
2424 // to manually rebuild the css file after the compilation is done.
2525 let cssPlugins : readonly Plugin [ ] = [ ]
2626
27- let roots : DefaultMap < string , Root > = new DefaultMap ( ( id ) => new Root ( id ) )
28-
2927 // The Vite extension has two types of sources for candidates:
3028 //
3129 // 1. The module graph: These are all modules that vite transforms and we want
@@ -45,164 +43,9 @@ export default function tailwindcss(): Plugin[] {
4543 let moduleGraphCandidates = new Set < string > ( )
4644 let moduleGraphScanner = new Scanner ( { } )
4745
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+ )
20649
20750 function scanFile ( id : string , content : string , extension : string , isSSR : boolean ) {
20851 let updated = false
@@ -487,3 +330,166 @@ class DefaultMap<K, V> extends Map<K, V> {
487330 return value
488331 }
489332}
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