Skip to content

Commit 3b2ca85

Browse files
Fix new file detection in PostCSS plugin (#14829)
We broke this at some point — probably when we tried to optimize rebuilds in PostCSS by not performing a full auto-source detection scan. This PR addresses this problem by: 1. Storing a list of found directories 2. Comparing their mod times on every scan 3. If the mod time has changed we scan the directory for new files which we then store and scan
1 parent 94ea5e2 commit 3b2ca85

File tree

7 files changed

+372
-139
lines changed

7 files changed

+372
-139
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
- Nothing yet!
10+
### Fixed
11+
12+
- Detect classes in new files when using `@tailwindcss/postcss` ([#14829](https://github.com/tailwindlabs/tailwindcss/pull/14829))
1113

1214
## [4.0.0-alpha.31] - 2024-10-29
1315

crates/oxide/src/lib.rs

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use glob::optimize_patterns;
99
use glob_match::glob_match;
1010
use paths::Path;
1111
use rayon::prelude::*;
12+
use scanner::allowed_paths::read_dir;
1213
use std::fs;
1314
use std::path::PathBuf;
1415
use std::sync;
@@ -77,6 +78,9 @@ pub struct Scanner {
7778
/// All files that we have to scan
7879
files: Vec<PathBuf>,
7980

81+
/// All directories, sub-directories, etc… we saw during source detection
82+
dirs: Vec<PathBuf>,
83+
8084
/// All generated globs
8185
globs: Vec<GlobEntry>,
8286

@@ -98,7 +102,7 @@ impl Scanner {
98102
pub fn scan(&mut self) -> Vec<String> {
99103
init_tracing();
100104
self.prepare();
101-
105+
self.check_for_new_files();
102106
self.compute_candidates();
103107

104108
let mut candidates: Vec<String> = self.candidates.clone().into_iter().collect();
@@ -213,6 +217,62 @@ impl Scanner {
213217
self.ready = true;
214218
}
215219

220+
#[tracing::instrument(skip_all)]
221+
fn check_for_new_files(&mut self) {
222+
let mut modified_dirs: Vec<PathBuf> = vec![];
223+
224+
// Check all directories to see if they were modified
225+
for path in &self.dirs {
226+
let current_time = fs::metadata(path)
227+
.and_then(|m| m.modified())
228+
.unwrap_or(SystemTime::now());
229+
230+
let previous_time = self.mtimes.insert(path.clone(), current_time);
231+
232+
let should_scan = match previous_time {
233+
// Time has changed, so we need to re-scan the file
234+
Some(prev) if prev != current_time => true,
235+
236+
// File was in the cache, no need to re-scan
237+
Some(_) => false,
238+
239+
// File didn't exist before, so we need to scan it
240+
None => true,
241+
};
242+
243+
if should_scan {
244+
modified_dirs.push(path.clone());
245+
}
246+
}
247+
248+
// Scan all modified directories for their immediate files
249+
let mut known = FxHashSet::from_iter(self.files.iter().chain(self.dirs.iter()).cloned());
250+
251+
while !modified_dirs.is_empty() {
252+
let new_entries = modified_dirs
253+
.iter()
254+
.flat_map(|dir| read_dir(dir, Some(1)))
255+
.map(|entry| entry.path().to_owned())
256+
.filter(|path| !known.contains(path))
257+
.collect::<Vec<_>>();
258+
259+
modified_dirs.clear();
260+
261+
for path in new_entries {
262+
if path.is_file() {
263+
known.insert(path.clone());
264+
self.files.push(path);
265+
} else if path.is_dir() {
266+
known.insert(path.clone());
267+
self.dirs.push(path.clone());
268+
269+
// Recursively scan the new directory for files
270+
modified_dirs.push(path);
271+
}
272+
}
273+
}
274+
}
275+
216276
#[tracing::instrument(skip_all)]
217277
fn scan_sources(&mut self) {
218278
let Some(sources) = &self.sources else {
@@ -282,9 +342,10 @@ impl Scanner {
282342
// Detect all files/folders in the directory
283343
let detect_sources = DetectSources::new(path);
284344

285-
let (files, globs) = detect_sources.detect();
345+
let (files, globs, dirs) = detect_sources.detect();
286346
self.files.extend(files);
287347
self.globs.extend(globs);
348+
self.dirs.extend(dirs);
288349
}
289350

290351
// Turn `Vec<&GlobEntry>` in `Vec<GlobEntry>`

crates/oxide/src/scanner/allowed_paths.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,25 @@ static IGNORED_CONTENT_DIRS: sync::LazyLock<Vec<&'static str>> =
2727

2828
#[tracing::instrument(skip(root))]
2929
pub fn resolve_allowed_paths(root: &Path) -> impl Iterator<Item = DirEntry> {
30+
// Read the directory recursively with no depth limit
31+
read_dir(root, None)
32+
}
33+
34+
#[tracing::instrument(skip(root))]
35+
pub fn resolve_paths(root: &Path) -> impl Iterator<Item = DirEntry> {
3036
WalkBuilder::new(root)
3137
.hidden(false)
3238
.require_git(false)
39+
.build()
40+
.filter_map(Result::ok)
41+
}
42+
43+
#[tracing::instrument(skip(root))]
44+
pub fn read_dir(root: &Path, depth: Option<usize>) -> impl Iterator<Item = DirEntry> {
45+
WalkBuilder::new(root)
46+
.hidden(false)
47+
.require_git(false)
48+
.max_depth(depth)
3349
.filter_entry(move |entry| match entry.file_type() {
3450
Some(file_type) if file_type.is_dir() => match entry.file_name().to_str() {
3551
Some(dir) => !IGNORED_CONTENT_DIRS.contains(&dir),
@@ -44,15 +60,6 @@ pub fn resolve_allowed_paths(root: &Path) -> impl Iterator<Item = DirEntry> {
4460
.filter_map(Result::ok)
4561
}
4662

47-
#[tracing::instrument(skip(root))]
48-
pub fn resolve_paths(root: &Path) -> impl Iterator<Item = DirEntry> {
49-
WalkBuilder::new(root)
50-
.hidden(false)
51-
.require_git(false)
52-
.build()
53-
.filter_map(Result::ok)
54-
}
55-
5663
pub fn is_allowed_content_path(path: &Path) -> bool {
5764
// Skip known ignored files
5865
if path

crates/oxide/src/scanner/detect_sources.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ impl DetectSources {
2727
Self { base }
2828
}
2929

30-
pub fn detect(&self) -> (Vec<PathBuf>, Vec<GlobEntry>) {
30+
pub fn detect(&self) -> (Vec<PathBuf>, Vec<GlobEntry>, Vec<PathBuf>) {
3131
let (files, dirs) = self.resolve_files();
3232
let globs = self.resolve_globs(&dirs);
3333

34-
(files, globs)
34+
(files, globs, dirs)
3535
}
3636

3737
fn resolve_files(&self) -> (Vec<PathBuf>, Vec<PathBuf>) {

0 commit comments

Comments
 (0)