5
5
* Use of this source code is governed by an MIT-style license that can be
6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
- import { dirname , experimental , join , normalize } from '@angular-devkit/core' ;
8
+ import { dirname , join , normalize } from '@angular-devkit/core' ;
9
9
import {
10
10
Rule ,
11
11
SchematicContext ,
12
12
SchematicsException ,
13
13
Tree ,
14
14
chain ,
15
+ noop ,
15
16
schematic ,
16
17
} from '@angular-devkit/schematics' ;
17
18
import { Schema as ComponentOptions } from '../component/schema' ;
@@ -26,11 +27,10 @@ import {
26
27
isImported ,
27
28
} from '../utility/ast-utils' ;
28
29
import { Change , InsertChange } from '../utility/change' ;
29
- import { getWorkspace , updateWorkspace } from '../utility/config' ;
30
30
import { getAppModulePath } from '../utility/ng-ast-utils' ;
31
- import { getProject } from '../utility/project' ;
32
- import { getProjectTargets , targetBuildNotFoundError } from '../utility/project-targets ' ;
33
- import { Builders , WorkspaceProject } from '../utility/workspace-models' ;
31
+ import { targetBuildNotFoundError } from '../utility/project-targets ' ;
32
+ import { getWorkspace , updateWorkspace } from '../utility/workspace ' ;
33
+ import { BrowserBuilderOptions , Builders , ServerBuilderOptions } from '../utility/workspace-models' ;
34
34
import { Schema as AppShellOptions } from './schema' ;
35
35
36
36
function getSourceFile ( host : Tree , path : string ) : ts . SourceFile {
@@ -46,18 +46,17 @@ function getSourceFile(host: Tree, path: string): ts.SourceFile {
46
46
47
47
function getServerModulePath (
48
48
host : Tree ,
49
- project : experimental . workspace . WorkspaceProject ,
50
- architect : experimental . workspace . WorkspaceTool ,
49
+ projectRoot : string ,
50
+ mainPath : string ,
51
51
) : string | null {
52
- const mainPath = architect . server . options . main ;
53
52
const mainSource = getSourceFile ( host , mainPath ) ;
54
53
const allNodes = getSourceNodes ( mainSource ) ;
55
54
const expNode = allNodes . filter ( node => node . kind === ts . SyntaxKind . ExportDeclaration ) [ 0 ] ;
56
55
if ( ! expNode ) {
57
56
return null ;
58
57
}
59
58
const relativePath = ( expNode as ts . ExportDeclaration ) . moduleSpecifier as ts . StringLiteral ;
60
- const modulePath = normalize ( `/${ project . root } /src/${ relativePath . text } .ts` ) ;
59
+ const modulePath = normalize ( `/${ projectRoot } /src/${ relativePath . text } .ts` ) ;
61
60
62
61
return modulePath ;
63
62
}
@@ -97,14 +96,8 @@ function getComponentTemplate(host: Tree, compPath: string, tmplInfo: TemplateIn
97
96
98
97
function getBootstrapComponentPath (
99
98
host : Tree ,
100
- project : WorkspaceProject ,
99
+ mainPath : string ,
101
100
) : string {
102
- const projectTargets = getProjectTargets ( project ) ;
103
- if ( ! projectTargets . build ) {
104
- throw targetBuildNotFoundError ( ) ;
105
- }
106
-
107
- const mainPath = projectTargets . build . options . main ;
108
101
const modulePath = getAppModulePath ( host , mainPath ) ;
109
102
const moduleSource = getSourceFile ( host , modulePath ) ;
110
103
@@ -131,15 +124,11 @@ function getBootstrapComponentPath(
131
124
}
132
125
// end helper functions.
133
126
134
- function validateProject ( options : AppShellOptions ) : Rule {
127
+ function validateProject ( mainPath : string ) : Rule {
135
128
return ( host : Tree , context : SchematicContext ) => {
136
129
const routerOutletCheckRegex = / < r o u t e r \- o u t l e t .* ?> ( [ \s \S ] * ?) < \/ r o u t e r \- o u t l e t > / ;
137
130
138
- const clientProject = getProject ( host , options . clientProject ) ;
139
- if ( clientProject . projectType !== 'application' ) {
140
- throw new SchematicsException ( `App shell requires a project type of "application".` ) ;
141
- }
142
- const componentPath = getBootstrapComponentPath ( host , clientProject ) ;
131
+ const componentPath = getBootstrapComponentPath ( host , mainPath ) ;
143
132
const tmpl = getComponentTemplateInfo ( host , componentPath ) ;
144
133
const template = getComponentTemplate ( host , componentPath , tmpl ) ;
145
134
if ( ! routerOutletCheckRegex . test ( template ) ) {
@@ -152,12 +141,7 @@ function validateProject(options: AppShellOptions): Rule {
152
141
}
153
142
154
143
function addUniversalTarget ( options : AppShellOptions ) : Rule {
155
- return ( host : Tree , context : SchematicContext ) => {
156
- const architect = getProjectTargets ( host , options . clientProject ) ;
157
- if ( architect . server ) {
158
- return host ;
159
- }
160
-
144
+ return ( ) => {
161
145
// Copy options.
162
146
const universalOptions = {
163
147
...options ,
@@ -177,39 +161,38 @@ function addUniversalTarget(options: AppShellOptions): Rule {
177
161
}
178
162
179
163
function addAppShellConfigToWorkspace ( options : AppShellOptions ) : Rule {
180
- return ( host : Tree ) => {
164
+ return ( ) => {
181
165
if ( ! options . route ) {
182
166
throw new SchematicsException ( `Route is not defined` ) ;
183
167
}
184
168
185
- const workspace = getWorkspace ( host ) ;
186
- const projectTargets = getProjectTargets ( workspace , options . clientProject ) ;
187
- projectTargets [ 'app-shell' ] = {
188
- builder : Builders . AppShell ,
189
- options : {
190
- browserTarget : `${ options . clientProject } :build` ,
191
- serverTarget : `${ options . clientProject } :server` ,
192
- route : options . route ,
193
- } ,
194
- configurations : {
195
- production : {
196
- browserTarget : `${ options . clientProject } :build:production` ,
197
- serverTarget : `${ options . clientProject } :server:production` ,
198
- } ,
199
- } ,
200
- } ;
169
+ return updateWorkspace ( workspace => {
170
+ const project = workspace . projects . get ( options . clientProject ) ;
171
+ if ( ! project ) {
172
+ return ;
173
+ }
201
174
202
- return updateWorkspace ( workspace ) ;
175
+ project . targets . add ( {
176
+ name : 'app-shell' ,
177
+ builder : Builders . AppShell ,
178
+ options : {
179
+ browserTarget : `${ options . clientProject } :build` ,
180
+ serverTarget : `${ options . clientProject } :server` ,
181
+ route : options . route ,
182
+ } ,
183
+ configurations : {
184
+ production : {
185
+ browserTarget : `${ options . clientProject } :build:production` ,
186
+ serverTarget : `${ options . clientProject } :server:production` ,
187
+ } ,
188
+ } ,
189
+ } ) ;
190
+ } ) ;
203
191
} ;
204
192
}
205
193
206
- function addRouterModule ( options : AppShellOptions ) : Rule {
194
+ function addRouterModule ( mainPath : string ) : Rule {
207
195
return ( host : Tree ) => {
208
- const projectTargets = getProjectTargets ( host , options . clientProject ) ;
209
- if ( ! projectTargets . build ) {
210
- throw targetBuildNotFoundError ( ) ;
211
- }
212
- const mainPath = projectTargets . build . options . main ;
213
196
const modulePath = getAppModulePath ( host , mainPath ) ;
214
197
const moduleSource = getSourceFile ( host , modulePath ) ;
215
198
const changes = addImportToModule ( moduleSource , modulePath , 'RouterModule' , '@angular/router' ) ;
@@ -245,11 +228,22 @@ function getMetadataProperty(metadata: ts.Node, propertyName: string): ts.Proper
245
228
}
246
229
247
230
function addServerRoutes ( options : AppShellOptions ) : Rule {
248
- return ( host : Tree ) => {
249
- const clientProject = getProject ( host , options . clientProject ) ;
250
- const architect = getProjectTargets ( clientProject ) ;
251
- // const mainPath = universalArchitect.build.options.main;
252
- const modulePath = getServerModulePath ( host , clientProject , architect ) ;
231
+ return async ( host : Tree ) => {
232
+ // The workspace gets updated so this needs to be reloaded
233
+ const workspace = await getWorkspace ( host ) ;
234
+ const clientProject = workspace . projects . get ( options . clientProject ) ;
235
+ if ( ! clientProject ) {
236
+ throw new Error ( 'Universal schematic removed client project.' ) ;
237
+ }
238
+ const clientServerTarget = clientProject . targets . get ( 'server' ) ;
239
+ if ( ! clientServerTarget ) {
240
+ throw new Error ( 'Universal schematic did not add server target to client project.' ) ;
241
+ }
242
+ const clientServerOptions = clientServerTarget . options as unknown as ServerBuilderOptions ;
243
+ if ( ! clientServerOptions ) {
244
+ throw new SchematicsException ( 'Server target does not contain options.' ) ;
245
+ }
246
+ const modulePath = getServerModulePath ( host , clientProject . root , clientServerOptions . main ) ;
253
247
if ( modulePath === null ) {
254
248
throw new SchematicsException ( 'Universal/server module not found.' ) ;
255
249
}
@@ -296,9 +290,6 @@ function addServerRoutes(options: AppShellOptions): Rule {
296
290
}
297
291
host . commitUpdate ( recorder ) ;
298
292
}
299
-
300
-
301
- return host ;
302
293
} ;
303
294
}
304
295
@@ -313,12 +304,26 @@ function addShellComponent(options: AppShellOptions): Rule {
313
304
}
314
305
315
306
export default function ( options : AppShellOptions ) : Rule {
316
- return chain ( [
317
- validateProject ( options ) ,
318
- addUniversalTarget ( options ) ,
319
- addAppShellConfigToWorkspace ( options ) ,
320
- addRouterModule ( options ) ,
321
- addServerRoutes ( options ) ,
322
- addShellComponent ( options ) ,
323
- ] ) ;
307
+ return async tree => {
308
+ const workspace = await getWorkspace ( tree ) ;
309
+ const clientProject = workspace . projects . get ( options . clientProject ) ;
310
+ if ( ! clientProject || clientProject . extensions . projectType !== 'application' ) {
311
+ throw new SchematicsException ( `A client project type of "application" is required.` ) ;
312
+ }
313
+ const clientBuildTarget = clientProject . targets . get ( 'build' ) ;
314
+ if ( ! clientBuildTarget ) {
315
+ throw targetBuildNotFoundError ( ) ;
316
+ }
317
+ const clientBuildOptions =
318
+ ( clientBuildTarget . options || { } ) as unknown as BrowserBuilderOptions ;
319
+
320
+ return chain ( [
321
+ validateProject ( clientBuildOptions . main ) ,
322
+ clientProject . targets . has ( 'server' ) ? noop ( ) : addUniversalTarget ( options ) ,
323
+ addAppShellConfigToWorkspace ( options ) ,
324
+ addRouterModule ( clientBuildOptions . main ) ,
325
+ addServerRoutes ( options ) ,
326
+ addShellComponent ( options ) ,
327
+ ] ) ;
328
+ } ;
324
329
}
0 commit comments