diff --git a/CHANGELOG.md b/CHANGELOG.md index c16a989dd0b6..e611a5b90219 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Sort tags before classes when `@applying` a selector with joined classes ([#9107](https://github.com/tailwindlabs/tailwindcss/pull/9107)) - Remove invalid `outline-hidden` utility ([#9147](https://github.com/tailwindlabs/tailwindcss/pull/9147)) - Honor the `hidden` attribute on elements in preflight ([#9174](https://github.com/tailwindlabs/tailwindcss/pull/9174)) -- Don't stop watching atomically renamed files ([#9173](https://github.com/tailwindlabs/tailwindcss/pull/9173)) +- Don't stop watching atomically renamed files ([#9173](https://github.com/tailwindlabs/tailwindcss/pull/9173), [#9215](https://github.com/tailwindlabs/tailwindcss/pull/9215)) - Re-use existing entries in the rule cache ([#9208](https://github.com/tailwindlabs/tailwindcss/pull/9208)) - Don't output duplicate utilities ([#9208](https://github.com/tailwindlabs/tailwindcss/pull/9208)) - Fix `fontFamily` config TypeScript types ([#9214](https://github.com/tailwindlabs/tailwindcss/pull/9214)) diff --git a/src/cli.js b/src/cli.js index 0797be2f211c..52c3d08f6b8e 100644 --- a/src/cli.js +++ b/src/cli.js @@ -17,6 +17,7 @@ import getModuleDependencies from './lib/getModuleDependencies' import log from './util/log' import packageJson from '../package.json' import normalizePath from 'normalize-path' +import micromatch from 'micromatch' import { validateConfig } from './util/validateConfig.js' let env = { @@ -837,6 +838,23 @@ async function build() { } let config = refreshConfig(configPath) + let contentPatterns = refreshContentPatterns(config) + + /** + * @param {import('../types/config.js').RequiredConfig} config + * @return {{all: string[], dynamic: string[], static: string[]}} + **/ + function refreshContentPatterns(config) { + let globs = extractFileGlobs(config) + let tasks = fastGlob.generateTasks(globs, { absolute: true }) + let dynamicPatterns = tasks.filter((task) => task.dynamic).flatMap((task) => task.patterns) + let staticPatterns = tasks.filter((task) => !task.dynamic).flatMap((task) => task.patterns) + + return { + all: [...staticPatterns, ...dynamicPatterns], + dynamic: dynamicPatterns, + } + } if (input) { contextDependencies.add(path.resolve(input)) @@ -867,6 +885,7 @@ async function build() { env.DEBUG && console.time('Resolve config') context = null config = refreshConfig(configPath) + contentPatterns = refreshContentPatterns(config) env.DEBUG && console.timeEnd('Resolve config') env.DEBUG && console.time('Watch new files') @@ -924,7 +943,14 @@ async function build() { // Restore watching any files that are "removed" // This can happen when a file is pseudo-atomically replaced (a copy is created, overwritten, the old one is unlinked, and the new one is renamed) // TODO: An an optimization we should allow removal when the config changes - watcher.on('unlink', (file) => watcher.add(file)) + watcher.on('unlink', (file) => { + file = normalizePath(file) + + // Only re-add the file if it's not covered by a dynamic pattern + if (!micromatch.some([file], contentPatterns.dynamic)) { + watcher.add(file) + } + }) // Some applications such as Visual Studio (but not VS Code) // will only fire a rename event for atomic writes and not a change event @@ -935,11 +961,16 @@ async function build() { return } - let watchedPath = path.resolve(meta.watchedPath) + let watchedPath = meta.watchedPath // Watched path might be the file itself // Or the directory it is in - filePath = watchedPath.endsWith(filePath) ? watchedPath : path.resolve(watchedPath, filePath) + filePath = watchedPath.endsWith(filePath) ? watchedPath : path.join(watchedPath, filePath) + + // Skip this event since the files it is for does not match any of the registered content globs + if (!micromatch.some([filePath], contentPatterns.all)) { + return + } // Skip since we've already queued a rebuild for this file that hasn't happened yet if (pendingRebuilds.has(filePath)) {