@@ -2,6 +2,8 @@ import * as ts from 'typescript';
2
2
import { basename , dirname , join , sep } from 'path' ;
3
3
import * as fs from 'fs' ;
4
4
import { WebpackResourceLoader } from './resource_loader' ;
5
+ import { TypeScriptFileRefactor } from './refactor' ;
6
+ const MagicString = require ( 'magic-string' ) ;
5
7
6
8
7
9
export interface OnErrorFn {
@@ -11,6 +13,49 @@ export interface OnErrorFn {
11
13
12
14
const dev = Math . floor ( Math . random ( ) * 10000 ) ;
13
15
16
+ // partial copy of TypeScriptFileRefactor
17
+ class InlineResourceRefactor {
18
+ private _sourceString : string ;
19
+ private _changed = false ;
20
+
21
+ constructor ( content : string , private _sourceFile : ts . SourceFile ) {
22
+ this . _sourceString = new MagicString ( content ) ;
23
+ }
24
+
25
+ getResourcesNodes ( ) {
26
+ return this . findAstNodes ( this . _sourceFile , ts . SyntaxKind . ObjectLiteralExpression , true )
27
+ . map ( node => this . findAstNodes ( node , ts . SyntaxKind . PropertyAssignment ) )
28
+ . filter ( node => ! ! node )
29
+ . reduce ( ( prev , curr : ts . PropertyAssignment [ ] ) => prev . concat ( curr
30
+ . filter ( node =>
31
+ node . name . kind == ts . SyntaxKind . Identifier ||
32
+ node . name . kind == ts . SyntaxKind . StringLiteral
33
+ )
34
+ ) , [ ] ) as ts . PropertyAssignment [ ] ;
35
+ }
36
+
37
+ getResourceContentAndType ( _content : string , defaultType : string ) {
38
+ let type = defaultType ;
39
+ const content = _content
40
+ . replace ( / ! ( \w * ) ! / , ( _ , _type ) => {
41
+ type = _type ;
42
+ return '' ;
43
+ } ) ;
44
+ return { content, type} ;
45
+ }
46
+
47
+ get hasChanged ( ) {
48
+ return this . _changed ;
49
+ }
50
+
51
+ getNewContent ( ) {
52
+ return this . _sourceString . toString ( ) ;
53
+ }
54
+
55
+ findAstNodes = TypeScriptFileRefactor . prototype . findAstNodes ;
56
+ replaceNode = TypeScriptFileRefactor . prototype . replaceNode ;
57
+
58
+ }
14
59
15
60
export class VirtualStats implements fs . Stats {
16
61
protected _ctime = new Date ( ) ;
@@ -61,6 +106,8 @@ export class VirtualDirStats extends VirtualStats {
61
106
62
107
export class VirtualFileStats extends VirtualStats {
63
108
private _sourceFile : ts . SourceFile | null ;
109
+ private _resources : string [ ] = [ ] ;
110
+
64
111
constructor ( _fileName : string , private _content : string ) {
65
112
super ( _fileName ) ;
66
113
}
@@ -71,21 +118,19 @@ export class VirtualFileStats extends VirtualStats {
71
118
this . _mtime = new Date ( ) ;
72
119
this . _sourceFile = null ;
73
120
}
74
- setSourceFile ( sourceFile : ts . SourceFile ) {
121
+ set sourceFile ( sourceFile : ts . SourceFile ) {
75
122
this . _sourceFile = sourceFile ;
76
123
}
77
- getSourceFile ( languageVersion : ts . ScriptTarget , setParentNodes : boolean ) {
78
- if ( ! this . _sourceFile ) {
79
- this . _sourceFile = ts . createSourceFile (
80
- this . _path ,
81
- this . _content ,
82
- languageVersion ,
83
- setParentNodes ) ;
84
- }
85
-
124
+ get sourceFile ( ) {
86
125
return this . _sourceFile ;
87
126
}
88
127
128
+ addResource ( resourcePath : string ) {
129
+ this . _resources . push ( resourcePath ) ;
130
+ }
131
+
132
+ get resources ( ) { return this . _resources ; }
133
+
89
134
isFile ( ) { return true ; }
90
135
91
136
get size ( ) { return this . _content . length ; }
@@ -107,7 +152,8 @@ export class WebpackCompilerHost implements ts.CompilerHost {
107
152
private _cache = false ;
108
153
private _resourceLoader ?: WebpackResourceLoader | undefined ;
109
154
110
- constructor ( private _options : ts . CompilerOptions , basePath : string ) {
155
+ constructor ( private _options : ts . CompilerOptions , basePath : string ,
156
+ private _defaultTemplateType = 'html' , private _defaultStyleType = 'css' ) {
111
157
this . _setParentNodes = true ;
112
158
this . _delegate = ts . createCompilerHost ( this . _options , this . _setParentNodes ) ;
113
159
this . _basePath = this . _normalizePath ( basePath ) ;
@@ -128,7 +174,7 @@ export class WebpackCompilerHost implements ts.CompilerHost {
128
174
}
129
175
}
130
176
131
- private _setFileContent ( fileName : string , content : string ) {
177
+ private _setFileContent ( fileName : string , content : string , resource ?: boolean ) {
132
178
this . _files [ fileName ] = new VirtualFileStats ( fileName , content ) ;
133
179
134
180
let p = dirname ( fileName ) ;
@@ -138,7 +184,10 @@ export class WebpackCompilerHost implements ts.CompilerHost {
138
184
p = dirname ( p ) ;
139
185
}
140
186
141
- this . _changedFiles [ fileName ] = true ;
187
+ // only ts files are expected on getChangedFiles()
188
+ if ( ! resource ) {
189
+ this . _changedFiles [ fileName ] = true ;
190
+ }
142
191
}
143
192
144
193
get dirty ( ) {
@@ -165,33 +214,82 @@ export class WebpackCompilerHost implements ts.CompilerHost {
165
214
166
215
invalidate ( fileName : string ) : void {
167
216
fileName = this . resolve ( fileName ) ;
168
- if ( fileName in this . _files ) {
217
+ const file = this . _files [ fileName ] ;
218
+ if ( file != null ) {
219
+ file . resources
220
+ . forEach ( r => this . invalidate ( r ) ) ;
221
+
169
222
this . _files [ fileName ] = null ;
170
- this . _changedFiles [ fileName ] = true ;
223
+ }
224
+ if ( fileName in this . _changedFiles ) {
225
+ this . _changedFiles [ fileName ] = true ;
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Return the corresponding component path
231
+ * or undefined if path isn't considered a resource
232
+ */
233
+ private _getComponentPath ( path : string ) {
234
+ const match = path . match (
235
+ // match ngtemplate, ngstyles but not shim nor summaries
236
+ / ( .* ) \. (?: n g t e m p l a t e | (?: n g s t y l e s [ \d ] * ) ) (? ! .* (?: s h i m .n g s t y l e .t s | n g s u m m a r y .j s o n ) $ ) .* $ /
237
+ ) ;
238
+
239
+ if ( match != null ) {
240
+ return match [ 1 ] + '.ts' ;
171
241
}
172
242
}
173
243
174
244
fileExists ( fileName : string , delegate = true ) : boolean {
175
245
fileName = this . resolve ( fileName ) ;
176
- return this . _files [ fileName ] != null || ( delegate && this . _delegate . fileExists ( fileName ) ) ;
246
+ if ( this . _files [ fileName ] != null ) {
247
+ return true ;
248
+ }
249
+
250
+ const componentPath = this . _getComponentPath ( fileName ) ;
251
+ if ( componentPath != null ) {
252
+ return this . _files [ componentPath ] == null &&
253
+ this . _readResource ( fileName , componentPath ) != null ;
254
+ } else {
255
+ if ( delegate ) {
256
+ return this . _delegate . fileExists ( fileName ) ;
257
+ }
258
+ }
259
+
260
+ return false ;
177
261
}
178
262
179
263
readFile ( fileName : string ) : string {
180
264
fileName = this . resolve ( fileName ) ;
181
265
182
266
const stats = this . _files [ fileName ] ;
183
267
if ( stats == null ) {
268
+ const componentPath = this . _getComponentPath ( fileName ) ;
269
+ if ( componentPath != null ) {
270
+ return this . _readResource ( fileName , componentPath ) ;
271
+ }
272
+
184
273
const result = this . _delegate . readFile ( fileName ) ;
185
274
if ( result !== undefined && this . _cache ) {
186
275
this . _setFileContent ( fileName , result ) ;
187
- return result ;
188
- } else {
189
- return result ;
190
276
}
277
+
278
+ return result ;
191
279
}
192
280
return stats . content ;
193
281
}
194
282
283
+ private _readResource ( resourcePath : string , componentPath : string ) {
284
+ // Trigger source file build which will create and cache associated resources
285
+ this . getSourceFile ( componentPath ) ;
286
+
287
+ const stats = this . _files [ resourcePath ] ;
288
+ if ( stats != null ) {
289
+ return stats . content ;
290
+ }
291
+ }
292
+
195
293
// Does not delegate, use with `fileExists/directoryExists()`.
196
294
stat ( path : string ) : VirtualStats {
197
295
path = this . resolve ( path ) ;
@@ -228,24 +326,98 @@ export class WebpackCompilerHost implements ts.CompilerHost {
228
326
return delegated . concat ( subdirs ) ;
229
327
}
230
328
231
- getSourceFile ( fileName : string , languageVersion : ts . ScriptTarget , _onError ?: OnErrorFn ) {
329
+ private _buildSourceFile ( fileName : string , content : string , languageVersion : ts . ScriptTarget ) {
330
+ let sourceFile = ts . createSourceFile ( fileName , content , languageVersion , this . _setParentNodes ) ;
331
+
332
+ const refactor = new InlineResourceRefactor ( content , sourceFile ) ;
333
+
334
+ const prefix = fileName . substring ( 0 , fileName . lastIndexOf ( '.' ) ) ;
335
+ const resources : string [ ] = [ ] ;
336
+
337
+ refactor . getResourcesNodes ( )
338
+ . forEach ( ( node : any ) => {
339
+ const name = node . name . text ;
340
+
341
+ if ( name === 'template' ) {
342
+ const { content, type} = refactor . getResourceContentAndType (
343
+ node . initializer . text ,
344
+ this . _defaultTemplateType
345
+ ) ;
346
+ const path = `${ prefix } .ngtemplate.${ type } ` ;
347
+
348
+ // always cache resources
349
+ this . _setFileContent ( path , content , true ) ;
350
+ resources . push ( path ) ;
351
+
352
+ refactor . replaceNode ( node , `templateUrl: './${ basename ( path ) } '` ) ;
353
+ } else {
354
+ if ( name === 'styles' ) {
355
+ const arr = < ts . ArrayLiteralExpression [ ] >
356
+ refactor . findAstNodes ( node , ts . SyntaxKind . ArrayLiteralExpression , false ) ;
357
+
358
+ if ( arr && arr . length > 0 && arr [ 0 ] . elements . length > 0 ) {
359
+ const styles = arr [ 0 ] . elements
360
+ . map ( ( element : any ) => element . text )
361
+ . map ( ( _content , idx ) => {
362
+ const { content, type} = refactor . getResourceContentAndType (
363
+ _content ,
364
+ this . _defaultStyleType
365
+ ) ;
366
+
367
+ return { path : `${ prefix } .ngstyles${ idx } .${ type } ` , content} ;
368
+ } ) ;
369
+
370
+ styles . forEach ( ( { path, content} ) => {
371
+ // always cache resources
372
+ this . _setFileContent ( path , content , true ) ;
373
+ resources . push ( path ) ;
374
+ } ) ;
375
+
376
+ const styleUrls = styles
377
+ . map ( ( { path} ) => `'./${ basename ( path ) } '` )
378
+ . join ( ',' ) ;
379
+
380
+ refactor . replaceNode ( node , `styleUrls: [${ styleUrls } ]` ) ;
381
+ }
382
+ }
383
+ }
384
+ } ) ;
385
+
386
+ if ( refactor . hasChanged ) {
387
+ sourceFile = ts . createSourceFile (
388
+ fileName , refactor . getNewContent ( ) , languageVersion , this . _setParentNodes
389
+ ) ;
390
+ }
391
+
392
+ return {
393
+ sourceFile,
394
+ resources
395
+ } ;
396
+ }
397
+
398
+ getSourceFile ( fileName : string , languageVersion = ts . ScriptTarget . Latest , _onError ?: OnErrorFn ) {
232
399
fileName = this . resolve ( fileName ) ;
233
400
234
401
const stats = this . _files [ fileName ] ;
235
- if ( stats == null ) {
236
- const content = this . readFile ( fileName ) ;
237
-
238
- if ( ! this . _cache ) {
239
- return ts . createSourceFile ( fileName , content , languageVersion , this . _setParentNodes ) ;
240
- } else if ( ! this . _files [ fileName ] ) {
241
- // If cache is turned on and the file exists, the readFile call will have populated stats.
242
- // Empty stats at this point mean the file doesn't exist at and so we should return
243
- // undefined.
244
- return undefined ;
245
- }
402
+ if ( stats != null && stats . sourceFile != null ) {
403
+ return stats . sourceFile ;
404
+ }
405
+
406
+ const content = this . readFile ( fileName ) ;
407
+ if ( ! content ) {
408
+ return ;
409
+ }
410
+
411
+ const { sourceFile, resources} = this . _buildSourceFile ( fileName , content , languageVersion ) ;
412
+
413
+ if ( this . _cache ) {
414
+ const stats = this . _files [ fileName ] ;
415
+ stats . sourceFile = sourceFile ;
416
+
417
+ resources . forEach ( r => stats . addResource ( r ) ) ;
246
418
}
247
419
248
- return this . _files [ fileName ] ! . getSourceFile ( languageVersion , this . _setParentNodes ) ;
420
+ return sourceFile ;
249
421
}
250
422
251
423
getCancellationToken ( ) {
@@ -288,6 +460,7 @@ export class WebpackCompilerHost implements ts.CompilerHost {
288
460
this . _resourceLoader = resourceLoader ;
289
461
}
290
462
463
+ // this function and resourceLoader is pretty new and seem unusued so I ignored it for the moment.
291
464
readResource ( fileName : string ) {
292
465
if ( this . _resourceLoader ) {
293
466
const denormalizedFileName = fileName . replace ( / \/ / g, sep ) ;
0 commit comments