Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7f3d514

Browse files
authoredMar 14, 2023
fix(compiler): use file system polling events in watch mode (#4146) (#4147)
this commit updates stencil to use the polling-based file watcher that was used prior to the typescript 4.9 upgrade. in ts 4.9, the ts compiler was updated to use filesystem events. since then, we've received reports of fs events not playing nicely with certain development environments. for this reason, we revert back to the polling based implementation.
1 parent 08e2c48 commit 7f3d514

File tree

2 files changed

+92
-14
lines changed

2 files changed

+92
-14
lines changed
 

‎src/declarations/stencil-public-compiler.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -1098,7 +1098,24 @@ export interface CompilerSystem {
10981098
statSync(p: string): CompilerFsStats;
10991099
tmpDirSync(): string;
11001100
watchDirectory?(p: string, callback: CompilerFileWatcherCallback, recursive?: boolean): CompilerFileWatcher;
1101-
watchFile?(p: string, callback: CompilerFileWatcherCallback): CompilerFileWatcher;
1101+
1102+
/**
1103+
* A `watchFile` implementation in order to hook into the rest of the {@link CompilerSystem} implementation that is
1104+
* used when running Stencil's compiler in "watch mode".
1105+
*
1106+
* It is analogous to TypeScript's `watchFile` implementation.
1107+
*
1108+
* Note, this function may be called for full builds of Stencil projects by the TypeScript compiler. It should not
1109+
* assume that it will only be called in watch mode.
1110+
*
1111+
* This function should not perform any file watcher registration itself. Each `path` provided to it when called
1112+
* should already have been registered as a file to watch.
1113+
*
1114+
* @param path the path to the file that is being watched
1115+
* @param callback a callback to invoke when a file that is being watched has changed in some way
1116+
* @returns an object with a method for unhooking the file watcher from the system
1117+
*/
1118+
watchFile?(path: string, callback: CompilerFileWatcherCallback): CompilerFileWatcher;
11021119
/**
11031120
* How many milliseconds to wait after a change before calling watch callbacks.
11041121
*/
@@ -1300,8 +1317,17 @@ export interface CompilerBuildStart {
13001317
timestamp: string;
13011318
}
13021319

1320+
/**
1321+
* A type describing a function to call when an event is emitted by a file watcher
1322+
* @param fileName the path of the file tied to event
1323+
* @param eventKind a variant describing the type of event that was emitter (added, edited, etc.)
1324+
*/
13031325
export type CompilerFileWatcherCallback = (fileName: string, eventKind: CompilerFileWatcherEvent) => void;
13041326

1327+
/**
1328+
* A type describing the different types of events that Stencil expects may happen when a file being watched is altered
1329+
* in some way
1330+
*/
13051331
export type CompilerFileWatcherEvent =
13061332
| CompilerEventFileAdd
13071333
| CompilerEventFileDelete

‎src/sys/node/node-sys.ts

+65-13
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type TypeScript from 'typescript';
1010

1111
import { buildEvents } from '../../compiler/events';
1212
import type {
13+
CompilerFileWatcher,
14+
CompilerFileWatcherCallback,
1315
CompilerSystem,
1416
CompilerSystemCreateDirectoryResults,
1517
CompilerSystemRealpathResults,
@@ -432,6 +434,7 @@ export function createNodeSys(c: { process?: any } = {}): CompilerSystem {
432434
return results;
433435
},
434436
setupCompiler(c) {
437+
// save references to typescript utilities so that we can wrap them
435438
const ts: typeof TypeScript = c.ts;
436439
const tsSysWatchDirectory = ts.sys.watchDirectory;
437440
const tsSysWatchFile = ts.sys.watchFile;
@@ -463,20 +466,69 @@ export function createNodeSys(c: { process?: any } = {}): CompilerSystem {
463466
};
464467
};
465468

466-
sys.watchFile = (p, callback) => {
467-
const tsFileWatcher = tsSysWatchFile(p, (fileName, tsEventKind) => {
468-
fileName = normalizePath(fileName);
469-
if (tsEventKind === ts.FileWatcherEventKind.Created) {
470-
callback(fileName, 'fileAdd');
471-
sys.events.emit('fileAdd', fileName);
472-
} else if (tsEventKind === ts.FileWatcherEventKind.Changed) {
473-
callback(fileName, 'fileUpdate');
474-
sys.events.emit('fileUpdate', fileName);
475-
} else if (tsEventKind === ts.FileWatcherEventKind.Deleted) {
476-
callback(fileName, 'fileDelete');
477-
sys.events.emit('fileDelete', fileName);
469+
/**
470+
* Wrap the TypeScript `watchFile` implementation in order to hook into the rest of the {@link CompilerSystem}
471+
* implementation that is used when running Stencil's compiler in "watch mode" in Node.
472+
*
473+
* The wrapped function calls the default TypeScript `watchFile` implementation for the provided `path`. Based on
474+
* the type of {@link ts.FileWatcherEventKind} emitted, invoke the provided callback and inform the rest of the
475+
* `CompilerSystem` that the event occurred.
476+
*
477+
* This function does not perform any file watcher registration itself. Each `path` provided to it when called
478+
* has already been registered as a file to watch.
479+
*
480+
* @param path the path to the file that is being watched
481+
* @param callback a callback to invoke. The same callback is invoked for every `ts.FileWatcherEventKind`, only
482+
* with a different event classifier string.
483+
* @returns an object with a method for unhooking the file watcher from the system
484+
*/
485+
sys.watchFile = (path: string, callback: CompilerFileWatcherCallback): CompilerFileWatcher => {
486+
const tsFileWatcher = tsSysWatchFile(
487+
path,
488+
(fileName: string, tsEventKind: TypeScript.FileWatcherEventKind) => {
489+
fileName = normalizePath(fileName);
490+
if (tsEventKind === ts.FileWatcherEventKind.Created) {
491+
callback(fileName, 'fileAdd');
492+
sys.events.emit('fileAdd', fileName);
493+
} else if (tsEventKind === ts.FileWatcherEventKind.Changed) {
494+
callback(fileName, 'fileUpdate');
495+
sys.events.emit('fileUpdate', fileName);
496+
} else if (tsEventKind === ts.FileWatcherEventKind.Deleted) {
497+
callback(fileName, 'fileDelete');
498+
sys.events.emit('fileDelete', fileName);
499+
}
500+
},
501+
502+
/**
503+
* When setting up a watcher, a numeric polling interval (in milliseconds) must be set when using
504+
* {@link ts.WatchFileKind.FixedPollingInterval}. Failing to do so may cause the watch process in the
505+
* TypeScript compiler to crash when files are deleted.
506+
*
507+
* This is the value that was used for files in TypeScript 4.8.4. The value is hardcoded as TS does not
508+
* export this value/make it publicly available.
509+
*/
510+
250,
511+
512+
/**
513+
* As of TypeScript v4.9, the default file watcher implementation is based on file system events, and moves
514+
* away from the previous polling based implementation. When attempting to use the file system events-based
515+
* implementation, issues with the dev server (which runs "watch mode") were reported, stating that the
516+
* compiler was continuously recompiling and reloading the dev server. It was found that in some cases, this
517+
* would be caused by the access time (`atime`) on a non-TypeScript file being update by some process on the
518+
* user's machine. For now, we default back to the poll-based implementation to avoid such issues, and will
519+
* revisit this functionality in the future.
520+
*
521+
* Ref: {@link https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#file-watching-now-uses-file-system-events|TS 4.9 Release Note}
522+
*
523+
* TODO(STENCIL-744): Revisit using file system events for watch mode
524+
*/
525+
{
526+
// TS 4.8 and under defaulted to this type of polling interval for polling-based watchers
527+
watchFile: ts.WatchFileKind.FixedPollingInterval,
528+
// set fallbackPolling so that directories are given the correct watcher variant
529+
fallbackPolling: ts.PollingWatchKind.FixedInterval,
478530
}
479-
});
531+
);
480532

481533
const close = () => {
482534
tsFileWatcher.close();

0 commit comments

Comments
 (0)
Please sign in to comment.