@@ -2,11 +2,9 @@ import * as fs from 'fs';
2
2
import * as path from 'path' ;
3
3
import * as ts from 'typescript' ;
4
4
5
- import { NgModule } from '@angular/core' ;
6
- import * as ngCompiler from '@angular/compiler-cli' ;
7
- import { tsc } from '@angular/tsc-wrapped/src/tsc' ;
5
+ import { __NGTOOLS_PRIVATE_API_2 } from '@angular/compiler-cli' ;
6
+ import { AngularCompilerOptions } from '@angular/tsc-wrapped' ;
8
7
9
- import { patchReflectorHost } from './reflector_host' ;
10
8
import { WebpackResourceLoader } from './resource_loader' ;
11
9
import { createResolveDependenciesFromContextMap } from './utils' ;
12
10
import { WebpackCompilerHost } from './compiler_host' ;
@@ -29,47 +27,22 @@ export interface AotPluginOptions {
29
27
i18nFile ?: string ;
30
28
i18nFormat ?: string ;
31
29
locale ?: string ;
32
- }
33
-
34
30
35
- export interface LazyRoute {
36
- moduleRoute : ModuleRoute ;
37
- absolutePath : string ;
38
- absoluteGenDirPath : string ;
39
- }
40
-
41
-
42
- export interface LazyRouteMap {
43
- [ path : string ] : LazyRoute ;
44
- }
45
-
46
-
47
- export class ModuleRoute {
48
- constructor ( public readonly path : string , public readonly className : string = null ) { }
49
-
50
- toString ( ) {
51
- return `${ this . path } #${ this . className } ` ;
52
- }
53
-
54
- static fromString ( entry : string ) : ModuleRoute {
55
- const split = entry . split ( '#' ) ;
56
- return new ModuleRoute ( split [ 0 ] , split [ 1 ] ) ;
57
- }
31
+ // We do not have an include because tsconfig can be used for that.
32
+ exclude ?: string | string [ ] ;
58
33
}
59
34
60
35
61
36
export class AotPlugin implements Tapable {
62
- private _entryModule : ModuleRoute ;
63
37
private _compilerOptions : ts . CompilerOptions ;
64
- private _angularCompilerOptions : ngCompiler . AngularCompilerOptions ;
38
+ private _angularCompilerOptions : AngularCompilerOptions ;
65
39
private _program : ts . Program ;
66
- private _reflector : ngCompiler . StaticReflector ;
67
- private _reflectorHost : ngCompiler . ReflectorHost ;
68
40
private _rootFilePath : string [ ] ;
69
41
private _compilerHost : WebpackCompilerHost ;
70
42
private _resourceLoader : WebpackResourceLoader ;
71
43
private _lazyRoutes : { [ route : string ] : string } ;
72
44
private _tsConfigPath : string ;
45
+ private _entryModule : string ;
73
46
74
47
private _donePromise : Promise < void > ;
75
48
private _compiler : any = null ;
@@ -93,7 +66,12 @@ export class AotPlugin implements Tapable {
93
66
get compilerHost ( ) { return this . _compilerHost ; }
94
67
get compilerOptions ( ) { return this . _compilerOptions ; }
95
68
get done ( ) { return this . _donePromise ; }
96
- get entryModule ( ) { return this . _entryModule ; }
69
+ get entryModule ( ) {
70
+ const splitted = this . _entryModule . split ( '#' ) ;
71
+ const path = splitted [ 0 ] ;
72
+ const className = splitted [ 1 ] || 'default' ;
73
+ return { path, className} ;
74
+ }
97
75
get genDir ( ) { return this . _genDir ; }
98
76
get program ( ) { return this . _program ; }
99
77
get skipCodeGeneration ( ) { return this . _skipCodeGeneration ; }
@@ -119,19 +97,52 @@ export class AotPlugin implements Tapable {
119
97
basePath = path . resolve ( process . cwd ( ) , options . basePath ) ;
120
98
}
121
99
122
- const tsConfig = tsc . readConfiguration ( this . _tsConfigPath , basePath ) ;
123
- this . _rootFilePath = tsConfig . parsed . fileNames
124
- . filter ( fileName => ! / \. s p e c \. t s $ / . test ( fileName ) ) ;
100
+ let tsConfigJson : any = null ;
101
+ try {
102
+ tsConfigJson = JSON . parse ( fs . readFileSync ( this . _tsConfigPath , 'utf8' ) ) ;
103
+ } catch ( err ) {
104
+ throw new Error ( `An error happened while parsing ${ this . _tsConfigPath } JSON: ${ err } .` ) ;
105
+ }
106
+ const tsConfig = ts . parseJsonConfigFileContent (
107
+ tsConfigJson , ts . sys , basePath , null , this . _tsConfigPath ) ;
108
+
109
+ let fileNames = tsConfig . fileNames ;
110
+ if ( options . hasOwnProperty ( 'exclude' ) ) {
111
+ let exclude : string [ ] = typeof options . exclude == 'string'
112
+ ? [ options . exclude as string ] : ( options . exclude as string [ ] ) ;
113
+
114
+ exclude . forEach ( ( pattern : string ) => {
115
+ pattern = pattern
116
+ // Replace characters that are used normally in regexes, except stars.
117
+ . replace ( / [ \- \[ \] \/ { } ( ) + ? . \\ ^ $ | ] / g, '\\$&' )
118
+ // Two stars replacement.
119
+ . replace ( / \* \* / g, '(?:.*)' )
120
+ . replace ( / \* / g, '(?:[^/]*)' )
121
+ . replace ( / ^ / , `(${ basePath } )?` ) ;
122
+
123
+ const re = new RegExp ( '^' + pattern + '$' ) ;
124
+ fileNames = fileNames . filter ( x => ! x . match ( re ) ) ;
125
+ } )
126
+ } else {
127
+ fileNames = fileNames . filter ( fileName => ! / \. s p e c \. t s $ / . test ( fileName ) ) ;
128
+ }
129
+ this . _rootFilePath = fileNames ;
125
130
126
131
// Check the genDir.
127
132
let genDir = basePath ;
128
- if ( tsConfig . ngOptions . hasOwnProperty ( 'genDir' ) ) {
129
- genDir = tsConfig . ngOptions . genDir ;
130
- }
131
133
132
- this . _compilerOptions = tsConfig . parsed . options ;
134
+ this . _compilerOptions = tsConfig . options ;
135
+ this . _angularCompilerOptions = Object . assign (
136
+ { genDir } ,
137
+ this . _compilerOptions ,
138
+ tsConfig . raw [ 'angularCompilerOptions' ] ,
139
+ { basePath }
140
+ ) ;
141
+
142
+ if ( this . _angularCompilerOptions . hasOwnProperty ( 'genDir' ) ) {
143
+ genDir = this . _angularCompilerOptions . genDir ;
144
+ }
133
145
134
- this . _angularCompilerOptions = Object . assign ( { } , tsConfig . ngOptions , { basePath, genDir } ) ;
135
146
this . _basePath = basePath ;
136
147
this . _genDir = genDir ;
137
148
@@ -147,21 +158,16 @@ export class AotPlugin implements Tapable {
147
158
this . _rootFilePath , this . _compilerOptions , this . _compilerHost ) ;
148
159
149
160
if ( options . entryModule ) {
150
- this . _entryModule = ModuleRoute . fromString ( options . entryModule ) ;
161
+ this . _entryModule = options . entryModule ;
151
162
} else {
152
163
if ( options . mainPath ) {
153
- const entryModuleString = resolveEntryModuleFromMain ( options . mainPath , this . _compilerHost ,
164
+ this . _entryModule = resolveEntryModuleFromMain ( options . mainPath , this . _compilerHost ,
154
165
this . _program ) ;
155
- this . _entryModule = ModuleRoute . fromString ( entryModuleString ) ;
156
166
} else {
157
- this . _entryModule = ModuleRoute . fromString ( ( tsConfig . ngOptions as any ) . entryModule ) ;
167
+ this . _entryModule = ( tsConfig . raw [ 'angularCompilerOptions' ] as any ) . entryModule ;
158
168
}
159
169
}
160
170
161
- this . _reflectorHost = new ngCompiler . ReflectorHost (
162
- this . _program , this . _compilerHost , this . _angularCompilerOptions ) ;
163
- this . _reflector = new ngCompiler . StaticReflector ( this . _reflectorHost ) ;
164
-
165
171
if ( options . hasOwnProperty ( 'i18nFile' ) ) {
166
172
this . _i18nFile = options . i18nFile ;
167
173
}
@@ -253,20 +259,18 @@ export class AotPlugin implements Tapable {
253
259
}
254
260
255
261
// Create the Code Generator.
256
- const codeGenerator = ngCompiler . CodeGenerator . create (
257
- this . _angularCompilerOptions ,
258
- i18nOptions ,
259
- this . _program ,
260
- this . _compilerHost ,
261
- new ngCompiler . NodeReflectorHostContext ( this . _compilerHost ) ,
262
- this . _resourceLoader
263
- ) ;
264
-
265
- // We need to temporarily patch the CodeGenerator until either it's patched or allows us
266
- // to pass in our own ReflectorHost.
267
- // TODO: remove this.
268
- patchReflectorHost ( codeGenerator ) ;
269
- return codeGenerator . codegen ( { transitiveModules : true } ) ;
262
+ return __NGTOOLS_PRIVATE_API_2 . codeGen ( {
263
+ basePath : this . _basePath ,
264
+ compilerOptions : this . _compilerOptions ,
265
+ program : this . _program ,
266
+ host : this . _compilerHost ,
267
+ angularCompilerOptions : this . _angularCompilerOptions ,
268
+ i18nFormat : null ,
269
+ i18nFile : null ,
270
+ locale : null ,
271
+
272
+ readResource : ( path : string ) => this . _resourceLoader . get ( path )
273
+ } ) ;
270
274
} )
271
275
. then ( ( ) => {
272
276
// Create a new Program, based on the old one. This will trigger a resolution of all
@@ -298,155 +302,25 @@ export class AotPlugin implements Tapable {
298
302
. then ( ( ) => {
299
303
// Process the lazy routes
300
304
this . _lazyRoutes = { } ;
301
- const allLazyRoutes = this . _processNgModule ( this . _entryModule , null ) ;
305
+ const allLazyRoutes = __NGTOOLS_PRIVATE_API_2 . listLazyRoutes ( {
306
+ program : this . _program ,
307
+ host : this . _compilerHost ,
308
+ angularCompilerOptions : this . _angularCompilerOptions ,
309
+ entryModule : this . _entryModule . toString ( )
310
+ } ) ;
302
311
Object . keys ( allLazyRoutes )
303
312
. forEach ( k => {
304
313
const lazyRoute = allLazyRoutes [ k ] ;
305
314
if ( this . skipCodeGeneration ) {
306
- this . _lazyRoutes [ k ] = lazyRoute . absolutePath + '.ts' ;
315
+ this . _lazyRoutes [ k ] = lazyRoute ;
307
316
} else {
308
- this . _lazyRoutes [ k + '.ngfactory' ] = lazyRoute . absoluteGenDirPath + '.ngfactory.ts' ;
317
+ const lr = path . relative ( this . basePath , lazyRoute . replace ( / \. t s $ / , '.ngfactory.ts' ) ) ;
318
+ this . _lazyRoutes [ k + '.ngfactory' ] = path . join ( this . genDir , lr ) ;
309
319
}
310
320
} ) ;
311
321
} )
312
- . then ( ( ) => cb ( ) , ( err : any ) => { cb ( err ) ; } ) ;
313
- }
314
-
315
- private _resolveModulePath ( module : ModuleRoute , containingFile : string ) {
316
- if ( module . path . startsWith ( '.' ) ) {
317
- return path . join ( path . dirname ( containingFile ) , module . path ) ;
318
- }
319
- return module . path ;
320
- }
321
-
322
- private _processNgModule ( module : ModuleRoute , containingFile : string | null ) : LazyRouteMap {
323
- const modulePath = containingFile ? module . path : ( './' + path . basename ( module . path ) ) ;
324
- if ( containingFile === null ) {
325
- containingFile = module . path + '.ts' ;
326
- }
327
- const relativeModulePath = this . _resolveModulePath ( module , containingFile ) ;
328
-
329
- const staticSymbol = this . _reflectorHost
330
- . findDeclaration ( modulePath , module . className , containingFile ) ;
331
- const entryNgModuleMetadata = this . getNgModuleMetadata ( staticSymbol ) ;
332
- const loadChildrenRoute : LazyRoute [ ] = this . extractLoadChildren ( entryNgModuleMetadata )
333
- . map ( route => {
334
- const moduleRoute = ModuleRoute . fromString ( route ) ;
335
- const resolvedModule = ts . resolveModuleName ( moduleRoute . path ,
336
- relativeModulePath , this . _compilerOptions , this . _compilerHost ) ;
337
-
338
- if ( ! resolvedModule . resolvedModule ) {
339
- throw new Error ( `Could not resolve route "${ route } " from file "${ relativeModulePath } ".` ) ;
340
- }
341
-
342
- const relativePath = path . relative ( this . basePath ,
343
- resolvedModule . resolvedModule . resolvedFileName ) . replace ( / \. t s $ / , '' ) ;
344
-
345
- const absolutePath = path . join ( this . basePath , relativePath ) ;
346
- const absoluteGenDirPath = path . join ( this . _genDir , relativePath ) ;
347
-
348
- return {
349
- moduleRoute,
350
- absoluteGenDirPath,
351
- absolutePath
352
- } ;
322
+ . then ( ( ) => cb ( ) , ( err : any ) => {
323
+ compilation . errors . push ( err ) ;
353
324
} ) ;
354
- const resultMap : LazyRouteMap = loadChildrenRoute
355
- . reduce ( ( acc : LazyRouteMap , curr : LazyRoute ) => {
356
- const key = curr . moduleRoute . path ;
357
- if ( acc [ key ] ) {
358
- if ( acc [ key ] . absolutePath != curr . absolutePath ) {
359
- throw new Error ( `Duplicated path in loadChildren detected: "${ key } " is used in 2 ` +
360
- 'loadChildren, but they point to different modules. Webpack cannot distinguish ' +
361
- 'between the two based on context and would fail to load the proper one.' ) ;
362
- }
363
- } else {
364
- acc [ key ] = curr ;
365
- }
366
- return acc ;
367
- } , { } ) ;
368
-
369
- // Also concatenate every child of child modules.
370
- for ( const lazyRoute of loadChildrenRoute ) {
371
- const mr = lazyRoute . moduleRoute ;
372
- const children = this . _processNgModule ( mr , relativeModulePath ) ;
373
- Object . keys ( children ) . forEach ( p => {
374
- const child = children [ p ] ;
375
- const key = child . moduleRoute . path ;
376
- if ( resultMap [ key ] ) {
377
- if ( resultMap [ key ] . absolutePath != child . absolutePath ) {
378
- throw new Error ( `Duplicated path in loadChildren detected: "${ key } " is used in 2 ` +
379
- 'loadChildren, but they point to different modules. Webpack cannot distinguish ' +
380
- 'between the two based on context and would fail to load the proper one.' ) ;
381
- }
382
- } else {
383
- resultMap [ key ] = child ;
384
- }
385
- } ) ;
386
- }
387
- return resultMap ;
388
- }
389
-
390
- private getNgModuleMetadata ( staticSymbol : ngCompiler . StaticSymbol ) {
391
- const ngModules = this . _reflector . annotations ( staticSymbol ) . filter ( s => s instanceof NgModule ) ;
392
- if ( ngModules . length === 0 ) {
393
- throw new Error ( `${ staticSymbol . name } is not an NgModule` ) ;
394
- }
395
- return ngModules [ 0 ] ;
396
- }
397
-
398
- private extractLoadChildren ( ngModuleDecorator : any ) : any [ ] {
399
- const routes = ( ngModuleDecorator . imports || [ ] ) . reduce ( ( mem : any [ ] , m : any ) => {
400
- return mem . concat ( this . collectRoutes ( m . providers ) ) ;
401
- } , this . collectRoutes ( ngModuleDecorator . providers ) ) ;
402
- return this . collectLoadChildren ( routes )
403
- . concat ( ( ngModuleDecorator . imports || [ ] )
404
- // Also recursively extractLoadChildren of modules we import.
405
- . map ( ( staticSymbol : any ) => {
406
- if ( staticSymbol instanceof StaticSymbol ) {
407
- const entryNgModuleMetadata = this . getNgModuleMetadata ( staticSymbol ) ;
408
- return this . extractLoadChildren ( entryNgModuleMetadata ) ;
409
- } else {
410
- return [ ] ;
411
- }
412
- } )
413
- // Poor man's flat map.
414
- . reduce ( ( acc : any [ ] , i : any ) => acc . concat ( i ) , [ ] ) )
415
- . filter ( x => ! ! x ) ;
416
- }
417
-
418
- private collectRoutes ( providers : any [ ] ) : any [ ] {
419
- if ( ! providers ) {
420
- return [ ] ;
421
- }
422
- const ROUTES = this . _reflectorHost . findDeclaration (
423
- '@angular/router/src/router_config_loader' , 'ROUTES' , undefined ) ;
424
-
425
- return providers . reduce ( ( m , p ) => {
426
- if ( p . provide === ROUTES ) {
427
- return m . concat ( p . useValue ) ;
428
- } else if ( Array . isArray ( p ) ) {
429
- return m . concat ( this . collectRoutes ( p ) ) ;
430
- } else {
431
- return m ;
432
- }
433
- } , [ ] ) ;
434
- }
435
-
436
- private collectLoadChildren ( routes : any [ ] ) : any [ ] {
437
- if ( ! routes ) {
438
- return [ ] ;
439
- }
440
- return routes . reduce ( ( m , r ) => {
441
- if ( r . loadChildren ) {
442
- return m . concat ( r . loadChildren ) ;
443
- } else if ( Array . isArray ( r ) ) {
444
- return m . concat ( this . collectLoadChildren ( r ) ) ;
445
- } else if ( r . children ) {
446
- return m . concat ( this . collectLoadChildren ( r . children ) ) ;
447
- } else {
448
- return m ;
449
- }
450
- } , [ ] ) ;
451
325
}
452
326
}
0 commit comments