Skip to content

Commit 7106b8d

Browse files
committed
fix(@angular-devkit/build-angular): improve file watching on Windows when using certain IDEs
This commit, fixes a file watching issue where file changes events are not triggered when using IDEs like Visual Studio (not VS Code). The main changes to solve the issue are; ## Replace `watcher.on('all')` with `watcher.on(`raw`)` The former does not fire for all events example ``` watcher.on('raw') Change 1 rename | 'C:/../src/app/app.component.css' rename | 'C:/../src/app/app.component.css' change | 'C:/../src/app/app.component.css' Change 2 rename | 'C:/../src/app/app.component.css' rename | 'C:/../src/app/app.component.css' change | 'C:/../src/app/app.component.css' Change 3 rename | 'C:/../src/app/app.component.css' rename | 'C:/../src/app/app.component.css' change | 'C:/../src/app/app.component.css' watcher.on('all') Change 1 change | 'C:\\..\\src\\app\\app.component.css' Change 2 unlink | 'C:\\..\\src\\app\\app.component.css' Change 3 ... (Nothing) ``` ## Handle `rename` events Some IDEs will fire a rename event instead of unlink/changed when a file is modified} Closes angular#26437
1 parent 3810bd4 commit 7106b8d

File tree

2 files changed

+75
-17
lines changed

2 files changed

+75
-17
lines changed

packages/angular_devkit/build_angular/src/tools/esbuild/watcher.ts

+74-13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import { FSWatcher } from 'chokidar';
10+
import { normalize } from 'node:path';
1011

1112
export class ChangedFiles {
1213
readonly added = new Set<string>();
@@ -51,19 +52,62 @@ export function createWatcher(options?: {
5152
let currentChanges: ChangedFiles | undefined;
5253
let nextWaitTimeout: NodeJS.Timeout | undefined;
5354

54-
watcher.on('all', (event, path) => {
55+
/**
56+
* We group the current events in a map as on Windows with certain IDE a file contents change can trigger multiple events.
57+
*
58+
* Example:
59+
* rename | 'C:/../src/app/app.component.css'
60+
* rename | 'C:/../src/app/app.component.css'
61+
* change | 'C:/../src/app/app.component.css'
62+
*
63+
*/
64+
let currentEvents: Map</* Event name */ string, /* File path */ string> | undefined;
65+
66+
/**
67+
* Using `watcher.on('all')` does not capture some of events fired when using Visual studio and this does not happen all the time,
68+
* but only after a file has been changed 3 or more times.
69+
*
70+
* Also, some IDEs such as Visual Studio (not VS Code) will fire a rename event instead of unlink when a file is renamed or changed.
71+
*
72+
* Example:
73+
* ```
74+
* watcher.on('raw')
75+
* Change 1
76+
* rename | 'C:/../src/app/app.component.css'
77+
* rename | 'C:/../src/app/app.component.css'
78+
* change | 'C:/../src/app/app.component.css'
79+
*
80+
* Change 2
81+
* rename | 'C:/../src/app/app.component.css'
82+
* rename | 'C:/../src/app/app.component.css'
83+
* change | 'C:/../src/app/app.component.css'
84+
*
85+
* Change 3
86+
* rename | 'C:/../src/app/app.component.css'
87+
* rename | 'C:/../src/app/app.component.css'
88+
* change | 'C:/../src/app/app.component.css'
89+
*
90+
* watcher.on('all')
91+
* Change 1
92+
* change | 'C:\\..\\src\\app\\app.component.css'
93+
*
94+
* Change 2
95+
* unlink | 'C:\\..\\src\\app\\app.component.css'
96+
*
97+
* Change 3
98+
* ... (Nothing)
99+
* ```
100+
*/
101+
watcher.on('raw', (event, _basename, { watchedPath }) => {
55102
switch (event) {
56103
case 'add':
57-
currentChanges ??= new ChangedFiles();
58-
currentChanges.added.add(path);
59-
break;
60104
case 'change':
61-
currentChanges ??= new ChangedFiles();
62-
currentChanges.modified.add(path);
63-
break;
105+
// When using Visual Studio the rename event is fired before a change event when the contents of the file changed
106+
// or instead of `unlink` when the file name has changed.
64107
case 'unlink':
65-
currentChanges ??= new ChangedFiles();
66-
currentChanges.removed.add(path);
108+
case 'rename':
109+
currentEvents ??= new Map();
110+
currentEvents.set(normalize(watchedPath), event);
67111
break;
68112
default:
69113
return;
@@ -74,10 +118,27 @@ export function createWatcher(options?: {
74118
nextWaitTimeout = setTimeout(() => {
75119
nextWaitTimeout = undefined;
76120
const next = nextQueue.shift();
77-
if (next) {
78-
const value = currentChanges;
79-
currentChanges = undefined;
80-
next(value);
121+
if (next && currentEvents) {
122+
const events = currentEvents;
123+
currentEvents = undefined;
124+
125+
const currentChanges = new ChangedFiles();
126+
for (const [path, event] of events) {
127+
switch (event) {
128+
case 'add':
129+
currentChanges.added.add(path);
130+
break;
131+
case 'change':
132+
currentChanges.modified.add(path);
133+
break;
134+
case 'unlink':
135+
case 'rename':
136+
currentChanges.removed.add(path);
137+
break;
138+
}
139+
}
140+
141+
next(currentChanges);
81142
}
82143
}, 250);
83144
nextWaitTimeout?.unref();

packages/angular_devkit/build_angular/src/utils/environment-options.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,4 @@ export const debugPerformance = isPresent(debugPerfVariable) && isEnabled(debugP
102102

103103
// Default to true on Windows to workaround Visual Studio atomic file saving watch issues
104104
const watchRootVariable = process.env['NG_BUILD_WATCH_ROOT'];
105-
export const shouldWatchRoot =
106-
process.platform === 'win32'
107-
? !isPresent(watchRootVariable) || !isDisabled(watchRootVariable)
108-
: isPresent(watchRootVariable) && isEnabled(watchRootVariable);
105+
export const shouldWatchRoot = isPresent(watchRootVariable) && isEnabled(watchRootVariable);

0 commit comments

Comments
 (0)