@@ -16,19 +16,20 @@ import {
16
16
noop ,
17
17
schematic ,
18
18
} from '@angular-devkit/schematics' ;
19
- import { Schema as ComponentOptions } from '../component/schema ' ;
19
+ import { findBootstrapApplicationCall } from '../private/standalone ' ;
20
20
import * as ts from '../third_party/github.com/Microsoft/TypeScript/lib/typescript' ;
21
21
import {
22
22
addImportToModule ,
23
23
addSymbolToNgModuleMetadata ,
24
24
findNode ,
25
+ findNodes ,
25
26
getDecoratorMetadata ,
26
27
getSourceNodes ,
27
28
insertImport ,
28
29
isImported ,
29
30
} from '../utility/ast-utils' ;
30
31
import { applyToUpdateRecorder } from '../utility/change' ;
31
- import { getAppModulePath } from '../utility/ng-ast-utils' ;
32
+ import { getAppModulePath , isStandaloneApp } from '../utility/ng-ast-utils' ;
32
33
import { targetBuildNotFoundError } from '../utility/project-targets' ;
33
34
import { getWorkspace , updateWorkspace } from '../utility/workspace' ;
34
35
import { BrowserBuilderOptions , Builders , ServerBuilderOptions } from '../utility/workspace-models' ;
@@ -87,28 +88,42 @@ function getComponentTemplate(host: Tree, compPath: string, tmplInfo: TemplateIn
87
88
}
88
89
89
90
function getBootstrapComponentPath ( host : Tree , mainPath : string ) : string {
90
- const modulePath = getAppModulePath ( host , mainPath ) ;
91
- const moduleSource = getSourceFile ( host , modulePath ) ;
92
-
93
- const metadataNode = getDecoratorMetadata ( moduleSource , 'NgModule' , '@angular/core' ) [ 0 ] ;
94
- const bootstrapProperty = getMetadataProperty ( metadataNode , 'bootstrap' ) ;
95
-
96
- const arrLiteral = bootstrapProperty . initializer as ts . ArrayLiteralExpression ;
97
-
98
- const componentSymbol = arrLiteral . elements [ 0 ] . getText ( ) ;
91
+ const mainSource = getSourceFile ( host , mainPath ) ;
92
+ const bootstrapAppCall = findBootstrapApplicationCall ( mainSource ) ;
93
+
94
+ let bootstrappingFilePath : string ;
95
+ let bootstrappingSource : ts . SourceFile ;
96
+ let componentName : string ;
97
+
98
+ if ( bootstrapAppCall ) {
99
+ // Standalone Application
100
+ componentName = bootstrapAppCall . arguments [ 0 ] . getText ( ) ;
101
+ bootstrappingFilePath = mainPath ;
102
+ bootstrappingSource = mainSource ;
103
+ } else {
104
+ // NgModule Application
105
+ const modulePath = getAppModulePath ( host , mainPath ) ;
106
+ const moduleSource = getSourceFile ( host , modulePath ) ;
107
+ const metadataNode = getDecoratorMetadata ( moduleSource , 'NgModule' , '@angular/core' ) [ 0 ] ;
108
+ const bootstrapProperty = getMetadataProperty ( metadataNode , 'bootstrap' ) ;
109
+ const arrLiteral = bootstrapProperty . initializer as ts . ArrayLiteralExpression ;
110
+ componentName = arrLiteral . elements [ 0 ] . getText ( ) ;
111
+ bootstrappingSource = moduleSource ;
112
+ bootstrappingFilePath = modulePath ;
113
+ }
99
114
100
- const relativePath = getSourceNodes ( moduleSource )
115
+ const componentRelativeFilePath = getSourceNodes ( bootstrappingSource )
101
116
. filter ( ts . isImportDeclaration )
102
117
. filter ( ( imp ) => {
103
- return findNode ( imp , ts . SyntaxKind . Identifier , componentSymbol ) ;
118
+ return findNode ( imp , ts . SyntaxKind . Identifier , componentName ) ;
104
119
} )
105
120
. map ( ( imp ) => {
106
121
const pathStringLiteral = imp . moduleSpecifier as ts . StringLiteral ;
107
122
108
123
return pathStringLiteral . text ;
109
124
} ) [ 0 ] ;
110
125
111
- return join ( dirname ( normalize ( modulePath ) ) , relativePath + '.ts' ) ;
126
+ return join ( dirname ( normalize ( bootstrappingFilePath ) ) , componentRelativeFilePath + '.ts' ) ;
112
127
}
113
128
// end helper functions.
114
129
@@ -300,14 +315,97 @@ function addServerRoutes(options: AppShellOptions): Rule {
300
315
} ;
301
316
}
302
317
303
- function addShellComponent ( options : AppShellOptions ) : Rule {
304
- const componentOptions : ComponentOptions = {
305
- name : 'app-shell' ,
306
- module : options . rootModuleFileName ,
307
- project : options . project ,
308
- } ;
318
+ function addStandaloneServerRoute ( options : AppShellOptions ) : Rule {
319
+ return async ( host : Tree ) => {
320
+ const workspace = await getWorkspace ( host ) ;
321
+ const project = workspace . projects . get ( options . project ) ;
322
+ if ( ! project ) {
323
+ throw new SchematicsException ( `Project name "${ options . project } " doesn't not exist.` ) ;
324
+ }
325
+
326
+ const configFilePath = join ( normalize ( project . sourceRoot ?? 'src' ) , 'app/app.config.server.ts' ) ;
327
+ if ( ! host . exists ( configFilePath ) ) {
328
+ throw new SchematicsException ( `Cannot find "${ configFilePath } ".` ) ;
329
+ }
330
+
331
+ let configSourceFile = getSourceFile ( host , configFilePath ) ;
332
+ if ( ! isImported ( configSourceFile , 'ROUTES' , '@angular/router' ) ) {
333
+ const routesChange = insertImport (
334
+ configSourceFile ,
335
+ configFilePath ,
336
+ 'ROUTES' ,
337
+ '@angular/router' ,
338
+ ) ;
309
339
310
- return schematic ( 'component' , componentOptions ) ;
340
+ const recorder = host . beginUpdate ( configFilePath ) ;
341
+ if ( routesChange ) {
342
+ applyToUpdateRecorder ( recorder , [ routesChange ] ) ;
343
+ host . commitUpdate ( recorder ) ;
344
+ }
345
+ }
346
+
347
+ configSourceFile = getSourceFile ( host , configFilePath ) ;
348
+ const providersLiteral = findNodes ( configSourceFile , ts . isPropertyAssignment ) . find (
349
+ ( n ) => ts . isArrayLiteralExpression ( n . initializer ) && n . name . getText ( ) === 'providers' ,
350
+ ) ?. initializer as ts . ArrayLiteralExpression | undefined ;
351
+ if ( ! providersLiteral ) {
352
+ throw new SchematicsException (
353
+ `Cannot find the "providers" configuration in "${ configFilePath } ".` ,
354
+ ) ;
355
+ }
356
+
357
+ // Add route to providers literal.
358
+ const newProvidersLiteral = ts . factory . updateArrayLiteralExpression ( providersLiteral , [
359
+ ...providersLiteral . elements ,
360
+ ts . factory . createObjectLiteralExpression (
361
+ [
362
+ ts . factory . createPropertyAssignment ( 'provide' , ts . factory . createIdentifier ( 'ROUTES' ) ) ,
363
+ ts . factory . createPropertyAssignment ( 'multi' , ts . factory . createIdentifier ( 'true' ) ) ,
364
+ ts . factory . createPropertyAssignment (
365
+ 'useValue' ,
366
+ ts . factory . createArrayLiteralExpression (
367
+ [
368
+ ts . factory . createObjectLiteralExpression (
369
+ [
370
+ ts . factory . createPropertyAssignment (
371
+ 'path' ,
372
+ ts . factory . createIdentifier ( `'${ options . route } '` ) ,
373
+ ) ,
374
+ ts . factory . createPropertyAssignment (
375
+ 'component' ,
376
+ ts . factory . createIdentifier ( 'AppShellComponent' ) ,
377
+ ) ,
378
+ ] ,
379
+ true ,
380
+ ) ,
381
+ ] ,
382
+ true ,
383
+ ) ,
384
+ ) ,
385
+ ] ,
386
+ true ,
387
+ ) ,
388
+ ] ) ;
389
+
390
+ const recorder = host . beginUpdate ( configFilePath ) ;
391
+ recorder . remove ( providersLiteral . getStart ( ) , providersLiteral . getWidth ( ) ) ;
392
+ const printer = ts . createPrinter ( ) ;
393
+ recorder . insertRight (
394
+ providersLiteral . getStart ( ) ,
395
+ printer . printNode ( ts . EmitHint . Unspecified , newProvidersLiteral , configSourceFile ) ,
396
+ ) ;
397
+
398
+ // Add AppShellComponent import
399
+ const appShellImportChange = insertImport (
400
+ configSourceFile ,
401
+ configFilePath ,
402
+ 'AppShellComponent' ,
403
+ './app-shell/app-shell.component' ,
404
+ ) ;
405
+
406
+ applyToUpdateRecorder ( recorder , [ appShellImportChange ] ) ;
407
+ host . commitUpdate ( recorder ) ;
408
+ } ;
311
409
}
312
410
313
411
export default function ( options : AppShellOptions ) : Rule {
@@ -324,13 +422,20 @@ export default function (options: AppShellOptions): Rule {
324
422
const clientBuildOptions = ( clientBuildTarget . options ||
325
423
{ } ) as unknown as BrowserBuilderOptions ;
326
424
425
+ const isStandalone = isStandaloneApp ( tree , clientBuildOptions . main ) ;
426
+
327
427
return chain ( [
328
428
validateProject ( clientBuildOptions . main ) ,
329
429
clientProject . targets . has ( 'server' ) ? noop ( ) : addUniversalTarget ( options ) ,
330
430
addAppShellConfigToWorkspace ( options ) ,
331
- addRouterModule ( clientBuildOptions . main ) ,
332
- addServerRoutes ( options ) ,
333
- addShellComponent ( options ) ,
431
+ isStandalone ? noop ( ) : addRouterModule ( clientBuildOptions . main ) ,
432
+ isStandalone ? addStandaloneServerRoute ( options ) : addServerRoutes ( options ) ,
433
+ schematic ( 'component' , {
434
+ name : 'app-shell' ,
435
+ module : options . rootModuleFileName ,
436
+ project : options . project ,
437
+ standalone : isStandalone ,
438
+ } ) ,
334
439
] ) ;
335
440
} ;
336
441
}
0 commit comments