@@ -11,12 +11,16 @@ import {
1111 SchematicsException ,
1212 chain ,
1313 externalSchematic ,
14+ noop ,
1415} from '@angular-devkit/schematics' ;
16+ import * as ts from 'typescript' ;
1517import { getWorkspace } from '@schematics/angular/utility/workspace' ;
1618import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks' ;
1719import { Builders } from '@schematics/angular/utility/workspace-models' ;
18- import { normalize , join } from '@angular-devkit/core' ;
20+ import { normalize , join , Path } from '@angular-devkit/core' ;
1921import { Schema as UniversalOptions } from '@schematics/angular/universal/schema' ;
22+ import { getDecoratorMetadata , getMetadataField } from '@schematics/angular/utility/ast-utils' ;
23+ import { removePackageJsonDependency } from '@schematics/angular/utility/dependencies' ;
2024
2125export function version9UpdateRule ( collectionPath : string ) : Rule {
2226 return async host => {
@@ -97,13 +101,118 @@ function updateProjectsStructureRule(collectionPath: string): Rule {
97101 skipInstall : true ,
98102 } ;
99103
100- if ( ! collectionPath ) {
101- continue ;
102- }
103104 // Run the install schematic again so that we re-create the entire stucture.
104- installRules . push ( externalSchematic ( collectionPath , 'ng-add' , installOptions ) ) ;
105+ installRules . push (
106+ removeModuleMapNgfactoryLoaderRule ( normalize ( projectDefinition . sourceRoot ) ) ,
107+ collectionPath
108+ ? externalSchematic ( collectionPath , 'ng-add' , installOptions )
109+ : noop ( ) ,
110+ ) ;
105111 }
106112
107113 return chain ( installRules ) ;
108114 } ;
109115}
116+
117+ function removeModuleMapNgfactoryLoaderRule ( sourceRoot : Path ) : Rule {
118+ return tree => {
119+ const moduleMapLoaderPackageName = '@nguniversal/module-map-ngfactory-loader' ;
120+
121+ // Strip BOM as otherwise TSC methods (Ex: getWidth) will return an offset which
122+ // which breaks the CLI UpdateRecorder.
123+ // See: https://github.com/angular/angular/pull/30719
124+ const createSourceFile = ( path : string ) => ts . createSourceFile (
125+ path ,
126+ tree . read ( path ) . toString ( ) . replace ( / ^ \uFEFF / , '' ) ,
127+ ts . ScriptTarget . Latest ,
128+ true ,
129+ ) ;
130+
131+ // Update main.server file
132+ const mainServerPath = join ( sourceRoot , 'main.server.ts' ) ;
133+ if ( tree . exists ( mainServerPath ) ) {
134+ const recorder = tree . beginUpdate ( mainServerPath ) ;
135+
136+ // Remove exports of '@nguniversal/module-map-ngfactory-loader'
137+ createSourceFile ( mainServerPath )
138+ . statements
139+ . filter ( s => (
140+ ts . isExportDeclaration ( s ) &&
141+ s . moduleSpecifier &&
142+ ts . isStringLiteral ( s . moduleSpecifier ) &&
143+ s . moduleSpecifier . text === moduleMapLoaderPackageName
144+ ) )
145+ . forEach ( node => {
146+ const index = node . getFullStart ( ) ;
147+ const length = node . getFullWidth ( ) ;
148+ recorder . remove ( index , length ) ;
149+ } ) ;
150+ tree . commitUpdate ( recorder ) ;
151+ }
152+
153+ // Update app.server.module file
154+ const appServerModule = join ( sourceRoot , 'app/app.server.module.ts' ) ;
155+ if ( tree . exists ( appServerModule ) ) {
156+ const recorder = tree . beginUpdate ( appServerModule ) ;
157+ const appServerSourceFile = createSourceFile ( appServerModule ) ;
158+
159+ // Remove imports of '@nguniversal/module-map-ngfactory-loader'
160+ appServerSourceFile
161+ . statements
162+ . filter ( s => (
163+ ts . isImportDeclaration ( s ) &&
164+ s . moduleSpecifier &&
165+ ts . isStringLiteral ( s . moduleSpecifier ) &&
166+ s . moduleSpecifier . text === moduleMapLoaderPackageName
167+ ) )
168+ . forEach ( node => {
169+ const index = node . getFullStart ( ) ;
170+ const length = node . getFullWidth ( ) ;
171+ recorder . remove ( index , length ) ;
172+ } ) ;
173+
174+
175+ // Create a TS printer to get the text
176+ const printer = ts . createPrinter ( ) ;
177+
178+ // Remove 'ModuleMapLoaderModule' from 'NgModule' imports
179+ getDecoratorMetadata ( appServerSourceFile , 'NgModule' , '@angular/core' )
180+ . forEach ( ( metadata : ts . ObjectLiteralExpression ) => {
181+ const matchingProperties = getMetadataField ( metadata , 'imports' ) ;
182+
183+ if ( ! matchingProperties ) {
184+ return ;
185+ }
186+
187+ const assignment = matchingProperties [ 0 ] as ts . PropertyAssignment ;
188+ if ( ! ts . isArrayLiteralExpression ( assignment . initializer ) ) {
189+ return ;
190+ }
191+
192+ const arrayLiteral = assignment . initializer ;
193+ const newImports = arrayLiteral . elements
194+ . filter ( n => ! ( ts . isIdentifier ( n ) && n . text === 'ModuleMapLoaderModule' ) ) ;
195+
196+ if ( arrayLiteral . elements . length !== newImports . length ) {
197+ const newImportsText = printer . printNode (
198+ ts . EmitHint . Unspecified ,
199+ ts . updateArrayLiteral ( arrayLiteral , newImports ) ,
200+ appServerSourceFile ,
201+ ) ;
202+
203+ const index = arrayLiteral . getStart ( ) ;
204+ const length = arrayLiteral . getWidth ( ) ;
205+
206+ recorder
207+ . remove ( index , length )
208+ . insertLeft ( index , newImportsText ) ;
209+ }
210+ } ) ;
211+
212+ tree . commitUpdate ( recorder ) ;
213+ }
214+
215+ // Remove package dependency
216+ removePackageJsonDependency ( tree , moduleMapLoaderPackageName ) ;
217+ } ;
218+ }
0 commit comments