@@ -16,19 +16,20 @@ import {
1616 noop ,
1717 schematic ,
1818} from '@angular-devkit/schematics' ;
19- import { Schema as ComponentOptions } from '../component/schema ' ;
19+ import { findBootstrapApplicationCall } from '../private/standalone ' ;
2020import * as ts from '../third_party/github.com/Microsoft/TypeScript/lib/typescript' ;
2121import {
2222 addImportToModule ,
2323 addSymbolToNgModuleMetadata ,
2424 findNode ,
25+ findNodes ,
2526 getDecoratorMetadata ,
2627 getSourceNodes ,
2728 insertImport ,
2829 isImported ,
2930} from '../utility/ast-utils' ;
3031import { applyToUpdateRecorder } from '../utility/change' ;
31- import { getAppModulePath } from '../utility/ng-ast-utils' ;
32+ import { getAppModulePath , isStandaloneApp } from '../utility/ng-ast-utils' ;
3233import { targetBuildNotFoundError } from '../utility/project-targets' ;
3334import { getWorkspace , updateWorkspace } from '../utility/workspace' ;
3435import { BrowserBuilderOptions , Builders , ServerBuilderOptions } from '../utility/workspace-models' ;
@@ -87,28 +88,42 @@ function getComponentTemplate(host: Tree, compPath: string, tmplInfo: TemplateIn
8788}
8889
8990function 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+ }
99114
100- const relativePath = getSourceNodes ( moduleSource )
115+ const componentRelativeFilePath = getSourceNodes ( bootstrappingSource )
101116 . filter ( ts . isImportDeclaration )
102117 . filter ( ( imp ) => {
103- return findNode ( imp , ts . SyntaxKind . Identifier , componentSymbol ) ;
118+ return findNode ( imp , ts . SyntaxKind . Identifier , componentName ) ;
104119 } )
105120 . map ( ( imp ) => {
106121 const pathStringLiteral = imp . moduleSpecifier as ts . StringLiteral ;
107122
108123 return pathStringLiteral . text ;
109124 } ) [ 0 ] ;
110125
111- return join ( dirname ( normalize ( modulePath ) ) , relativePath + '.ts' ) ;
126+ return join ( dirname ( normalize ( bootstrappingFilePath ) ) , componentRelativeFilePath + '.ts' ) ;
112127}
113128// end helper functions.
114129
@@ -300,14 +315,97 @@ function addServerRoutes(options: AppShellOptions): Rule {
300315 } ;
301316}
302317
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+ ) ;
309339
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+ } ;
311409}
312410
313411export default function ( options : AppShellOptions ) : Rule {
@@ -324,13 +422,20 @@ export default function (options: AppShellOptions): Rule {
324422 const clientBuildOptions = ( clientBuildTarget . options ||
325423 { } ) as unknown as BrowserBuilderOptions ;
326424
425+ const isStandalone = isStandaloneApp ( tree , clientBuildOptions . main ) ;
426+
327427 return chain ( [
328428 validateProject ( clientBuildOptions . main ) ,
329429 clientProject . targets . has ( 'server' ) ? noop ( ) : addUniversalTarget ( options ) ,
330430 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+ } ) ,
334439 ] ) ;
335440 } ;
336441}
0 commit comments