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 apply ,
1111 chain ,
@@ -28,6 +28,15 @@ import {
2828 NodeDependencyType ,
2929} from '@schematics/angular/utility/dependencies' ;
3030import { BrowserBuilderOptions } from '@schematics/angular/utility/workspace-models' ;
31+ import { getProject } from '@schematics/angular/utility/project' ;
32+ import {
33+ getProjectTargets ,
34+ targetBuildNotFoundError ,
35+ } from '@schematics/angular/utility/project-targets' ;
36+ import { InsertChange } from '@schematics/angular/utility/change' ;
37+ import { addSymbolToNgModuleMetadata , insertImport } from '@schematics/angular/utility/ast-utils' ;
38+ import * as ts from 'typescript' ;
39+ import { findAppServerModulePath } from './utils' ;
3140
3241// TODO(CaerusKaru): make these configurable
3342const BROWSER_DIST = 'dist/browser' ;
@@ -141,6 +150,59 @@ function updateConfigFile(options: UniversalOptions): Rule {
141150 } ;
142151}
143152
153+ function addModuleMapLoader ( options : UniversalOptions ) : Rule {
154+ return ( host : Tree ) => {
155+ const clientProject = getProject ( host , options . clientProject ) ;
156+ const clientTargets = getProjectTargets ( clientProject ) ;
157+ if ( ! clientTargets . server ) {
158+ // If they skipped Universal schematics and don't have a server target,
159+ // just get out
160+ return ;
161+ }
162+ const mainPath = normalize ( '/' + clientTargets . server . options . main ) ;
163+
164+ const appServerModuleRelativePath = findAppServerModulePath ( host , mainPath ) ;
165+ const modulePath = normalize (
166+ `/${ clientProject . root } /src/${ appServerModuleRelativePath } .ts` ) ;
167+
168+ // Add the module map loader import
169+ let moduleSource = getTsSourceFile ( host , modulePath ) ;
170+ const importModule = 'ModuleMapLoaderModule' ;
171+ const importPath = '@nguniversal/module-map-ngfactory-loader' ;
172+ const moduleMapImportChange = insertImport ( moduleSource , modulePath , importModule ,
173+ importPath ) as InsertChange ;
174+ if ( moduleMapImportChange ) {
175+ const recorder = host . beginUpdate ( modulePath ) ;
176+ recorder . insertLeft ( moduleMapImportChange . pos , moduleMapImportChange . toAdd ) ;
177+ host . commitUpdate ( recorder ) ;
178+ }
179+
180+ // Add the module map loader module to the imports
181+ const importText = 'ModuleMapLoaderModule' ;
182+ moduleSource = getTsSourceFile ( host , modulePath ) ;
183+ const metadataChanges = addSymbolToNgModuleMetadata (
184+ moduleSource , modulePath , 'imports' , importText ) ;
185+ if ( metadataChanges ) {
186+ const recorder = host . beginUpdate ( modulePath ) ;
187+ metadataChanges . forEach ( ( change : InsertChange ) => {
188+ recorder . insertRight ( change . pos , change . toAdd ) ;
189+ } ) ;
190+ host . commitUpdate ( recorder ) ;
191+ }
192+ } ;
193+ }
194+
195+ function getTsSourceFile ( host : Tree , path : string ) : ts . SourceFile {
196+ const buffer = host . read ( path ) ;
197+ if ( ! buffer ) {
198+ throw new SchematicsException ( `Could not read file (${ path } ).` ) ;
199+ }
200+ const content = buffer . toString ( ) ;
201+ const source = ts . createSourceFile ( path , content , ts . ScriptTarget . Latest , true ) ;
202+
203+ return source ;
204+ }
205+
144206export default function ( options : UniversalOptions ) : Rule {
145207 return ( host : Tree , context : SchematicContext ) => {
146208 const clientProject = getClientProject ( host , options ) ;
@@ -171,6 +233,7 @@ export default function (options: UniversalOptions): Rule {
171233 updateConfigFile ( options ) ,
172234 mergeWith ( rootSource ) ,
173235 addDependenciesAndScripts ( options ) ,
236+ addModuleMapLoader ( options ) ,
174237 ] ) ;
175238 } ;
176239}
0 commit comments