@@ -2,6 +2,7 @@ import { compile, env, normalizePath } from '@tailwindcss/node'
22import { clearRequireCache } from '@tailwindcss/node/require-cache'
33import { Scanner } from '@tailwindcss/oxide'
44import { Features , transform } from 'lightningcss'
5+ import fs from 'node:fs/promises'
56import path from 'path'
67import type { Plugin , ResolvedConfig , Rollup , Update , ViteDevServer } from 'vite'
78
@@ -35,7 +36,7 @@ export default function tailwindcss(): Plugin[] {
3536 // Note: To improve performance, we do not remove candidates from this set.
3637 // This means a longer-ongoing dev mode session might contain candidates that
3738 // are no longer referenced in code.
38- let moduleGraphCandidates = new Set < string > ( )
39+ let moduleGraphCandidates = new DefaultMap < string , Set < string > > ( ( ) => new Set < string > ( ) )
3940 let moduleGraphScanner = new Scanner ( { } )
4041
4142 let roots : DefaultMap < string , Root > = new DefaultMap (
@@ -46,7 +47,7 @@ export default function tailwindcss(): Plugin[] {
4647 let updated = false
4748 for ( let candidate of moduleGraphScanner . scanFiles ( [ { content, extension } ] ) ) {
4849 updated = true
49- moduleGraphCandidates . add ( candidate )
50+ moduleGraphCandidates . get ( id ) . add ( candidate )
5051 }
5152
5253 if ( updated ) {
@@ -343,14 +344,16 @@ class Root {
343344 // the lifetime of the root.
344345 private candidates : Set < string > = new Set < string > ( )
345346
346- // List of all file dependencies that were captured while generating the root.
347- // These are retained so we can clear the require cache when we rebuild the
348- // root.
347+ // List of all dependencies captured while generating the root. These are
348+ // retained so we can clear the require cache when we rebuild the root.
349349 private dependencies = new Set < string > ( )
350350
351+ // The resolved path given to `source(…)`. When not given this is `null`.
352+ private basePath : string | null = null
353+
351354 constructor (
352355 private id : string ,
353- private getSharedCandidates : ( ) => Set < string > ,
356+ private getSharedCandidates : ( ) => Map < string , Set < string > > ,
354357 private base : string ,
355358 ) { }
356359
@@ -379,9 +382,22 @@ class Root {
379382 } )
380383 env . DEBUG && console . timeEnd ( '[@tailwindcss/vite] Setup compiler' )
381384
382- this . scanner = new Scanner ( {
383- sources : this . compiler . globs ,
384- } )
385+ let sources = ( ( ) => {
386+ // Disable auto source detection
387+ if ( this . compiler . root === 'none' ) {
388+ return [ ]
389+ }
390+
391+ // No root specified, use the module graph
392+ if ( this . compiler . root === null ) {
393+ return [ ]
394+ }
395+
396+ // Use the specified root
397+ return [ this . compiler . root ]
398+ } ) ( ) . concat ( this . compiler . globs )
399+
400+ this . scanner = new Scanner ( { sources } )
385401 }
386402
387403 // This should not be here, but right now the Vite plugin is setup where we
@@ -411,14 +427,62 @@ class Root {
411427 relative = normalizePath ( relative )
412428
413429 addWatchFile ( path . posix . join ( relative , glob . pattern ) )
430+
431+ let root = this . compiler . root
432+
433+ if ( root !== 'none' && root !== null ) {
434+ let basePath = path . posix . resolve ( root . base , root . pattern )
435+
436+ let isDir = await fs . stat ( basePath ) . then (
437+ ( stats ) => stats . isDirectory ( ) ,
438+ ( ) => false ,
439+ )
440+
441+ if ( ! isDir ) {
442+ throw new Error (
443+ `The path given to \`source(…)\` must be a directory but got \`source(${ basePath } )\` instead.` ,
444+ )
445+ }
446+
447+ this . basePath = basePath
448+ } else if ( root === null ) {
449+ this . basePath = null
450+ }
414451 }
415452
416453 this . requiresRebuild = true
417454
418455 env . DEBUG && console . time ( '[@tailwindcss/vite] Build CSS' )
419- let result = this . compiler . build ( [ ...this . getSharedCandidates ( ) , ...this . candidates ] )
456+ let result = this . compiler . build ( [ ...this . sharedCandidates ( ) , ...this . candidates ] )
420457 env . DEBUG && console . timeEnd ( '[@tailwindcss/vite] Build CSS' )
421458
422459 return result
423460 }
461+
462+ private sharedCandidates ( ) : Set < string > {
463+ if ( ! this . compiler ) return new Set ( )
464+ if ( this . compiler . root === 'none' ) return new Set ( )
465+
466+ let shouldIncludeCandidatesFrom = ( id : string ) => {
467+ if ( this . basePath === null ) return true
468+
469+ // This a virtual module that's not on the file system
470+ // TODO: What should we do here?
471+ if ( ! id . startsWith ( '/' ) ) return true
472+
473+ return id . startsWith ( this . basePath )
474+ }
475+
476+ let shared = new Set < string > ( )
477+
478+ for ( let [ id , candidates ] of this . getSharedCandidates ( ) ) {
479+ if ( ! shouldIncludeCandidatesFrom ( id ) ) continue
480+
481+ for ( let candidate of candidates ) {
482+ shared . add ( candidate )
483+ }
484+ }
485+
486+ return shared
487+ }
424488}
0 commit comments