@@ -10,6 +10,8 @@ import type TypeScript from 'typescript';
10
10
11
11
import { buildEvents } from '../../compiler/events' ;
12
12
import type {
13
+ CompilerFileWatcher ,
14
+ CompilerFileWatcherCallback ,
13
15
CompilerSystem ,
14
16
CompilerSystemCreateDirectoryResults ,
15
17
CompilerSystemRealpathResults ,
@@ -432,6 +434,7 @@ export function createNodeSys(c: { process?: any } = {}): CompilerSystem {
432
434
return results ;
433
435
} ,
434
436
setupCompiler ( c ) {
437
+ // save references to typescript utilities so that we can wrap them
435
438
const ts : typeof TypeScript = c . ts ;
436
439
const tsSysWatchDirectory = ts . sys . watchDirectory ;
437
440
const tsSysWatchFile = ts . sys . watchFile ;
@@ -463,20 +466,69 @@ export function createNodeSys(c: { process?: any } = {}): CompilerSystem {
463
466
} ;
464
467
} ;
465
468
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 ,
478
530
}
479
- } ) ;
531
+ ) ;
480
532
481
533
const close = ( ) => {
482
534
tsFileWatcher . close ( ) ;
0 commit comments