Auto source detection improvements (#14820)

This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.

By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.

If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.

E.g.:

```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```

Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:

E.g.:

```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```

Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:

```css
/* ./src/index.css */
@import 'tailwindcss';

/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```

However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.

```css
/* ./src/index.css */
@import 'tailwindcss';

@source '../resources/views';
```

If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.

```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);

/* Only look at .blade.php files, nothing else  */
@source "../resources/views/**/*.blade.php";
```

Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.

```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);

/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This commit is contained in:
Robin Malfait 2024-10-29 21:33:34 +01:00 • committed by GitHub
parent c439cdf43c
commit d68a780f98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 2341 additions and 606 deletions

View file

@ -137,21 +137,34 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
fullRebuildPaths.push(path)
},
})
let sources = (() => {
// Disable auto source detection
if (compiler.root === 'none') {
return []
}
// No root specified, use the base directory
if (compiler.root === null) {
return [{ base, pattern: '**/*' }]
}
// Use the specified root
return [compiler.root]
})().concat(compiler.globs)
let scanner = new Scanner({ sources })
env.DEBUG && console.timeEnd('[@tailwindcss/cli] Setup compiler')
return compiler
return [compiler, scanner] as const
}
// Compile the input
let compiler = await createCompiler(input)
let scanner = new Scanner({
detectSources: { base },
sources: compiler.globs,
})
let [compiler, scanner] = await createCompiler(input)
// Watch for changes
if (args['--watch']) {
let cleanupWatchers = await createWatchers(
watchDirectories(base, scanner),
watchDirectories(scanner),
async function handle(files) {
try {
// If the only change happened to the output file, then we don't want to
@ -205,13 +218,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
fullRebuildPaths = inputFilePath ? [inputFilePath] : []
// Create a new compiler, given the new `input`
compiler = await createCompiler(input)
// Re-scan the directory to get the new `candidates`
scanner = new Scanner({
detectSources: { base },
sources: compiler.globs,
})
;[compiler, scanner] = await createCompiler(input)
// Scan the directory for candidates
env.DEBUG && console.time('[@tailwindcss/cli] Scan for candidates')
@ -219,7 +226,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
env.DEBUG && console.timeEnd('[@tailwindcss/cli] Scan for candidates')
// Setup new watchers
cleanupWatchers = await createWatchers(watchDirectories(base, scanner), handle)
cleanupWatchers = await createWatchers(watchDirectories(scanner), handle)
// Re-compile the CSS
env.DEBUG && console.time('[@tailwindcss/cli] Build CSS')
@ -287,19 +294,16 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
eprintln(`Done in ${formatDuration(end - start)}`)
}
function watchDirectories(base: string, scanner: Scanner) {
return [base].concat(
scanner.globs.flatMap((globEntry) => {
// We don't want a watcher for negated globs.
if (globEntry.pattern[0] === '!') return []
function watchDirectories(scanner: Scanner) {
return scanner.globs.flatMap((globEntry) => {
// We don't want a watcher for negated globs.
if (globEntry.pattern[0] === '!') return []
// We don't want a watcher for nested directories, these will be covered
// by the `base` directory already.
if (globEntry.base.startsWith(base)) return []
// We don't want a watcher for files, only directories.
if (globEntry.pattern === '') return []
return globEntry.base
}),
)
return globEntry.base
})
}
async function createWatchers(dirs: string[], cb: (files: string[]) => void) {