@@ -35,9 +35,17 @@ export function toStandalone(
3535
3636 for ( const sourceFile of sourceFiles ) {
3737 const { modules, testObjects} = findModulesToMigrate ( sourceFile , typeChecker ) ;
38- modules . forEach (
39- module => declarations . push ( ...extractDeclarationsFromModule ( module , templateTypeChecker ) ) ) ;
40- modulesToMigrate . push ( ...modules ) ;
38+
39+ for ( const module of modules ) {
40+ const moduleDeclarations =
41+ extractDeclarationsFromModule ( module , typeChecker , templateTypeChecker ) ;
42+
43+ if ( moduleDeclarations . length > 0 ) {
44+ modulesToMigrate . push ( module ) ;
45+ declarations . push ( ...moduleDeclarations ) ;
46+ }
47+ }
48+
4149 testObjectsToMigrate . push ( ...testObjects ) ;
4250 }
4351
@@ -153,13 +161,11 @@ function migrateNgModuleClass(
153161 node : ts . ClassDeclaration , allDeclarations : Reference < ts . ClassDeclaration > [ ] ,
154162 tracker : ChangeTracker , typeChecker : ts . TypeChecker , templateTypeChecker : TemplateTypeChecker ) {
155163 const decorator = templateTypeChecker . getNgModuleMetadata ( node ) ?. decorator ;
164+ const metadata = decorator ? extractMetadataLiteral ( decorator ) : null ;
156165
157- if ( decorator && ts . isCallExpression ( decorator . expression ) &&
158- decorator . expression . arguments . length === 1 &&
159- ts . isObjectLiteralExpression ( decorator . expression . arguments [ 0 ] ) ) {
166+ if ( metadata ) {
160167 moveDeclarationsToImports (
161- decorator . expression . arguments [ 0 ] , allDeclarations . map ( decl => decl . node ) , typeChecker ,
162- tracker ) ;
168+ metadata , allDeclarations . map ( decl => decl . node ) , typeChecker , tracker ) ;
163169 }
164170}
165171
@@ -355,18 +361,12 @@ function findModulesToMigrate(sourceFile: ts.SourceFile, typeChecker: ts.TypeChe
355361 if ( ts . isClassDeclaration ( node ) ) {
356362 const decorator = getAngularDecorators ( typeChecker , ts . getDecorators ( node ) || [ ] )
357363 . find ( current => current . name === 'NgModule' ) ;
358- const metadata = decorator ? .node . expression . arguments [ 0 ] ;
364+ const metadata = decorator ? extractMetadataLiteral ( decorator . node ) : null ;
359365
360- if ( metadata && ts . isObjectLiteralExpression ( metadata ) ) {
366+ if ( metadata ) {
361367 const declarations = findLiteralProperty ( metadata , 'declarations' ) ;
362- const bootstrap = findLiteralProperty ( metadata , 'bootstrap' ) ;
363- const hasDeclarations = declarations != null && hasNgModuleMetadataElements ( declarations ) ;
364- const hasBootstrap = bootstrap != null && hasNgModuleMetadataElements ( bootstrap ) ;
365-
366- // Skip modules that bootstrap components since changing them would also involve
367- // converting `bootstrapModule` calls to `bootstrapApplication`. These declarations
368- // will be converted to standalone in the `standalone-bootstrap` step.
369- if ( hasDeclarations && ! hasBootstrap ) {
368+
369+ if ( declarations != null && hasNgModuleMetadataElements ( declarations ) ) {
370370 modules . push ( node ) ;
371371 }
372372 }
@@ -424,11 +424,53 @@ function findTemplateDependencies(
424424
425425/** Extracts classes that are referred to in a module's `declarations` array. */
426426function extractDeclarationsFromModule (
427- ngModule : ts . ClassDeclaration ,
428- typeChecker : TemplateTypeChecker ) : Reference < ts . ClassDeclaration > [ ] {
429- return typeChecker . getNgModuleMetadata ( ngModule ) ?. declarations . filter (
430- decl => ts . isClassDeclaration ( decl . node ) ) as Reference < ts . ClassDeclaration > [ ] ||
431- [ ] ;
427+ ngModule : ts . ClassDeclaration , typeChecker : ts . TypeChecker ,
428+ templateTypeChecker : TemplateTypeChecker ) : Reference < ts . ClassDeclaration > [ ] {
429+ const metadata = templateTypeChecker . getNgModuleMetadata ( ngModule ) ;
430+
431+ if ( ! metadata ) {
432+ return [ ] ;
433+ }
434+
435+ const allDeclarations = metadata . declarations . filter ( decl => ts . isClassDeclaration ( decl . node ) ) as
436+ Reference < ts . ClassDeclaration > [ ] ;
437+ const metaLiteral = metadata . decorator ? extractMetadataLiteral ( metadata . decorator ) : null ;
438+ const bootstrapProp = metaLiteral ? findLiteralProperty ( metaLiteral , 'bootstrap' ) : null ;
439+
440+ // If there's no `bootstrap`, we can migrate all the declarations.
441+ if ( ! bootstrapProp ) {
442+ return allDeclarations ;
443+ }
444+
445+ // If we can't analyze the `bootstrap` property, we can't safely migrate the module.
446+ if ( ! ts . isPropertyAssignment ( bootstrapProp ) ||
447+ ! ts . isArrayLiteralExpression ( bootstrapProp . initializer ) ) {
448+ return [ ] ;
449+ }
450+
451+ const bootstrappedClasses = new Set < ts . ClassDeclaration > ( ) ;
452+
453+ for ( const el of bootstrapProp . initializer . elements ) {
454+ const referencedClass = ts . isIdentifier ( el ) ? findClassDeclaration ( el , typeChecker ) : null ;
455+
456+ // If we manage to resolve the class, we can use it to filter out declarations that are
457+ // being bootstrapped. Otherwise we risk breaking the app so we skip the entire module.
458+ if ( referencedClass ) {
459+ bootstrappedClasses . add ( referencedClass ) ;
460+ } else {
461+ return [ ] ;
462+ }
463+ }
464+
465+ return allDeclarations . filter ( ref => ! bootstrappedClasses . has ( ref . node ) ) ;
466+ }
467+
468+ /** Extracts the metadata object literal from an Angular decorator. */
469+ function extractMetadataLiteral ( decorator : ts . Decorator ) : ts . ObjectLiteralExpression | null {
470+ return ts . isCallExpression ( decorator . expression ) && decorator . expression . arguments . length === 1 &&
471+ ts . isObjectLiteralExpression ( decorator . expression . arguments [ 0 ] ) ?
472+ decorator . expression . arguments [ 0 ] :
473+ null ;
432474}
433475
434476/**
0 commit comments