11import * as TheLogManager from 'aurelia-logging' ;
2- import { ViewEngine } from 'aurelia-templating' ;
2+ import { ViewEngine , HtmlBehaviorResource } from 'aurelia-templating' ;
33import { join } from 'aurelia-path' ;
44import { Container } from 'aurelia-dependency-injection' ;
55
66const logger = TheLogManager . getLogger ( 'aurelia' ) ;
77const extPattern = / \. [ ^ / . ] + $ / ;
88
9- function runTasks ( config , tasks ) {
9+ function runTasks ( config : FrameworkConfiguration , tasks ) {
1010 let current ;
1111 let next = ( ) => {
1212 current = tasks . shift ( ) ;
@@ -20,35 +20,62 @@ function runTasks(config, tasks) {
2020 return next ( ) ;
2121}
2222
23- function loadPlugin ( config , loader , info ) {
23+ interface FrameworkPluginInfo {
24+ moduleId ? : string ;
25+ resourcesRelativeTo ?: string [ ] ;
26+ configure ?: ( config : FrameworkConfiguration , pluginConfig ?: any ) => any ;
27+ config ?: any ;
28+ }
29+
30+ function loadPlugin ( fwConfig : FrameworkConfiguration , loader : Loader , info : FrameworkPluginInfo ) {
2431 logger . debug ( `Loading plugin ${ info . moduleId } .` ) ;
25- config . resourcesRelativeTo = info . resourcesRelativeTo ;
32+ if ( typeof info . moduleId === 'string' ) {
33+ fwConfig . resourcesRelativeTo = info . resourcesRelativeTo ;
2634
27- let id = info . moduleId ; // General plugins installed/configured by the end user.
35+ let id = info . moduleId ; // General plugins installed/configured by the end user.
2836
29- if ( info . resourcesRelativeTo . length > 1 ) { // In case of bootstrapper installed plugins like `aurelia-templating-resources` or `aurelia-history-browser`.
30- return loader . normalize ( info . moduleId , info . resourcesRelativeTo [ 1 ] )
31- . then ( normalizedId => _loadPlugin ( normalizedId ) ) ;
32- }
37+ if ( info . resourcesRelativeTo . length > 1 ) { // In case of bootstrapper installed plugins like `aurelia-templating-resources` or `aurelia-history-browser`.
38+ return loader . normalize ( info . moduleId , info . resourcesRelativeTo [ 1 ] )
39+ . then ( normalizedId => _loadPlugin ( normalizedId ) ) ;
40+ }
3341
34- return _loadPlugin ( id ) ;
42+ return _loadPlugin ( id ) ;
43+ } else if ( typeof info . configure === 'function' ) {
44+ if ( fwConfig . configuredPlugins . indexOf ( info . configure ) !== - 1 ) {
45+ return Promise . resolve ( ) ;
46+ }
47+ fwConfig . configuredPlugins . push ( info . configure ) ;
48+ // use info.config || {} to keep behavior consistent with loading from string
49+ return Promise . resolve ( info . configure . call ( null , fwConfig , info . config || { } ) ) ;
50+ }
51+ throw new Error ( invalidConfigMsg ( info . moduleId || info . configure , 'plugin' ) ) ;
3552
3653 function _loadPlugin ( moduleId ) {
3754 return loader . loadModule ( moduleId ) . then ( m => { // eslint-disable-line consistent-return
3855 if ( 'configure' in m ) {
39- return Promise . resolve ( m . configure ( config , info . config || { } ) ) . then ( ( ) => {
40- config . resourcesRelativeTo = null ;
56+ if ( fwConfig . configuredPlugins . indexOf ( m . configure ) !== - 1 ) {
57+ return Promise . resolve ( ) ;
58+ }
59+ return Promise . resolve ( m . configure ( fwConfig , info . config || { } ) ) . then ( ( ) => {
60+ fwConfig . configuredPlugins . push ( m . configure ) ;
61+ fwConfig . resourcesRelativeTo = null ;
4162 logger . debug ( `Configured plugin ${ info . moduleId } .` ) ;
4263 } ) ;
4364 }
4465
45- config . resourcesRelativeTo = null ;
66+ fwConfig . resourcesRelativeTo = null ;
4667 logger . debug ( `Loaded plugin ${ info . moduleId } .` ) ;
4768 } ) ;
4869 }
4970}
5071
5172function loadResources ( aurelia , resourcesToLoad , appResources ) {
73+ // if devs want to go all in static, and remove loader
74+ // the code after this fucntion shouldn't run
75+ // add a check to make sure it only runs when there is something to do so
76+ if ( Object . keys ( resourcesToLoad ) . length === 0 ) {
77+ return Promise . resolve ( ) ;
78+ }
5279 let viewEngine = aurelia . container . get ( ViewEngine ) ;
5380
5481 return Promise . all ( Object . keys ( resourcesToLoad ) . map ( n => _normalize ( resourcesToLoad [ n ] ) ) )
@@ -98,19 +125,29 @@ function loadResources(aurelia, resourcesToLoad, appResources) {
98125 }
99126}
100127
101- function getExt ( name ) { // eslint-disable-line consistent-return
128+ function getExt ( name : string ) { // eslint-disable-line consistent-return
102129 let match = name . match ( extPattern ) ;
103130 if ( match && match . length > 0 ) {
104131 return ( match [ 0 ] . split ( '.' ) ) [ 1 ] ;
105132 }
106133}
107134
108- function assertProcessed ( plugins ) {
135+ function loadBehaviors ( config : FrameworkConfiguration ) {
136+ return Promise . all ( config . behaviorsToLoad . map ( m => m . load ( config . container , m . target ) ) ) . then ( ( ) => {
137+ config . behaviorsToLoad = null ;
138+ } ) ;
139+ }
140+
141+ function assertProcessed ( plugins : FrameworkConfiguration ) {
109142 if ( plugins . processed ) {
110143 throw new Error ( 'This config instance has already been applied. To load more plugins or global resources, create a new FrameworkConfiguration instance.' ) ;
111144 }
112145}
113146
147+ function invalidConfigMsg ( cfg : any , type : string ) {
148+ return `Invalid ${type } [ $ { cfg} ] , $ { type} must be specified as functions or relative module IDs . `;
149+ }
150+
114151/**
115152 * Manages configuring the aurelia framework instance.
116153 */
@@ -132,10 +169,24 @@ export class FrameworkConfiguration {
132169 constructor(aurelia: Aurelia) {
133170 this.aurelia = aurelia;
134171 this.container = aurelia.container;
172+ /**
173+ * Plugin / feature loading instruction
174+ * @type {FrameworkPluginInfo[]}
175+ */
135176 this.info = [];
136177 this.processed = false;
137178 this.preTasks = [];
138179 this.postTasks = [];
180+ /**
181+ * Custom element's metadata queue for loading view factory
182+ * @type {HtmlBehaviorResource[]}
183+ */
184+ this.behaviorsToLoad = [];
185+ /**
186+ * Plugin configure functions temporary cache for avoid duplicate calls
187+ * @type {Function[]}
188+ */
189+ this.configuredPlugins = [];
139190 this.resourcesToLoad = {};
140191 this.preTask(() => aurelia.loader.normalize('aurelia-bootstrapper').then(name => this.bootstrapperName = name));
141192 this.postTask(() => loadResources(aurelia, this.resourcesToLoad, aurelia.resources));
@@ -202,19 +253,31 @@ export class FrameworkConfiguration {
202253 * @param config The configuration for the specified plugin.
203254 * @return Returns the current FrameworkConfiguration instance.
204255 */
205- feature ( plugin : string , config ? : any = { } ) : FrameworkConfiguration {
206- let hasIndex = / \/ i n d e x $ / i. test ( plugin ) ;
207- let moduleId = hasIndex || getExt ( plugin ) ? plugin : plugin + '/index' ;
208- let root = hasIndex ? plugin . substr ( 0 , plugin . length - 6 ) : plugin ;
209- return this . plugin ( { moduleId, resourcesRelativeTo : [ root , '' ] , config } ) ;
256+ feature(plugin: string | ((config: FrameworkConfiguration, pluginConfig?: any) => any), config?: any = {}): FrameworkConfiguration {
257+ switch (typeof plugin) {
258+ case 'string':
259+ let hasIndex = /\/index$/i.test(plugin);
260+ let moduleId = hasIndex || getExt(plugin) ? plugin : plugin + '/index';
261+ let root = hasIndex ? plugin.substr(0, plugin.length - 6) : plugin;
262+ this.info.push({ moduleId, resourcesRelativeTo: [root, ''], config });
263+ break;
264+ // return this.plugin({ moduleId, resourcesRelativeTo: [root, ''], config });
265+ case 'function':
266+ this.info.push({ configure: plugin, config: config || {} });
267+ break;
268+ default:
269+ throw new Error(invalidConfigMsg(plugin, 'feature'));
270+ }
271+ return this;
272+ // return this.plugin(plugin, config);
210273 }
211274
212275 /**
213276 * Adds globally available view resources to be imported into the Aurelia framework.
214277 * @param resources The relative module id to the resource. (Relative to the plugin's installer.)
215278 * @return Returns the current FrameworkConfiguration instance.
216279 */
217- globalResources ( resources : string | string [ ] ) : FrameworkConfiguration {
280+ globalResources(resources: string | Function | Array< string | Function> ): FrameworkConfiguration {
218281 assertProcessed(this);
219282
220283 let toAdd = Array.isArray(resources) ? resources : arguments;
@@ -223,19 +286,29 @@ export class FrameworkConfiguration {
223286
224287 for (let i = 0, ii = toAdd.length; i < ii; ++i) {
225288 resource = toAdd[i];
226- if ( typeof resource !== 'string' ) {
227- throw new Error ( `Invalid resource path [${ resource } ]. Resources must be specified as relative module IDs.` ) ;
228- }
229-
230- let parent = resourcesRelativeTo [ 0 ] ;
231- let grandParent = resourcesRelativeTo [ 1 ] ;
232- let name = resource ;
289+ switch (typeof resource) {
290+ case 'string':
291+ let parent = resourcesRelativeTo[0];
292+ let grandParent = resourcesRelativeTo[1];
293+ let name = resource;
294+
295+ if ((resource.startsWith('./') || resource.startsWith('../')) && parent !== '') {
296+ name = join(parent, resource);
297+ }
233298
234- if ( ( resource . startsWith ( './' ) || resource . startsWith ( '../' ) ) && parent !== '' ) {
235- name = join ( parent , resource ) ;
299+ this.resourcesToLoad[name] = { moduleId: name, relativeTo: grandParent };
300+ break;
301+ case 'function':
302+ let meta = this.aurelia.resources.autoRegister(this.container, resource);
303+ if (meta instanceof HtmlBehaviorResource && meta.elementName !== null) {
304+ if (this.behaviorsToLoad.push(meta) === 1) {
305+ this.postTask(() => loadBehaviors(this));
306+ }
307+ }
308+ break;
309+ default:
310+ throw new Error(invalidConfigMsg(resource, 'resource'));
236311 }
237-
238- this . resourcesToLoad [ name ] = { moduleId : name , relativeTo : grandParent } ;
239312 }
240313
241314 return this;
@@ -256,17 +329,27 @@ export class FrameworkConfiguration {
256329 /**
257330 * Configures an external, 3rd party plugin before Aurelia starts.
258331 * @param plugin The ID of the 3rd party plugin to configure.
259- * @param config The configuration for the specified plugin.
332+ * @param pluginConfig The configuration for the specified plugin.
260333 * @return Returns the current FrameworkConfiguration instance.
261334 */
262- plugin ( plugin : string , config ? : any ) : FrameworkConfiguration {
335+ plugin(
336+ plugin: string | ((frameworkConfig: FrameworkConfiguration) => any) | FrameworkPluginInfo,
337+ pluginConfig?: any
338+ ): FrameworkConfiguration {
263339 assertProcessed(this);
264340
265- if ( typeof ( plugin ) === 'string' ) {
266- return this . plugin ( { moduleId : plugin , resourcesRelativeTo : [ plugin , '' ] , config : config || { } } ) ;
341+ let info: FrameworkPluginInfo;
342+ switch (typeof plugin) {
343+ case 'string':
344+ info = { moduleId: plugin, resourcesRelativeTo: [plugin, ''], config: pluginConfig || {} };
345+ break;
346+ case 'function':
347+ info = { configure: plugin, config: pluginConfig || {} };
348+ break;
349+ default:
350+ throw new Error(invalidConfigMsg(plugin, 'plugin'));
267351 }
268-
269- this . info . push ( plugin ) ;
352+ this.info.push(info);
270353 return this;
271354 }
272355
@@ -393,6 +476,7 @@ export class FrameworkConfiguration {
393476 }
394477
395478 this . processed = true ;
479+ this . configuredPlugins = null ;
396480 return Promise . resolve ( ) ;
397481 } ;
398482
0 commit comments