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 , normalize } from '@angular-devkit/core' ;
8+ import { strings , normalize , workspaces , join } from '@angular-devkit/core' ;
99import {
1010 apply ,
1111 chain ,
@@ -21,53 +21,35 @@ import {
2121 url ,
2222} from '@angular-devkit/schematics' ;
2323import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks' ;
24- import { getWorkspace } from '@schematics/angular/utility/config' ;
2524import { Schema as UniversalOptions } from './schema' ;
2625import {
2726 addPackageJsonDependency ,
2827 NodeDependencyType ,
2928} from '@schematics/angular/utility/dependencies' ;
30- import { getProject } from '@schematics/angular/utility/project' ;
31- import { getProjectTargets } from '@schematics/angular/utility/project-targets' ;
3229import { InsertChange } from '@schematics/angular/utility/change' ;
33- import {
34- addSymbolToNgModuleMetadata ,
35- findNodes ,
36- insertAfterLastOccurrence ,
37- insertImport
38- } from '@schematics/angular/utility/ast-utils' ;
30+ import { insertAfterLastOccurrence , findNodes } from '@schematics/angular/utility/ast-utils' ;
3931import * as ts from 'typescript' ;
40- import { findAppServerModulePath , generateExport , getTsSourceFile , getTsSourceText } from './utils' ;
41- import { updateWorkspace } from '@schematics/angular/utility/workspace' ;
42-
43- // TODO(CaerusKaru): make these configurable
44- const BROWSER_DIST = 'dist/browser' ;
45- const SERVER_DIST = 'dist/server' ;
46-
47- function getClientProject (
48- host : Tree , options : UniversalOptions ,
49- ) : experimental . workspace . WorkspaceProject {
50- const workspace = getWorkspace ( host ) ;
51- const clientProject = workspace . projects [ options . clientProject ] ;
52- if ( ! clientProject ) {
53- throw new SchematicsException ( `Client app ${ options . clientProject } not found.` ) ;
32+ import { generateExport , getTsSourceFile , getTsSourceText } from './utils' ;
33+ import { getWorkspace , updateWorkspace } from '@schematics/angular/utility/workspace' ;
34+
35+ async function getClientProject ( host , projectName : string ) : Promise < workspaces . ProjectDefinition > {
36+ const workspace = await getWorkspace ( host ) ;
37+ const clientProject = workspace . projects . get ( projectName ) ;
38+
39+ if ( ! clientProject || clientProject . extensions . projectType !== 'application' ) {
40+ throw new SchematicsException ( `Universal requires a project type of "application".` ) ;
5441 }
5542
5643 return clientProject ;
5744}
5845
59- function addDependenciesAndScripts ( options : UniversalOptions ) : Rule {
46+ function addDependenciesAndScripts ( options : UniversalOptions , serverDist : string ) : Rule {
6047 return ( host : Tree ) => {
6148 addPackageJsonDependency ( host , {
6249 type : NodeDependencyType . Default ,
6350 name : '@nguniversal/express-engine' ,
6451 version : '0.0.0-PLACEHOLDER' ,
6552 } ) ;
66- addPackageJsonDependency ( host , {
67- type : NodeDependencyType . Default ,
68- name : '@nguniversal/module-map-ngfactory-loader' ,
69- version : '0.0.0-PLACEHOLDER' ,
70- } ) ;
7153 addPackageJsonDependency ( host , {
7254 type : NodeDependencyType . Default ,
7355 name : 'express' ,
@@ -100,23 +82,24 @@ function addDependenciesAndScripts(options: UniversalOptions): Rule {
10082 }
10183
10284 const pkg = JSON . parse ( buffer . toString ( ) ) ;
103-
104- pkg . scripts [ 'compile:server' ] = options . webpack ?
105- 'webpack --config webpack.server.config.js --progress --colors' :
106- `tsc -p ${ serverFileName } .tsconfig.json` ;
107- pkg . scripts [ 'serve:ssr' ] = `node dist/${ serverFileName } ` ;
108- pkg . scripts [ 'build:ssr' ] = 'npm run build:client-and-server-bundles && npm run compile:server' ;
109- pkg . scripts [ 'build:client-and-server-bundles' ] =
110- // tslint:disable:max-line-length
111- `ng build --prod && ng run ${ options . clientProject } :server:production --bundleDependencies all` ;
85+ pkg . scripts = {
86+ ...pkg . scripts ,
87+ 'compile:server' : options . webpack
88+ ? 'webpack --config webpack.server.config.js --progress --colors'
89+ : `tsc -p ${ serverFileName } .tsconfig.json` ,
90+ 'serve:ssr' : `node ${ serverDist . substr ( 1 ) } /${ serverFileName } ` ,
91+ 'build:ssr' : 'npm run build:client-and-server-bundles && npm run compile:server' ,
92+ // tslint:disable-next-line: max-line-length
93+ 'build:client-and-server-bundles' : `ng build --prod && ng run ${ options . clientProject } :server:production` ,
94+ } ;
11295
11396 host . overwrite ( pkgPath , JSON . stringify ( pkg , null , 2 ) ) ;
11497
11598 return host ;
11699 } ;
117100}
118101
119- function updateConfigFile ( options : UniversalOptions ) {
102+ function updateConfigFile ( options : UniversalOptions , browserDist : string , serverDist : string ) {
120103 return updateWorkspace ( ( workspace => {
121104 const clientProject = workspace . projects . get ( options . clientProject ) ;
122105 if ( clientProject ) {
@@ -132,93 +115,54 @@ function updateConfigFile(options: UniversalOptions) {
132115
133116 serverTarget . options = {
134117 ...serverTarget . options ,
135- outputPath : SERVER_DIST ,
118+ outputPath : serverDist ,
136119 } ;
137120
138121 buildTarget . options = {
139122 ...buildTarget . options ,
140- outputPath : BROWSER_DIST ,
123+ outputPath : browserDist ,
141124 } ;
142125 }
143126 } ) ) ;
144127}
145128
146- function addModuleMapLoader ( options : UniversalOptions ) : Rule {
147- return ( host : Tree ) => {
148- const clientProject = getProject ( host , options . clientProject ) ;
149- const clientTargets = getProjectTargets ( clientProject ) ;
150- if ( ! clientTargets . server ) {
151- // If they skipped Universal schematics and don't have a server target,
152- // just get out
153- return ;
154- }
155- const mainPath = normalize ( '/' + clientTargets . server . options . main ) ;
156- const appServerModuleRelativePath = findAppServerModulePath ( host , mainPath ) ;
157- const modulePath = normalize (
158- `/${ clientProject . root } /src/${ appServerModuleRelativePath } .ts` ) ;
159-
160- // Add the module map loader import
161- let moduleSource = getTsSourceFile ( host , modulePath ) ;
162- const importModule = 'ModuleMapLoaderModule' ;
163- const importPath = '@nguniversal/module-map-ngfactory-loader' ;
164- const moduleMapImportChange = insertImport ( moduleSource , modulePath , importModule ,
165- importPath ) as InsertChange ;
166- if ( moduleMapImportChange ) {
167- const recorder = host . beginUpdate ( modulePath ) ;
168- recorder . insertLeft ( moduleMapImportChange . pos , moduleMapImportChange . toAdd ) ;
169- host . commitUpdate ( recorder ) ;
170- }
171-
172- // Add the module map loader module to the imports
173- const importText = 'ModuleMapLoaderModule' ;
174- moduleSource = getTsSourceFile ( host , modulePath ) ;
175- const metadataChanges = addSymbolToNgModuleMetadata (
176- moduleSource , modulePath , 'imports' , importText ) ;
177- if ( metadataChanges ) {
178- const recorder = host . beginUpdate ( modulePath ) ;
179- metadataChanges . forEach ( ( change : InsertChange ) => {
180- recorder . insertRight ( change . pos , change . toAdd ) ;
181- } ) ;
182- host . commitUpdate ( recorder ) ;
183- }
184- } ;
185- }
186-
187129function addExports ( options : UniversalOptions ) : Rule {
188- return ( host : Tree ) => {
189- const clientProject = getProject ( host , options . clientProject ) ;
190- const clientTargets = getProjectTargets ( clientProject ) ;
191-
192- if ( ! clientTargets . server ) {
130+ return async ( host : Tree ) => {
131+ const clientProject = await getClientProject ( host , options . clientProject ) ;
132+ const serverTarget = clientProject . targets . get ( 'server' ) ;
133+ if ( ! serverTarget ) {
193134 // If they skipped Universal schematics and don't have a server target,
194135 // just get out
195136 return ;
196137 }
197138
198- const mainPath = normalize ( '/' + clientTargets . server . options . main ) ;
139+ const mainPath = normalize ( '/' + serverTarget . options . main ) ;
199140 const mainSourceFile = getTsSourceFile ( host , mainPath ) ;
200141 let mainText = getTsSourceText ( host , mainPath ) ;
201142 const mainRecorder = host . beginUpdate ( mainPath ) ;
202143 const expressEngineExport = generateExport ( mainSourceFile , [ 'ngExpressEngine' ] ,
203144 '@nguniversal/express-engine' ) ;
204- const moduleMapExport = generateExport ( mainSourceFile , [ 'provideModuleMap' ] ,
205- '@nguniversal/module-map-ngfactory-loader' ) ;
145+ const addedExports = `\n${ expressEngineExport } \n` ;
206146 const exports = findNodes ( mainSourceFile , ts . SyntaxKind . ExportDeclaration ) ;
207- const addedExports = `\n${ expressEngineExport } \n${ moduleMapExport } \n` ;
208- const exportChange = insertAfterLastOccurrence ( exports , addedExports , mainText ,
209- 0 ) as InsertChange ;
147+ const exportChange =
148+ insertAfterLastOccurrence ( exports , addedExports , mainText , 0 ) as InsertChange ;
210149
211150 mainRecorder . insertLeft ( exportChange . pos , exportChange . toAdd ) ;
212151 host . commitUpdate ( mainRecorder ) ;
213152 } ;
214153}
215154
216155export default function ( options : UniversalOptions ) : Rule {
217- return ( host : Tree , context : SchematicContext ) => {
218- const clientProject = getClientProject ( host , options ) ;
219- if ( clientProject . projectType !== 'application' ) {
220- throw new SchematicsException ( `Universal requires a project type of "application".` ) ;
221- }
156+ return async ( host : Tree , context : SchematicContext ) => {
157+ // Generate new output paths
158+ const clientProject = await getClientProject ( host , options . clientProject ) ;
159+ const { options : buildOptions } = clientProject . targets . get ( 'build' ) ;
160+ const clientOutputPath = normalize (
161+ typeof buildOptions . outputPath === 'string' ? buildOptions . outputPath : 'dist'
162+ ) ;
163+
164+ const browserDist = join ( clientOutputPath , 'browser' ) ;
165+ const serverDist = join ( clientOutputPath , 'server' ) ;
222166
223167 if ( ! options . skipInstall ) {
224168 context . addTask ( new NodePackageInstallTask ( ) ) ;
@@ -230,20 +174,20 @@ export default function (options: UniversalOptions): Rule {
230174 filter ( path => ! path . includes ( 'tsconfig' ) ) : filter ( path => ! path . startsWith ( 'webpack' ) ) ,
231175 template ( {
232176 ...strings ,
233- ...options as object ,
177+ ...options ,
234178 stripTsExtension : ( s : string ) => s . replace ( / \. t s $ / , '' ) ,
235- getBrowserDistDirectory : ( ) => BROWSER_DIST ,
236- getServerDistDirectory : ( ) => SERVER_DIST ,
179+ // remove the leading slashes
180+ getBrowserDistDirectory : ( ) => browserDist . substr ( 1 ) ,
181+ getServerDistDirectory : ( ) => serverDist . substr ( 1 ) ,
237182 } )
238183 ] ) ;
239184
240185 return chain ( [
241186 options . skipUniversal ?
242187 noop ( ) : externalSchematic ( '@schematics/angular' , 'universal' , options ) ,
243- updateConfigFile ( options ) ,
188+ updateConfigFile ( options , browserDist , serverDist ) ,
244189 mergeWith ( rootSource ) ,
245- addDependenciesAndScripts ( options ) ,
246- addModuleMapLoader ( options ) ,
190+ addDependenciesAndScripts ( options , serverDist ) ,
247191 addExports ( options ) ,
248192 ] ) ;
249193 } ;
0 commit comments