Merge 269b78ac69 into 86f3daf912
This commit is contained in:
commit
4535cda700
2 changed files with 344 additions and 167 deletions
|
|
@ -375,6 +375,131 @@ describe.each([
|
|||
},
|
||||
)
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/17246
|
||||
test(
|
||||
'watch mode does not rebuild for changes inside node_modules or .git',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`{}`,
|
||||
'index.html': html`<div class="underline"></div>`,
|
||||
'src/index.css': css` @import 'tailwindcss/utilities'; `,
|
||||
'node_modules/some-dep/index.js': js` module.exports = {} `,
|
||||
'.git/HEAD': txt`ref: refs/heads/main`,
|
||||
},
|
||||
},
|
||||
async ({ fs, spawn, expect }) => {
|
||||
let process = await spawn(`${command} --input src/index.css --output dist/out.css --watch`)
|
||||
await process.onStderr((m) => m.includes('Done in'))
|
||||
process.flush()
|
||||
|
||||
await fs.write('node_modules/some-dep/index.js', js`module.exports = { touched: true }`)
|
||||
await fs.write('.git/HEAD', txt`ref: refs/heads/other`)
|
||||
|
||||
// A watcher rebuild cycle (real or a no-op early return) always logs
|
||||
// "Done in", so if the watcher incorrectly reacted to either change
|
||||
// above, this would resolve. It must not.
|
||||
let sawRebuild = await Promise.race([
|
||||
process.onStderr((m) => m.includes('Done in')).then(() => true),
|
||||
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 1000)),
|
||||
])
|
||||
expect(sawRebuild).toBe(false)
|
||||
|
||||
// Sanity check: the watcher is still alive and reacts to real changes.
|
||||
await fs.write('index.html', html`<div class="underline flex"></div>`)
|
||||
await fs.expectFileToContain('dist/out.css', [candidate`flex`])
|
||||
},
|
||||
)
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/17246
|
||||
test(
|
||||
'watch mode still rebuilds for an explicit @source nested inside node_modules, even when its watch root is collapsed into a broader ancestor',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`{}`,
|
||||
'src/index.css': css`
|
||||
@import 'tailwindcss/utilities';
|
||||
@source '../**/*.html';
|
||||
@source '../node_modules/my-lib/src/*.html';
|
||||
`,
|
||||
'index.html': html`<div class="underline"></div>`,
|
||||
// This lives inside node_modules, but is explicitly opted back in via
|
||||
// @source above. The broader `../**/*.html` source also covers the
|
||||
// project root, so this directory gets collapsed into it by
|
||||
// createWatchers' dedup step -- the ignore filter must not apply to
|
||||
// node_modules for that collapsed root.
|
||||
'node_modules/my-lib/src/index.html': html`
|
||||
<div
|
||||
class="content-['initial']"
|
||||
></div>
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ fs, spawn }) => {
|
||||
let process = await spawn(`${command} --input src/index.css --output dist/out.css --watch`)
|
||||
await process.onStderr((m) => m.includes('Done in'))
|
||||
|
||||
await fs.expectFileToContain('dist/out.css', [candidate`content-['initial']`])
|
||||
|
||||
await fs.write(
|
||||
'node_modules/my-lib/src/index.html',
|
||||
html`<div class="content-['changed']"></div>`,
|
||||
)
|
||||
await fs.expectFileToContain('dist/out.css', [candidate`content-['changed']`])
|
||||
},
|
||||
)
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/pull/20388#discussion (Greptile)
|
||||
test(
|
||||
'watch mode still rebuilds when a tracked @import dependency inside node_modules changes',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`{}`,
|
||||
'src/index.css': css`
|
||||
@import 'tailwindcss/utilities';
|
||||
@import '../node_modules/my-base-styles/base.css';
|
||||
`,
|
||||
'index.html': html`<div class="underline"></div>`,
|
||||
// Not covered by any @source -- this is a plain @import dependency,
|
||||
// tracked via `onDependency`/`fullRebuildPaths` rather than
|
||||
// `scanner.normalizedSources`. It must still be exempted from the
|
||||
// node_modules ignore filter for the collapsed project root.
|
||||
'node_modules/my-base-styles/base.css': css`
|
||||
.imported-marker {
|
||||
color: red;
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ fs, spawn }) => {
|
||||
let process = await spawn(`${command} --input src/index.css --output dist/out.css --watch`)
|
||||
await process.onStderr((m) => m.includes('Done in'))
|
||||
|
||||
await fs.expectFileToContain('dist/out.css', [
|
||||
css`
|
||||
.imported-marker {
|
||||
color: red;
|
||||
}
|
||||
`,
|
||||
])
|
||||
|
||||
await fs.write(
|
||||
'node_modules/my-base-styles/base.css',
|
||||
css`
|
||||
.imported-marker {
|
||||
color: blue;
|
||||
}
|
||||
`,
|
||||
)
|
||||
await fs.expectFileToContain('dist/out.css', [
|
||||
css`
|
||||
.imported-marker {
|
||||
color: blue;
|
||||
}
|
||||
`,
|
||||
])
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'watch mode with polling',
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,6 +28,14 @@ const css = String.raw
|
|||
const DEBUG = env.DEBUG
|
||||
const DEFAULT_POLL_INTERVAL_MS = 250
|
||||
|
||||
// Directory segments that should not be watched by default, regardless of
|
||||
// which project directories are being watched. Watching these can cause
|
||||
// excessive CPU usage or hangs on file watcher backends (e.g. Watchman) when
|
||||
// they contain large or frequently-changing trees, and Tailwind normally
|
||||
// never needs to react to changes inside them. See `watchIgnoreFor` for the
|
||||
// one exception: an explicit `@source` pointing inside one of these.
|
||||
const DEFAULT_WATCH_IGNORE_SEGMENTS = ['node_modules', '.git', '.hg', '.svn']
|
||||
|
||||
export function options() {
|
||||
return {
|
||||
'--input': {
|
||||
|
|
@ -334,154 +342,162 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
await handleError(() => loadWatcher())
|
||||
|
||||
cleanupWatchers.push(
|
||||
await createWatchers(await watchDirectories(scanner), async function handle(files) {
|
||||
try {
|
||||
// If the only change happened to the output file, then we don't want to
|
||||
// trigger a rebuild because that will result in an infinite loop.
|
||||
if (files.length === 1 && files[0] === args['--output']) return
|
||||
await createWatchers(
|
||||
await watchDirectories(scanner),
|
||||
async function handle(files) {
|
||||
try {
|
||||
// If the only change happened to the output file, then we don't want to
|
||||
// trigger a rebuild because that will result in an infinite loop.
|
||||
if (files.length === 1 && files[0] === args['--output']) return
|
||||
|
||||
using I = new Instrumentation()
|
||||
DEBUG && I.start('[@tailwindcss/cli] (watcher)')
|
||||
using I = new Instrumentation()
|
||||
DEBUG && I.start('[@tailwindcss/cli] (watcher)')
|
||||
|
||||
// Re-compile the input
|
||||
let start = process.hrtime.bigint()
|
||||
// Re-compile the input
|
||||
let start = process.hrtime.bigint()
|
||||
|
||||
let resolvedFullRebuildPaths = fullRebuildPaths
|
||||
let rebuildStrategy = getRebuildStrategy(files, resolvedFullRebuildPaths)
|
||||
let resolvedFullRebuildPaths = fullRebuildPaths
|
||||
let rebuildStrategy = getRebuildStrategy(files, resolvedFullRebuildPaths)
|
||||
|
||||
// Track the compiled CSS
|
||||
let compiledCss = ''
|
||||
let compiledMap: SourceMap | null = null
|
||||
// Track the compiled CSS
|
||||
let compiledCss = ''
|
||||
let compiledMap: SourceMap | null = null
|
||||
|
||||
// Scan the entire `base` directory for full rebuilds.
|
||||
if (rebuildStrategy.kind === 'full') {
|
||||
// Read the new `input`.
|
||||
let input = args['--input']
|
||||
? args['--input'] === '-'
|
||||
? await drainStdin()
|
||||
: await fs.readFile(args['--input'], 'utf-8')
|
||||
: css`
|
||||
@import 'tailwindcss';
|
||||
`
|
||||
clearRequireCache(resolvedFullRebuildPaths)
|
||||
// Scan the entire `base` directory for full rebuilds.
|
||||
if (rebuildStrategy.kind === 'full') {
|
||||
// Read the new `input`.
|
||||
let input = args['--input']
|
||||
? args['--input'] === '-'
|
||||
? await drainStdin()
|
||||
: await fs.readFile(args['--input'], 'utf-8')
|
||||
: css`
|
||||
@import 'tailwindcss';
|
||||
`
|
||||
clearRequireCache(resolvedFullRebuildPaths)
|
||||
|
||||
// Track current rebuild paths in case something goes wrong when
|
||||
// performing a full rebuild.
|
||||
backupRebuildPaths = fullRebuildPaths.slice()
|
||||
// Track current rebuild paths in case something goes wrong when
|
||||
// performing a full rebuild.
|
||||
backupRebuildPaths = fullRebuildPaths.slice()
|
||||
|
||||
// The `inputFilePath`, if provided, will be the only known full
|
||||
// rebuild path before the compiler is re-created.
|
||||
fullRebuildPaths = inputFilePath ? [inputFilePath] : []
|
||||
// The `inputFilePath`, if provided, will be the only known full
|
||||
// rebuild path before the compiler is re-created.
|
||||
fullRebuildPaths = inputFilePath ? [inputFilePath] : []
|
||||
|
||||
// Create a new compiler, given the new `input`
|
||||
;[compiler, scanner] = await createCompiler(input, I)
|
||||
// Create a new compiler, given the new `input`
|
||||
;[compiler, scanner] = await createCompiler(input, I)
|
||||
|
||||
// Succesfully created a new compiler, so the `fullRebuildPaths`
|
||||
// will be updated. If other errors occur, we should be able to
|
||||
// restore the paths unconditionally.
|
||||
backupRebuildPaths = fullRebuildPaths.slice()
|
||||
// Succesfully created a new compiler, so the `fullRebuildPaths`
|
||||
// will be updated. If other errors occur, we should be able to
|
||||
// restore the paths unconditionally.
|
||||
backupRebuildPaths = fullRebuildPaths.slice()
|
||||
|
||||
// Scan the directory for candidates
|
||||
DEBUG && I.start('Scan for candidates')
|
||||
let candidates = scanner.scan()
|
||||
DEBUG && I.end('Scan for candidates')
|
||||
// Scan the directory for candidates
|
||||
DEBUG && I.start('Scan for candidates')
|
||||
let candidates = scanner.scan()
|
||||
DEBUG && I.end('Scan for candidates')
|
||||
|
||||
// Setup new watchers
|
||||
DEBUG && I.start('Setup new watchers')
|
||||
let newCleanupFunction = await createWatchers(await watchDirectories(scanner), handle)
|
||||
DEBUG && I.end('Setup new watchers')
|
||||
// Setup new watchers
|
||||
DEBUG && I.start('Setup new watchers')
|
||||
let newCleanupFunction = await createWatchers(
|
||||
await watchDirectories(scanner),
|
||||
handle,
|
||||
fullRebuildPaths,
|
||||
)
|
||||
DEBUG && I.end('Setup new watchers')
|
||||
|
||||
// Clear old watchers
|
||||
DEBUG && I.start('Cleanup old watchers')
|
||||
await Promise.all(cleanupWatchers.splice(0).map((cleanup) => cleanup()))
|
||||
DEBUG && I.end('Cleanup old watchers')
|
||||
// Clear old watchers
|
||||
DEBUG && I.start('Cleanup old watchers')
|
||||
await Promise.all(cleanupWatchers.splice(0).map((cleanup) => cleanup()))
|
||||
DEBUG && I.end('Cleanup old watchers')
|
||||
|
||||
cleanupWatchers.push(newCleanupFunction)
|
||||
cleanupWatchers.push(newCleanupFunction)
|
||||
|
||||
// Re-compile the CSS
|
||||
DEBUG && I.start('Build CSS')
|
||||
compiledCss = compiler.build(candidates)
|
||||
DEBUG && I.end('Build CSS')
|
||||
// Re-compile the CSS
|
||||
DEBUG && I.start('Build CSS')
|
||||
compiledCss = compiler.build(candidates)
|
||||
DEBUG && I.end('Build CSS')
|
||||
|
||||
if (args['--map']) {
|
||||
DEBUG && I.start('Build Source Map')
|
||||
compiledMap = toSourceMap(compiler.buildSourceMap())
|
||||
DEBUG && I.end('Build Source Map')
|
||||
if (args['--map']) {
|
||||
DEBUG && I.start('Build Source Map')
|
||||
compiledMap = toSourceMap(compiler.buildSourceMap())
|
||||
DEBUG && I.end('Build Source Map')
|
||||
}
|
||||
}
|
||||
|
||||
// Scan changed files only for incremental rebuilds.
|
||||
else if (rebuildStrategy.kind === 'incremental') {
|
||||
DEBUG && I.start('Scan for candidates')
|
||||
let newCandidates = scanner.scanFiles(rebuildStrategy.changedFiles)
|
||||
DEBUG && I.end('Scan for candidates')
|
||||
|
||||
// No new candidates found which means we don't need to write to
|
||||
// disk, and can return early.
|
||||
if (newCandidates.length <= 0) {
|
||||
let end = process.hrtime.bigint()
|
||||
if (!args['--silent']) eprintln(`Done in ${formatDuration(end - start)}`)
|
||||
return
|
||||
}
|
||||
|
||||
DEBUG && I.start('Build CSS')
|
||||
compiledCss = compiler.build(newCandidates)
|
||||
DEBUG && I.end('Build CSS')
|
||||
|
||||
if (args['--map']) {
|
||||
DEBUG && I.start('Build Source Map')
|
||||
compiledMap = toSourceMap(compiler.buildSourceMap())
|
||||
DEBUG && I.end('Build Source Map')
|
||||
}
|
||||
}
|
||||
|
||||
await write(compiledCss, compiledMap, args, I)
|
||||
|
||||
let end = process.hrtime.bigint()
|
||||
if (!args['--silent']) eprintln(`Done in ${formatDuration(end - start)}`)
|
||||
} catch (err) {
|
||||
// It's important that we perform a full rebuild when any of the
|
||||
// dependencies tracked in `fullRebuildPaths` has been changed.
|
||||
//
|
||||
// If we remove one of those files, then a subsequent build will be
|
||||
// triggered, but it will fail because the dependency is gone. The
|
||||
// compiler itself will be in a broken state and won't be able to
|
||||
// register any dependencies therefore we want to restore all the
|
||||
// dependencies from before. If we don't do that, then we won't be
|
||||
// able to recover from a bug in a transitive dependency.
|
||||
//
|
||||
// E.g.:
|
||||
// ```css
|
||||
// /* input.css — known full rebuild path */
|
||||
// @import 'tailwindcss';
|
||||
// @config "./tailwind.config.js";
|
||||
// ```
|
||||
//
|
||||
// ```js
|
||||
// // tailwind.config.js
|
||||
// const theme = require('./my-theme.js');
|
||||
//
|
||||
// module.exports = {
|
||||
// theme
|
||||
// }
|
||||
// ```
|
||||
// In this case `my-theme.js` is a transitive dependency of
|
||||
// `input.css` via `tailwind.config.js`. Removing `my-theme.js` will
|
||||
// result in an error, restoring `my-theme.js` should trigger a
|
||||
// fresh build even though the compiler didn't restore.
|
||||
//
|
||||
// Once the build error is fixed, a fresh full rebuild will happen
|
||||
// which in turn will fixup the full rebuild paths.
|
||||
fullRebuildPaths = backupRebuildPaths
|
||||
|
||||
// Catch any errors and print them to stderr, but don't exit the process
|
||||
// and keep watching.
|
||||
eprintln(formatError(err))
|
||||
|
||||
let end = process.hrtime.bigint()
|
||||
if (!args['--silent']) eprintln(`Done in ${formatDuration(end - start)}`)
|
||||
}
|
||||
|
||||
// Scan changed files only for incremental rebuilds.
|
||||
else if (rebuildStrategy.kind === 'incremental') {
|
||||
DEBUG && I.start('Scan for candidates')
|
||||
let newCandidates = scanner.scanFiles(rebuildStrategy.changedFiles)
|
||||
DEBUG && I.end('Scan for candidates')
|
||||
|
||||
// No new candidates found which means we don't need to write to
|
||||
// disk, and can return early.
|
||||
if (newCandidates.length <= 0) {
|
||||
let end = process.hrtime.bigint()
|
||||
if (!args['--silent']) eprintln(`Done in ${formatDuration(end - start)}`)
|
||||
return
|
||||
}
|
||||
|
||||
DEBUG && I.start('Build CSS')
|
||||
compiledCss = compiler.build(newCandidates)
|
||||
DEBUG && I.end('Build CSS')
|
||||
|
||||
if (args['--map']) {
|
||||
DEBUG && I.start('Build Source Map')
|
||||
compiledMap = toSourceMap(compiler.buildSourceMap())
|
||||
DEBUG && I.end('Build Source Map')
|
||||
}
|
||||
}
|
||||
|
||||
await write(compiledCss, compiledMap, args, I)
|
||||
|
||||
let end = process.hrtime.bigint()
|
||||
if (!args['--silent']) eprintln(`Done in ${formatDuration(end - start)}`)
|
||||
} catch (err) {
|
||||
// It's important that we perform a full rebuild when any of the
|
||||
// dependencies tracked in `fullRebuildPaths` has been changed.
|
||||
//
|
||||
// If we remove one of those files, then a subsequent build will be
|
||||
// triggered, but it will fail because the dependency is gone. The
|
||||
// compiler itself will be in a broken state and won't be able to
|
||||
// register any dependencies therefore we want to restore all the
|
||||
// dependencies from before. If we don't do that, then we won't be
|
||||
// able to recover from a bug in a transitive dependency.
|
||||
//
|
||||
// E.g.:
|
||||
// ```css
|
||||
// /* input.css — known full rebuild path */
|
||||
// @import 'tailwindcss';
|
||||
// @config "./tailwind.config.js";
|
||||
// ```
|
||||
//
|
||||
// ```js
|
||||
// // tailwind.config.js
|
||||
// const theme = require('./my-theme.js');
|
||||
//
|
||||
// module.exports = {
|
||||
// theme
|
||||
// }
|
||||
// ```
|
||||
// In this case `my-theme.js` is a transitive dependency of
|
||||
// `input.css` via `tailwind.config.js`. Removing `my-theme.js` will
|
||||
// result in an error, restoring `my-theme.js` should trigger a
|
||||
// fresh build even though the compiler didn't restore.
|
||||
//
|
||||
// Once the build error is fixed, a fresh full rebuild will happen
|
||||
// which in turn will fixup the full rebuild paths.
|
||||
fullRebuildPaths = backupRebuildPaths
|
||||
|
||||
// Catch any errors and print them to stderr, but don't exit the process
|
||||
// and keep watching.
|
||||
eprintln(formatError(err))
|
||||
|
||||
let end = process.hrtime.bigint()
|
||||
if (!args['--silent']) eprintln(`Done in ${formatDuration(end - start)}`)
|
||||
}
|
||||
}),
|
||||
},
|
||||
fullRebuildPaths,
|
||||
),
|
||||
)
|
||||
|
||||
// Abort the watcher if `stdin` is closed to avoid zombie processes. You can
|
||||
|
|
@ -693,9 +709,22 @@ async function loadWatcher(): Promise<typeof import('@parcel/watcher')> {
|
|||
}
|
||||
}
|
||||
|
||||
async function createWatchers(dirs: string[], cb: (files: string[]) => void) {
|
||||
async function createWatchers(
|
||||
dirs: string[],
|
||||
cb: (files: string[]) => void,
|
||||
extraWatchPaths: string[] = [],
|
||||
) {
|
||||
let watcher = await loadWatcher()
|
||||
|
||||
// Keep every originally-requested directory, plus any tracked compiler
|
||||
// dependency path (e.g. an `@import`-resolved file inside `node_modules`,
|
||||
// such as `tailwindcss` itself), before the dedup step below collapses
|
||||
// child directories into an already-watched parent. We need this to
|
||||
// detect when an explicit source or dependency file ends up nested under
|
||||
// a broader watched root, so we know not to ignore that noise directory
|
||||
// for that root -- otherwise we'd never observe changes to it again.
|
||||
let allRequestedDirs = dirs.concat(extraWatchPaths)
|
||||
|
||||
// Remove any directories that are children of an already watched directory.
|
||||
// If we don't we may not get notified of certain filesystem events regardless
|
||||
// of whether or not they are for the directory that is duplicated.
|
||||
|
|
@ -747,44 +776,48 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) {
|
|||
|
||||
// Setup a watcher for every directory.
|
||||
for (let dir of dirs) {
|
||||
let { unsubscribe } = await watcher.subscribe(dir, async (err, events) => {
|
||||
// Whenever an error occurs we want to let the user know about it but we
|
||||
// want to keep watching for changes.
|
||||
if (err) {
|
||||
console.error(err)
|
||||
return
|
||||
}
|
||||
let { unsubscribe } = await watcher.subscribe(
|
||||
dir,
|
||||
async (err, events) => {
|
||||
// Whenever an error occurs we want to let the user know about it but we
|
||||
// want to keep watching for changes.
|
||||
if (err) {
|
||||
console.error(err)
|
||||
return
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
events.map(async (event) => {
|
||||
// When a file is deleted, a rebuild should be triggered such that we
|
||||
// can figure out whether this file must trigger a fresh build or not.
|
||||
//
|
||||
// If it must trigger a fresh build, then we will temporarily end up
|
||||
// in a broken state, but an error will be shown to the user. Once the
|
||||
// user resolves the issue, the CLI will recover.
|
||||
if (event.type === 'delete') {
|
||||
await Promise.all(
|
||||
events.map(async (event) => {
|
||||
// When a file is deleted, a rebuild should be triggered such that we
|
||||
// can figure out whether this file must trigger a fresh build or not.
|
||||
//
|
||||
// If it must trigger a fresh build, then we will temporarily end up
|
||||
// in a broken state, but an error will be shown to the user. Once the
|
||||
// user resolves the issue, the CLI will recover.
|
||||
if (event.type === 'delete') {
|
||||
files.add(event.path)
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore directory changes. We only care about file changes
|
||||
let stats: Stats | null = null
|
||||
try {
|
||||
stats = await fs.lstat(event.path)
|
||||
} catch {}
|
||||
if (!stats?.isFile() && !stats?.isSymbolicLink()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Track the changed file.
|
||||
files.add(event.path)
|
||||
return
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
// Ignore directory changes. We only care about file changes
|
||||
let stats: Stats | null = null
|
||||
try {
|
||||
stats = await fs.lstat(event.path)
|
||||
} catch {}
|
||||
if (!stats?.isFile() && !stats?.isSymbolicLink()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Track the changed file.
|
||||
files.add(event.path)
|
||||
}),
|
||||
)
|
||||
|
||||
// Handle the tracked files at some point in the future.
|
||||
await enqueueCallback()
|
||||
})
|
||||
// Handle the tracked files at some point in the future.
|
||||
await enqueueCallback()
|
||||
},
|
||||
{ ignore: watchIgnoreFor(dir, allRequestedDirs) },
|
||||
)
|
||||
|
||||
// Ensure we cleanup the watcher when we're done.
|
||||
watchers.add(unsubscribe)
|
||||
|
|
@ -847,6 +880,25 @@ function createPollingWatcher(cb: () => Promise<void>, pollInterval: number) {
|
|||
}
|
||||
}
|
||||
|
||||
// Compute the default watch-ignore glob list for `dir`, one of the
|
||||
// (already deduped) directories being watched. Skips any noise segment
|
||||
// (`node_modules`, `.git`, etc.) that contains another originally-requested
|
||||
// directory nested inside it — e.g. an explicit `@source` pointing inside
|
||||
// `node_modules` whose own watch root got collapsed into this broader `dir`
|
||||
// by the dedup step in `createWatchers`. Ignoring that segment for `dir`
|
||||
// would otherwise silently stop the watcher from picking up changes to that
|
||||
// explicitly-configured source, since `@parcel/watcher` matches `ignore`
|
||||
// globs relative to the watched root — the segment only needs to be
|
||||
// preserved for the root that actually ended up watching that subtree.
|
||||
function watchIgnoreFor(dir: string, allRequestedDirs: string[]): string[] {
|
||||
return DEFAULT_WATCH_IGNORE_SEGMENTS.filter((segment) => {
|
||||
let marker = `/${segment}/`
|
||||
return !allRequestedDirs.some(
|
||||
(other) => other !== dir && other.startsWith(`${dir}/`) && `${other}/`.includes(marker),
|
||||
)
|
||||
}).map((segment) => `**/${segment}/**`)
|
||||
}
|
||||
|
||||
async function watchDirectories(scanner: Scanner) {
|
||||
let directories = (
|
||||
await Promise.all(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue