@@ -35,18 +35,35 @@ const ChangeDiagnostics = {
35
35
} ,
36
36
} ;
37
37
38
+ function totalCompilationTimeDiagnostic ( timeInMillis : number ) : api . Diagnostic {
39
+ let duration : string ;
40
+ if ( timeInMillis > 1000 ) {
41
+ duration = `${ ( timeInMillis / 1000 ) . toPrecision ( 2 ) } s` ;
42
+ } else {
43
+ duration = `${ timeInMillis } ms` ;
44
+ }
45
+ return {
46
+ category : ts . DiagnosticCategory . Message ,
47
+ messageText : `Total time: ${ duration } ` ,
48
+ code : api . DEFAULT_ERROR_CODE ,
49
+ source : api . SOURCE ,
50
+ } ;
51
+ }
52
+
38
53
export enum FileChangeEvent {
39
54
Change ,
40
- CreateDelete
55
+ CreateDelete ,
56
+ CreateDeleteDir ,
41
57
}
42
58
43
59
export interface PerformWatchHost {
44
60
reportDiagnostics ( diagnostics : Diagnostics ) : void ;
45
61
readConfiguration ( ) : ParsedConfiguration ;
46
62
createCompilerHost ( options : api . CompilerOptions ) : api . CompilerHost ;
47
63
createEmitCallback ( options : api . CompilerOptions ) : api . TsEmitCallback | undefined ;
48
- onFileChange ( listener : ( event : FileChangeEvent , fileName : string ) => void ) :
49
- { close : ( ) => void , ready : ( cb : ( ) => void ) => void } ;
64
+ onFileChange (
65
+ options : api . CompilerOptions , listener : ( event : FileChangeEvent , fileName : string ) => void ,
66
+ ready : ( ) => void ) : { close : ( ) => void } ;
50
67
setTimeout ( callback : ( ) => void , ms : number ) : any ;
51
68
clearTimeout ( timeoutId : any ) : void ;
52
69
}
@@ -60,23 +77,17 @@ export function createPerformWatchHost(
60
77
createCompilerHost : options => createCompilerHost ( { options} ) ,
61
78
readConfiguration : ( ) => readConfiguration ( configFileName , existingOptions ) ,
62
79
createEmitCallback : options => createEmitCallback ? createEmitCallback ( options ) : undefined ,
63
- onFileChange : ( listeners ) => {
64
- const parsed = readConfiguration ( configFileName , existingOptions ) ;
65
- function stubReady ( cb : ( ) => void ) { process . nextTick ( cb ) ; }
66
- if ( parsed . errors && parsed . errors . length ) {
67
- reportDiagnostics ( parsed . errors ) ;
68
- return { close : ( ) => { } , ready : stubReady } ;
69
- }
70
- if ( ! parsed . options . basePath ) {
80
+ onFileChange : ( options , listener , ready : ( ) => void ) => {
81
+ if ( ! options . basePath ) {
71
82
reportDiagnostics ( [ {
72
83
category : ts . DiagnosticCategory . Error ,
73
84
messageText : 'Invalid configuration option. baseDir not specified' ,
74
85
source : api . SOURCE ,
75
86
code : api . DEFAULT_ERROR_CODE
76
87
} ] ) ;
77
- return { close : ( ) => { } , ready : stubReady } ;
88
+ return { close : ( ) => { } } ;
78
89
}
79
- const watcher = chokidar . watch ( parsed . options . basePath , {
90
+ const watcher = chokidar . watch ( options . basePath , {
80
91
// ignore .dotfiles, .js and .map files.
81
92
// can't ignore other files as we e.g. want to recompile if an `.html` file changes as well.
82
93
ignored : / ( ( ^ [ \/ \\ ] ) \. .) | ( \. j s $ ) | ( \. m a p $ ) | ( \. m e t a d a t a \. j s o n ) / ,
@@ -86,22 +97,32 @@ export function createPerformWatchHost(
86
97
watcher . on ( 'all' , ( event : string , path : string ) => {
87
98
switch ( event ) {
88
99
case 'change' :
89
- listeners ( FileChangeEvent . Change , path ) ;
100
+ listener ( FileChangeEvent . Change , path ) ;
90
101
break ;
91
102
case 'unlink' :
92
103
case 'add' :
93
- listeners ( FileChangeEvent . CreateDelete , path ) ;
104
+ listener ( FileChangeEvent . CreateDelete , path ) ;
105
+ break ;
106
+ case 'unlinkDir' :
107
+ case 'addDir' :
108
+ listener ( FileChangeEvent . CreateDeleteDir , path ) ;
94
109
break ;
95
110
}
96
111
} ) ;
97
- function ready ( cb : ( ) => void ) { watcher . on ( 'ready' , cb ) ; }
112
+ watcher . on ( 'ready' , ready ) ;
98
113
return { close : ( ) => watcher . close ( ) , ready} ;
99
114
} ,
100
115
setTimeout : ( ts . sys . clearTimeout && ts . sys . setTimeout ) || setTimeout ,
101
116
clearTimeout : ( ts . sys . setTimeout && ts . sys . clearTimeout ) || clearTimeout ,
102
117
} ;
103
118
}
104
119
120
+ interface CacheEntry {
121
+ exists ?: boolean ;
122
+ sf ?: ts . SourceFile ;
123
+ content ?: string ;
124
+ }
125
+
105
126
/**
106
127
* The logic in this function is adapted from `tsc.ts` from TypeScript.
107
128
*/
@@ -112,16 +133,30 @@ export function performWatchCompilation(host: PerformWatchHost):
112
133
let cachedOptions : ParsedConfiguration | undefined ; // CompilerOptions cached from last compilation
113
134
let timerHandleForRecompilation : any ; // Handle for 0.25s wait timer to trigger recompilation
114
135
115
- // Watch basePath, ignoring .dotfiles
116
- const fileWatcher = host . onFileChange ( watchedFileChanged ) ;
117
136
const ingoreFilesForWatch = new Set < string > ( ) ;
137
+ const fileCache = new Map < string , CacheEntry > ( ) ;
118
138
119
139
const firstCompileResult = doCompilation ( ) ;
120
140
121
- const readyPromise = new Promise ( resolve => fileWatcher . ready ( resolve ) ) ;
141
+ // Watch basePath, ignoring .dotfiles
142
+ let resolveReadyPromise : ( ) => void ;
143
+ const readyPromise = new Promise ( resolve => resolveReadyPromise = resolve ) ;
144
+ // Note: ! is ok as options are filled after the first compilation
145
+ // Note: ! is ok as resolvedReadyPromise is filled by the previous call
146
+ const fileWatcher =
147
+ host . onFileChange ( cachedOptions ! . options , watchedFileChanged , resolveReadyPromise ! ) ;
122
148
123
149
return { close, ready : cb => readyPromise . then ( cb ) , firstCompileResult} ;
124
150
151
+ function cacheEntry ( fileName : string ) : CacheEntry {
152
+ let entry = fileCache . get ( fileName ) ;
153
+ if ( ! entry ) {
154
+ entry = { } ;
155
+ fileCache . set ( fileName , entry ) ;
156
+ }
157
+ return entry ;
158
+ }
159
+
125
160
function close ( ) {
126
161
fileWatcher . close ( ) ;
127
162
if ( timerHandleForRecompilation ) {
@@ -139,11 +174,8 @@ export function performWatchCompilation(host: PerformWatchHost):
139
174
host . reportDiagnostics ( cachedOptions . errors ) ;
140
175
return cachedOptions . errors ;
141
176
}
177
+ const startTime = Date . now ( ) ;
142
178
if ( ! cachedCompilerHost ) {
143
- // TODO(chuckj): consider avoiding re-generating factories for libraries.
144
- // Consider modifying the AotCompilerHost to be able to remember the summary files
145
- // generated from previous compiliations and return false from isSourceFile for
146
- // .d.ts files for which a summary file was already generated.å
147
179
cachedCompilerHost = host . createCompilerHost ( cachedOptions . options ) ;
148
180
const originalWriteFileCallback = cachedCompilerHost . writeFile ;
149
181
cachedCompilerHost . writeFile = function (
@@ -152,6 +184,31 @@ export function performWatchCompilation(host: PerformWatchHost):
152
184
ingoreFilesForWatch . add ( path . normalize ( fileName ) ) ;
153
185
return originalWriteFileCallback ( fileName , data , writeByteOrderMark , onError , sourceFiles ) ;
154
186
} ;
187
+ const originalFileExists = cachedCompilerHost . fileExists ;
188
+ cachedCompilerHost . fileExists = function ( fileName : string ) {
189
+ const ce = cacheEntry ( fileName ) ;
190
+ if ( ce . exists == null ) {
191
+ ce . exists = originalFileExists . call ( this , fileName ) ;
192
+ }
193
+ return ce . exists ! ;
194
+ } ;
195
+ const originalGetSourceFile = cachedCompilerHost . getSourceFile ;
196
+ cachedCompilerHost . getSourceFile = function (
197
+ fileName : string , languageVersion : ts . ScriptTarget ) {
198
+ const ce = cacheEntry ( fileName ) ;
199
+ if ( ! ce . sf ) {
200
+ ce . sf = originalGetSourceFile . call ( this , fileName , languageVersion ) ;
201
+ }
202
+ return ce . sf ! ;
203
+ } ;
204
+ const originalReadFile = cachedCompilerHost . readFile ;
205
+ cachedCompilerHost . readFile = function ( fileName : string ) {
206
+ const ce = cacheEntry ( fileName ) ;
207
+ if ( ce . content == null ) {
208
+ ce . content = originalReadFile . call ( this , fileName ) ;
209
+ }
210
+ return ce . content ! ;
211
+ } ;
155
212
}
156
213
ingoreFilesForWatch . clear ( ) ;
157
214
const compileResult = performCompilation ( {
@@ -166,6 +223,11 @@ export function performWatchCompilation(host: PerformWatchHost):
166
223
host . reportDiagnostics ( compileResult . diagnostics ) ;
167
224
}
168
225
226
+ const endTime = Date . now ( ) ;
227
+ if ( cachedOptions . options . diagnostics ) {
228
+ const totalTime = ( endTime - startTime ) / 1000 ;
229
+ host . reportDiagnostics ( [ totalCompilationTimeDiagnostic ( endTime - startTime ) ] ) ;
230
+ }
169
231
const exitCode = exitCodeFromResult ( compileResult . diagnostics ) ;
170
232
if ( exitCode == 0 ) {
171
233
cachedProgram = compileResult . program ;
@@ -191,11 +253,19 @@ export function performWatchCompilation(host: PerformWatchHost):
191
253
path . normalize ( fileName ) === path . normalize ( cachedOptions . project ) ) {
192
254
// If the configuration file changes, forget everything and start the recompilation timer
193
255
resetOptions ( ) ;
194
- } else if ( event === FileChangeEvent . CreateDelete ) {
256
+ } else if (
257
+ event === FileChangeEvent . CreateDelete || event === FileChangeEvent . CreateDeleteDir ) {
195
258
// If a file was added or removed, reread the configuration
196
259
// to determine the new list of root files.
197
260
cachedOptions = undefined ;
198
261
}
262
+
263
+ if ( event === FileChangeEvent . CreateDeleteDir ) {
264
+ fileCache . clear ( ) ;
265
+ } else {
266
+ fileCache . delete ( fileName ) ;
267
+ }
268
+
199
269
if ( ! ingoreFilesForWatch . has ( path . normalize ( fileName ) ) ) {
200
270
// Ignore the file if the file is one that was written by the compiler.
201
271
startTimerForRecompilation ( ) ;
0 commit comments