@@ -23,16 +23,18 @@ import {
2323} from '@angular-devkit/core' ;
2424import { resolve as nodeResolve } from '@angular-devkit/core/node' ;
2525import { Observable } from 'rxjs/Observable' ;
26+ import { forkJoin } from 'rxjs/observable/forkJoin' ;
2627import { of } from 'rxjs/observable/of' ;
2728import { _throw } from 'rxjs/observable/throw' ;
28- import { concatMap } from 'rxjs/operators' ;
29+ import { concatMap , map } from 'rxjs/operators' ;
2930import {
3031 BuildEvent ,
3132 Builder ,
3233 BuilderConstructor ,
3334 BuilderContext ,
3435 BuilderDescription ,
35- BuilderMap ,
36+ BuilderPaths ,
37+ BuilderPathsMap ,
3638} from './builder' ;
3739import { Workspace } from './workspace' ;
3840
@@ -74,6 +76,12 @@ export class WorkspaceNotYetLoadedException extends BaseException {
7476 constructor ( ) { super ( `Workspace needs to be loaded before Architect is used.` ) ; }
7577}
7678
79+ export class BuilderNotFoundException extends BaseException {
80+ constructor ( builder : string ) {
81+ super ( `Builder ${ builder } could not be found.` ) ;
82+ }
83+ }
84+
7785export interface Target < OptionsT = { } > {
7886 root : Path ;
7987 projectType : string ;
@@ -87,26 +95,29 @@ export interface TargetOptions<OptionsT = {}> {
8795 configuration ?: string ;
8896 overrides ?: Partial < OptionsT > ;
8997}
90-
9198export class Architect {
92- private readonly _workspaceSchema = join ( normalize ( __dirname ) , 'workspace-schema.json' ) ;
93- private readonly _buildersSchema = join ( normalize ( __dirname ) , 'builders-schema.json' ) ;
99+ private readonly _workspaceSchemaPath = join ( normalize ( __dirname ) , 'workspace-schema.json' ) ;
100+ private readonly _buildersSchemaPath = join ( normalize ( __dirname ) , 'builders-schema.json' ) ;
101+ private _workspaceSchema : JsonObject ;
102+ private _buildersSchema : JsonObject ;
103+ private _architectSchemasLoaded = false ;
104+ private _builderPathsMap = new Map < string , BuilderPaths > ( ) ;
105+ private _builderDescriptionMap = new Map < string , BuilderDescription > ( ) ;
106+ private _builderConstructorMap = new Map < string , BuilderConstructor < { } > > ( ) ;
94107 private _workspace : Workspace ;
95108
96109 constructor ( private _root : Path , private _host : virtualFs . Host < { } > ) { }
97110
98111 loadWorkspaceFromHost ( workspacePath : Path ) {
99- return this . _host . read ( join ( this . _root , workspacePath ) ) . pipe (
100- concatMap ( ( buffer ) => {
101- const json = JSON . parse ( virtualFs . fileBufferToString ( buffer ) ) ;
102-
103- return this . loadWorkspaceFromJson ( json ) ;
104- } ) ,
112+ return this . _loadArchitectSchemas ( ) . pipe (
113+ concatMap ( ( ) => this . _loadJsonFile ( join ( this . _root , workspacePath ) ) ) ,
114+ concatMap ( json => this . loadWorkspaceFromJson ( json as { } as Workspace ) ) ,
105115 ) ;
106116 }
107117
108118 loadWorkspaceFromJson ( json : Workspace ) {
109- return this . _validateAgainstSchema ( json , this . _workspaceSchema ) . pipe (
119+ return this . _loadArchitectSchemas ( ) . pipe (
120+ concatMap ( ( ) => this . _validateAgainstSchema ( json , this . _workspaceSchema ) ) ,
110121 concatMap ( ( validatedWorkspace : Workspace ) => {
111122 this . _workspace = validatedWorkspace ;
112123
@@ -115,6 +126,24 @@ export class Architect {
115126 ) ;
116127 }
117128
129+ private _loadArchitectSchemas ( ) {
130+ if ( this . _architectSchemasLoaded ) {
131+ return of ( null ) ;
132+ } else {
133+ return forkJoin (
134+ this . _loadJsonFile ( this . _workspaceSchemaPath ) ,
135+ this . _loadJsonFile ( this . _buildersSchemaPath ) ,
136+ ) . pipe (
137+ concatMap ( ( [ workspaceSchema , buildersSchema ] ) => {
138+ this . _workspaceSchema = workspaceSchema ;
139+ this . _buildersSchema = buildersSchema ;
140+
141+ return of ( null ) ;
142+ } ) ,
143+ ) ;
144+ }
145+ }
146+
118147 getTarget < OptionsT > ( options : TargetOptions = { } ) : Target < OptionsT > {
119148 let { project, target : targetName } = options ;
120149 const { configuration, overrides } = options ;
@@ -187,23 +216,27 @@ export class Architect {
187216
188217 return this . validateBuilderOptions ( target , builderDescription ) ;
189218 } ) ,
190- concatMap ( ( ) => of ( this . getBuilder ( builderDescription , context ) ) ) ,
219+ map ( ( ) => this . getBuilder ( builderDescription , context ) ) ,
191220 concatMap ( builder => builder . run ( target ) ) ,
192221 ) ;
193222 }
194223
195224 getBuilderDescription < OptionsT > ( target : Target < OptionsT > ) : Observable < BuilderDescription > {
225+ // Check cache for this builder description.
226+ if ( this . _builderDescriptionMap . has ( target . builder ) ) {
227+ return of ( this . _builderDescriptionMap . get ( target . builder ) as BuilderDescription ) ;
228+ }
229+
196230 return new Observable ( ( obs ) => {
197231 // TODO: this probably needs to be more like NodeModulesEngineHost.
198232 const basedir = getSystemPath ( this . _root ) ;
199233 const [ pkg , builderName ] = target . builder . split ( ':' ) ;
200234 const pkgJsonPath = nodeResolve ( pkg , { basedir, resolvePackageJson : true } ) ;
201235 let buildersJsonPath : Path ;
236+ let builderPaths : BuilderPaths ;
202237
203238 // Read the `builders` entry of package.json.
204- return this . _host . read ( normalize ( pkgJsonPath ) ) . pipe (
205- concatMap ( buffer =>
206- of ( parseJson ( virtualFs . fileBufferToString ( buffer ) , JsonParseMode . Loose ) ) ) ,
239+ return this . _loadJsonFile ( normalize ( pkgJsonPath ) ) . pipe (
207240 concatMap ( ( pkgJson : JsonObject ) => {
208241 const pkgJsonBuildersentry = pkgJson [ 'builders' ] as string ;
209242 if ( ! pkgJsonBuildersentry ) {
@@ -212,28 +245,40 @@ export class Architect {
212245
213246 buildersJsonPath = join ( dirname ( normalize ( pkgJsonPath ) ) , pkgJsonBuildersentry ) ;
214247
215- return this . _host . read ( buildersJsonPath ) ;
248+ return this . _loadJsonFile ( buildersJsonPath ) ;
216249 } ) ,
217- concatMap ( ( buffer ) => of ( JSON . parse ( virtualFs . fileBufferToString ( buffer ) ) ) ) ,
218250 // Validate builders json.
219- concatMap ( ( builderMap ) =>
220- this . _validateAgainstSchema < BuilderMap > ( builderMap , this . _buildersSchema ) ) ,
221-
251+ concatMap ( ( builderPathsMap ) =>
252+ this . _validateAgainstSchema < BuilderPathsMap > ( builderPathsMap , this . _buildersSchema ) ) ,
253+ concatMap ( ( builderPathsMap ) => {
254+ builderPaths = builderPathsMap . builders [ builderName ] ;
222255
223- concatMap ( ( builderMap ) => {
224- const builderDescription = builderMap . builders [ builderName ] ;
225-
226- if ( ! builderDescription ) {
256+ if ( ! builderPaths ) {
227257 throw new BuilderCannotBeResolvedException ( target . builder ) ;
228258 }
229259
230- // Resolve paths in the builder description .
260+ // Resolve paths in the builder paths .
231261 const builderJsonDir = dirname ( buildersJsonPath ) ;
232- builderDescription . schema = join ( builderJsonDir , builderDescription . schema ) ;
233- builderDescription . class = join ( builderJsonDir , builderDescription . class ) ;
262+ builderPaths . schema = join ( builderJsonDir , builderPaths . schema ) ;
263+ builderPaths . class = join ( builderJsonDir , builderPaths . class ) ;
264+
265+ // Save the builder paths so that we can lazily load the builder.
266+ this . _builderPathsMap . set ( target . builder , builderPaths ) ;
234267
235- // Validate options again builder schema.
236- return of ( builderDescription ) ;
268+ // Load the schema.
269+ return this . _loadJsonFile ( builderPaths . schema ) ;
270+ } ) ,
271+ map ( builderSchema => {
272+ const builderDescription = {
273+ name : target . builder ,
274+ schema : builderSchema ,
275+ description : builderPaths . description ,
276+ } ;
277+
278+ // Save to cache before returning.
279+ this . _builderDescriptionMap . set ( builderDescription . name , builderDescription ) ;
280+
281+ return builderDescription ;
237282 } ) ,
238283 ) . subscribe ( obs ) ;
239284 } ) ;
@@ -242,28 +287,44 @@ export class Architect {
242287 validateBuilderOptions < OptionsT > (
243288 target : Target < OptionsT > , builderDescription : BuilderDescription ,
244289 ) : Observable < OptionsT > {
245- return this . _validateAgainstSchema < OptionsT > ( target . options ,
246- normalize ( builderDescription . schema ) ) ;
290+ return this . _validateAgainstSchema < OptionsT > ( target . options , builderDescription . schema ) ;
247291 }
248292
249293 getBuilder < OptionsT > (
250294 builderDescription : BuilderDescription , context : BuilderContext ,
251295 ) : Builder < OptionsT > {
252- // TODO: support more than the default export, maybe via builder#import-name.
253- const builderModule = require ( getSystemPath ( builderDescription . class ) ) ;
254- const builderClass = builderModule [ 'default' ] as BuilderConstructor < OptionsT > ;
296+ const name = builderDescription . name ;
297+ let builderConstructor : BuilderConstructor < OptionsT > ;
298+
299+ // Check cache for this builder.
300+ if ( this . _builderConstructorMap . has ( name ) ) {
301+ builderConstructor = this . _builderConstructorMap . get ( name ) as BuilderConstructor < OptionsT > ;
302+ } else {
303+ if ( ! this . _builderPathsMap . has ( name ) ) {
304+ throw new BuilderNotFoundException ( name ) ;
305+ }
306+
307+ const builderPaths = this . _builderPathsMap . get ( name ) as BuilderPaths ;
255308
256- return new builderClass ( context ) ;
309+ // TODO: support more than the default export, maybe via builder#import-name.
310+ const builderModule = require ( getSystemPath ( builderPaths . class ) ) ;
311+ builderConstructor = builderModule [ 'default' ] as BuilderConstructor < OptionsT > ;
312+
313+ // Save builder to cache before returning.
314+ this . _builderConstructorMap . set ( builderDescription . name , builderConstructor ) ;
315+ }
316+
317+ const builder = new builderConstructor ( context ) ;
318+
319+ return builder ;
257320 }
258321
259322 // Warning: this method changes contentJson in place.
260323 // TODO: add transforms to resolve paths.
261- private _validateAgainstSchema < T = { } > ( contentJson : { } , schemaPath : Path ) : Observable < T > {
324+ private _validateAgainstSchema < T = { } > ( contentJson : { } , schemaJson : JsonObject ) : Observable < T > {
262325 const registry = new schema . CoreSchemaRegistry ( ) ;
263326
264- return this . _host . read ( schemaPath ) . pipe (
265- concatMap ( ( buffer ) => of ( JSON . parse ( virtualFs . fileBufferToString ( buffer ) ) ) ) ,
266- concatMap ( ( schemaContent ) => registry . compile ( schemaContent ) ) ,
327+ return registry . compile ( schemaJson ) . pipe (
267328 concatMap ( validator => validator ( contentJson ) ) ,
268329 concatMap ( validatorResult => {
269330 if ( validatorResult . success ) {
@@ -274,4 +335,11 @@ export class Architect {
274335 } ) ,
275336 ) ;
276337 }
338+
339+ private _loadJsonFile ( path : Path ) : Observable < JsonObject > {
340+ return this . _host . read ( normalize ( path ) ) . pipe (
341+ map ( buffer => virtualFs . fileBufferToString ( buffer ) ) ,
342+ map ( str => parseJson ( str , JsonParseMode . Loose ) as { } as JsonObject ) ,
343+ ) ;
344+ }
277345}
0 commit comments