55 * Use of this source code is governed by an MIT-style license that can be
66 * found in the LICENSE file at https://angular.io/license
77 */
8- import { experimental , strings } from '@angular-devkit/core' ;
8+ import { experimental , strings , normalize } from '@angular-devkit/core' ;
99import {
1010 Rule ,
1111 SchematicContext ,
@@ -28,6 +28,12 @@ import {
2828 addPackageJsonDependency ,
2929 NodeDependencyType ,
3030} from '@schematics/angular/utility/dependencies' ;
31+ import { getProject } from '@schematics/angular/utility/project' ;
32+ import { getProjectTargets } from '@schematics/angular/utility/project-targets' ;
33+ import * as ts from 'typescript' ;
34+ import { findAppServerModulePath } from './utils' ;
35+ import { addSymbolToNgModuleMetadata , insertImport } from '@schematics/angular/utility/ast-utils' ;
36+ import { InsertChange } from '@schematics/angular/utility/change' ;
3137
3238// TODO(CaerusKaru): make these configurable
3339const BROWSER_DIST = 'dist/browser' ;
@@ -146,6 +152,59 @@ function updateConfigFile(options: UniversalOptions): Rule {
146152 } ;
147153}
148154
155+ function addModuleMapLoader ( options : UniversalOptions ) : Rule {
156+ return ( host : Tree ) => {
157+ const clientProject = getProject ( host , options . clientProject ) ;
158+ const clientTargets = getProjectTargets ( clientProject ) ;
159+ if ( ! clientTargets . server ) {
160+ // If they skipped Universal schematics and don't have a server target,
161+ // just get out
162+ return ;
163+ }
164+ const mainPath = normalize ( '/' + clientTargets . server . options . main ) ;
165+
166+ const appServerModuleRelativePath = findAppServerModulePath ( host , mainPath ) ;
167+ const modulePath = normalize (
168+ `/${ clientProject . root } /src/${ appServerModuleRelativePath } .ts` ) ;
169+
170+ // Add the module map loader import
171+ let moduleSource = getTsSourceFile ( host , modulePath ) ;
172+ const importModule = 'ModuleMapLoaderModule' ;
173+ const importPath = '@nguniversal/module-map-ngfactory-loader' ;
174+ const moduleMapImportChange = insertImport ( moduleSource , modulePath , importModule ,
175+ importPath ) as InsertChange ;
176+ if ( moduleMapImportChange ) {
177+ const recorder = host . beginUpdate ( modulePath ) ;
178+ recorder . insertLeft ( moduleMapImportChange . pos , moduleMapImportChange . toAdd ) ;
179+ host . commitUpdate ( recorder ) ;
180+ }
181+
182+ // Add the module map loader module to the imports
183+ const importText = 'ModuleMapLoaderModule' ;
184+ moduleSource = getTsSourceFile ( host , modulePath ) ;
185+ const metadataChanges = addSymbolToNgModuleMetadata (
186+ moduleSource , modulePath , 'imports' , importText ) ;
187+ if ( metadataChanges ) {
188+ const recorder = host . beginUpdate ( modulePath ) ;
189+ metadataChanges . forEach ( ( change : InsertChange ) => {
190+ recorder . insertRight ( change . pos , change . toAdd ) ;
191+ } ) ;
192+ host . commitUpdate ( recorder ) ;
193+ }
194+ } ;
195+ }
196+
197+ function getTsSourceFile ( host : Tree , path : string ) : ts . SourceFile {
198+ const buffer = host . read ( path ) ;
199+ if ( ! buffer ) {
200+ throw new SchematicsException ( `Could not read file (${ path } ).` ) ;
201+ }
202+ const content = buffer . toString ( ) ;
203+ const source = ts . createSourceFile ( path , content , ts . ScriptTarget . Latest , true ) ;
204+
205+ return source ;
206+ }
207+
149208export default function ( options : UniversalOptions ) : Rule {
150209 return ( host : Tree , context : SchematicContext ) => {
151210 const clientProject = getClientProject ( host , options ) ;
@@ -176,6 +235,7 @@ export default function (options: UniversalOptions): Rule {
176235 updateConfigFile ( options ) ,
177236 mergeWith ( rootSource ) ,
178237 addDependenciesAndScripts ( options ) ,
238+ addModuleMapLoader ( options ) ,
179239 ] ) ;
180240 } ;
181241}
0 commit comments