mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Fix CLI not watching atomically renamed files (#9173)
* Fix CLI not watching atomically renamed files Chokdar should take care of this itself but sometimes it doesn’t do so OR is otherwise very sensitive to timing problems * Force chokidar to always check for atomic writes * Handle repeated atomic saves by retrying file reads * Update changelog
This commit is contained in:
parent
7b6ac54ec8
commit
ad7dbda7e9
@ -21,6 +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))
|
||||
|
||||
## [3.1.8] - 2022-08-05
|
||||
|
||||
|
||||
77
src/cli.js
77
src/cli.js
@ -843,6 +843,11 @@ async function build() {
|
||||
}
|
||||
|
||||
watcher = chokidar.watch([...contextDependencies, ...extractFileGlobs(config)], {
|
||||
// Force checking for atomic writes in all situations
|
||||
// This causes chokidar to wait up to 100ms for a file to re-added after it's been unlinked
|
||||
// This only works when watching directories though
|
||||
atomic: true,
|
||||
|
||||
usePolling: shouldPoll,
|
||||
interval: shouldPoll ? pollInterval : undefined,
|
||||
ignoreInitial: true,
|
||||
@ -855,6 +860,7 @@ async function build() {
|
||||
})
|
||||
|
||||
let chain = Promise.resolve()
|
||||
let pendingRebuilds = new Set()
|
||||
|
||||
watcher.on('change', async (file) => {
|
||||
if (contextDependencies.has(file)) {
|
||||
@ -885,6 +891,77 @@ async function build() {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* When rapidly saving files atomically a couple of situations can happen:
|
||||
* - The file is missing since the external program has deleted it by the time we've gotten around to reading it from the earlier save.
|
||||
* - The file is being written to by the external program by the time we're going to read it and is thus treated as busy because a lock is held.
|
||||
*
|
||||
* To work around this we retry reading the file a handful of times with a delay between each attempt
|
||||
*
|
||||
* @param {string} path
|
||||
* @param {number} tries
|
||||
* @returns {string}
|
||||
* @throws {Error} If the file is still missing or busy after the specified number of tries
|
||||
*/
|
||||
async function readFileWithRetries(path, tries = 5) {
|
||||
for (let n = 0; n < tries; n++) {
|
||||
try {
|
||||
return await fs.promises.readFile(path, 'utf8')
|
||||
} catch (err) {
|
||||
if (n < tries) {
|
||||
if (err.code === 'ENOENT' || err.code === 'EBUSY') {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
|
||||
// Some applications such as Visual Studio (but not VS Code)
|
||||
// will only fire a rename event for atomic writes and not a change event
|
||||
// This is very likely a chokidar bug but it's one we need to work around
|
||||
// We treat this as a change event and rebuild the CSS
|
||||
watcher.on('raw', (evt, filePath, meta) => {
|
||||
if (evt !== 'rename') {
|
||||
return
|
||||
}
|
||||
|
||||
filePath = path.resolve(meta.watchedPath, filePath)
|
||||
|
||||
// Skip since we've already queued a rebuild for this file that hasn't happened yet
|
||||
if (pendingRebuilds.has(filePath)) {
|
||||
return
|
||||
}
|
||||
|
||||
pendingRebuilds.add(filePath)
|
||||
|
||||
chain = chain.then(async () => {
|
||||
let content
|
||||
|
||||
try {
|
||||
content = await readFileWithRetries(path.resolve(filePath))
|
||||
} finally {
|
||||
pendingRebuilds.delete(filePath)
|
||||
}
|
||||
|
||||
changedContent.push({
|
||||
content,
|
||||
extension: path.extname(filePath).slice(1),
|
||||
})
|
||||
|
||||
await rebuild(config)
|
||||
})
|
||||
})
|
||||
|
||||
watcher.on('add', async (file) => {
|
||||
chain = chain.then(async () => {
|
||||
changedContent.push({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user