@@ -62,8 +62,94 @@ function getServerModulePath(host: Tree, app: AppConfig): string | null {
6262
6363 return modulePath ;
6464}
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+ }
65129// end helper functions.
66130
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+
67153function addUniversalApp ( options : AppShellOptions ) : Rule {
68154 return ( host : Tree , context : SchematicContext ) => {
69155 const config = getConfig ( host ) ;
@@ -153,72 +239,6 @@ function getMetadataProperty(metadata: ts.Node, propertyName: string): ts.Proper
153239 return property as ts . PropertyAssignment ;
154240}
155241
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-
222242function addServerRoutes ( options : AppShellOptions ) : Rule {
223243 return ( host : Tree ) => {
224244 const config = getConfig ( host ) ;
@@ -293,10 +313,10 @@ function addShellComponent(options: AppShellOptions): Rule {
293313
294314export default function ( options : AppShellOptions ) : Rule {
295315 return chain ( [
316+ validateProject ( options ) ,
296317 addUniversalApp ( options ) ,
297318 addAppShellConfig ( options ) ,
298319 addRouterModule ( options ) ,
299- addRouterOutlet ( options ) ,
300320 addServerRoutes ( options ) ,
301321 addShellComponent ( options ) ,
302322 ] ) ;
0 commit comments