@@ -62,8 +62,94 @@ function getServerModulePath(host: Tree, app: AppConfig): string | null {
62
62
63
63
return modulePath ;
64
64
}
65
+
66
+ interface TemplateInfo {
67
+ templateProp ?: ts . PropertyAssignment ;
68
+ templateUrlProp ?: ts . PropertyAssignment ;
69
+ }
70
+
71
+ function getComponentTemplateInfo ( host : Tree , componentPath : string ) : TemplateInfo {
72
+ const compSource = getSourceFile ( host , componentPath ) ;
73
+ const compMetadata = getDecoratorMetadata ( compSource , 'Component' , '@angular/core' ) [ 0 ] ;
74
+
75
+ return {
76
+ templateProp : getMetadataProperty ( compMetadata , 'template' ) ,
77
+ templateUrlProp : getMetadataProperty ( compMetadata , 'templateUrl' ) ,
78
+ } ;
79
+ }
80
+
81
+ function getComponentTemplate ( host : Tree , compPath : string , tmplInfo : TemplateInfo ) : string {
82
+ let template = '' ;
83
+
84
+ if ( tmplInfo . templateProp ) {
85
+ template = tmplInfo . templateProp . getFullText ( ) ;
86
+ } else if ( tmplInfo . templateUrlProp ) {
87
+ const templateUrl = ( tmplInfo . templateUrlProp . initializer as ts . StringLiteral ) . text ;
88
+ const dirEntry = host . getDir ( compPath ) ;
89
+ const dir = dirEntry . parent ? dirEntry . parent . path : '/' ;
90
+ const templatePath = normalize ( `/${ dir } /${ templateUrl } ` ) ;
91
+ const buffer = host . read ( templatePath ) ;
92
+ if ( buffer ) {
93
+ template = buffer . toString ( ) ;
94
+ }
95
+ }
96
+
97
+ return template ;
98
+ }
99
+
100
+ function getBootstrapComponentPath ( host : Tree , appConfig : AppConfig ) : string {
101
+ const modulePath = getAppModulePath ( host , appConfig ) ;
102
+ const moduleSource = getSourceFile ( host , modulePath ) ;
103
+
104
+ const metadataNode = getDecoratorMetadata ( moduleSource , 'NgModule' , '@angular/core' ) [ 0 ] ;
105
+ const bootstrapProperty = getMetadataProperty ( metadataNode , 'bootstrap' ) ;
106
+
107
+ const arrLiteral = ( < ts . PropertyAssignment > bootstrapProperty )
108
+ . initializer as ts . ArrayLiteralExpression ;
109
+
110
+ const componentSymbol = arrLiteral . elements [ 0 ] . getText ( ) ;
111
+
112
+ const relativePath = getSourceNodes ( moduleSource )
113
+ . filter ( node => node . kind === ts . SyntaxKind . ImportDeclaration )
114
+ . filter ( imp => {
115
+ return findNode ( imp , ts . SyntaxKind . Identifier , componentSymbol ) ;
116
+ } )
117
+ . map ( ( imp : ts . ImportDeclaration ) => {
118
+ const pathStringLiteral = < ts . StringLiteral > imp . moduleSpecifier ;
119
+
120
+ return pathStringLiteral . text ;
121
+ } ) [ 0 ] ;
122
+
123
+ const dirEntry = host . getDir ( modulePath ) ;
124
+ const dir = dirEntry . parent ? dirEntry . parent . path : '/' ;
125
+ const compPath = normalize ( `/${ dir } /${ relativePath } .ts` ) ;
126
+
127
+ return compPath ;
128
+ }
65
129
// end helper functions.
66
130
131
+ function validateProject ( options : AppShellOptions ) : Rule {
132
+ return ( host : Tree , context : SchematicContext ) => {
133
+ const routerOutletCheckRegex = / < r o u t e r \- o u t l e t .* ?> ( [ \s \S ] * ?) < \/ r o u t e r \- o u t l e t > / ;
134
+
135
+ const config = getConfig ( host ) ;
136
+ const app = getAppFromConfig ( config , options . clientApp || '0' ) ;
137
+ if ( app === null ) {
138
+ throw new SchematicsException ( formatMissingAppMsg ( 'Client' , options . clientApp ) ) ;
139
+ }
140
+
141
+ const componentPath = getBootstrapComponentPath ( host , app ) ;
142
+ const tmpl = getComponentTemplateInfo ( host , componentPath ) ;
143
+ const template = getComponentTemplate ( host , componentPath , tmpl ) ;
144
+ if ( ! routerOutletCheckRegex . test ( template ) ) {
145
+ const errorMsg =
146
+ `Prerequisite for app shell is to define a router-outlet in your root component.` ;
147
+ context . logger . error ( errorMsg ) ;
148
+ throw new SchematicsException ( errorMsg ) ;
149
+ }
150
+ } ;
151
+ }
152
+
67
153
function addUniversalApp ( options : AppShellOptions ) : Rule {
68
154
return ( host : Tree , context : SchematicContext ) => {
69
155
const config = getConfig ( host ) ;
@@ -153,72 +239,6 @@ function getMetadataProperty(metadata: ts.Node, propertyName: string): ts.Proper
153
239
return property as ts . PropertyAssignment ;
154
240
}
155
241
156
- function addRouterOutlet ( options : AppShellOptions ) : Rule {
157
- return ( host : Tree ) => {
158
- const routerOutletMarkup = `<router-outlet></router-outlet>` ;
159
-
160
- const config = getConfig ( host ) ;
161
- const app = getAppFromConfig ( config , options . clientApp || '0' ) ;
162
- if ( app === null ) {
163
- throw new SchematicsException ( formatMissingAppMsg ( 'Client' , options . clientApp ) ) ;
164
- }
165
- const modulePath = getAppModulePath ( host , app ) ;
166
- const moduleSource = getSourceFile ( host , modulePath ) ;
167
-
168
- const metadataNode = getDecoratorMetadata ( moduleSource , 'NgModule' , '@angular/core' ) [ 0 ] ;
169
- const bootstrapProperty = getMetadataProperty ( metadataNode , 'bootstrap' ) ;
170
-
171
- const arrLiteral = ( < ts . PropertyAssignment > bootstrapProperty )
172
- . initializer as ts . ArrayLiteralExpression ;
173
-
174
- const componentSymbol = arrLiteral . elements [ 0 ] . getText ( ) ;
175
-
176
- const relativePath = getSourceNodes ( moduleSource )
177
- . filter ( node => node . kind === ts . SyntaxKind . ImportDeclaration )
178
- . filter ( imp => {
179
- return findNode ( imp , ts . SyntaxKind . Identifier , componentSymbol ) ;
180
- } )
181
- . map ( ( imp : ts . ImportDeclaration ) => {
182
- const pathStringLiteral = < ts . StringLiteral > imp . moduleSpecifier ;
183
-
184
- return pathStringLiteral . text ;
185
- } ) [ 0 ] ;
186
-
187
- const dirEntry = host . getDir ( modulePath ) ;
188
- const dir = dirEntry . parent ? dirEntry . parent . path : '/' ;
189
- const compPath = normalize ( `/${ dir } /${ relativePath } .ts` ) ;
190
-
191
- const compSource = getSourceFile ( host , compPath ) ;
192
- const compMetadata = getDecoratorMetadata ( compSource , 'Component' , '@angular/core' ) [ 0 ] ;
193
- const templateProp = getMetadataProperty ( compMetadata , 'template' ) ;
194
- const templateUrlProp = getMetadataProperty ( compMetadata , 'templateUrl' ) ;
195
-
196
- if ( templateProp ) {
197
- if ( ! / < r o u t e r \- o u t l e t > / . test ( templateProp . initializer . getText ( ) ) ) {
198
- const recorder = host . beginUpdate ( compPath ) ;
199
- recorder . insertRight ( templateProp . initializer . getEnd ( ) - 1 , routerOutletMarkup ) ;
200
- host . commitUpdate ( recorder ) ;
201
- }
202
- } else {
203
- const templateUrl = ( templateUrlProp . initializer as ts . StringLiteral ) . text ;
204
- const dirEntry = host . getDir ( compPath ) ;
205
- const dir = dirEntry . parent ? dirEntry . parent . path : '/' ;
206
- const templatePath = normalize ( `/${ dir } /${ templateUrl } ` ) ;
207
- const buffer = host . read ( templatePath ) ;
208
- if ( buffer ) {
209
- const content = buffer . toString ( ) ;
210
- if ( ! / < r o u t e r \- o u t l e t > / . test ( content ) ) {
211
- const recorder = host . beginUpdate ( templatePath ) ;
212
- recorder . insertRight ( buffer . length , routerOutletMarkup ) ;
213
- host . commitUpdate ( recorder ) ;
214
- }
215
- }
216
- }
217
-
218
- return host ;
219
- } ;
220
- }
221
-
222
242
function addServerRoutes ( options : AppShellOptions ) : Rule {
223
243
return ( host : Tree ) => {
224
244
const config = getConfig ( host ) ;
@@ -293,10 +313,10 @@ function addShellComponent(options: AppShellOptions): Rule {
293
313
294
314
export default function ( options : AppShellOptions ) : Rule {
295
315
return chain ( [
316
+ validateProject ( options ) ,
296
317
addUniversalApp ( options ) ,
297
318
addAppShellConfig ( options ) ,
298
319
addRouterModule ( options ) ,
299
- addRouterOutlet ( options ) ,
300
320
addServerRoutes ( options ) ,
301
321
addShellComponent ( options ) ,
302
322
] ) ;
0 commit comments