Skip to content

Commit 8ff0218

Browse files
committed
Merge pull request #164 from ouadi/enh-manage-imports
fixes #162: Allow import of a context-spec into another.
2 parents 1e9e2e3 + 93e5777 commit 8ff0218

25 files changed

Lines changed: 504 additions & 51 deletions

docs/concepts.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,30 @@ The second references the `message` String (the first item in the wire spec):
130130

131131
When you feed a spec to wire.js, it will create a [context](#contexts) containing fully realized versions of the components in the spec. In the Hello Wire case, the context will contain the message String, an *instance* of the `HelloWired` object from the `app/HelloWire` AMD module, and an Array with one element--the `wire/dom` plugin AMD module.
132132

133+
### Assembling applications
134+
135+
In addition to [Application composition](#application-composition), Wire.js helps you assemble large application from parts using the `$imports` keyword.
136+
137+
You may decide to decompose your large application into domains (transacting, settling, accounting, ...). You may also decide to decompose each domain into layers (presentation, business, infrastructure, ...). Let's call a layer of a domain an *application part*. Each application part would be developed, tested and wired in isolation.
138+
139+
To build an application, you will select the appropriate parts and assemble them together. To do so, all you need is import the spec of each selected part within the spec of the final application.
140+
141+
The spec of the final application would be as follows:
142+
143+
```javascript
144+
define({
145+
$imports: [
146+
'utils-spec',
147+
'commons-spec',
148+
'transaction-spec',
149+
'settlement-spec',
150+
'accounting-spec',
151+
]
152+
});
153+
```
154+
155+
Reading the above spec, Wire.js will first inline the content of each imported spec then process the result.
156+
133157
## Contexts
134158

135159
As the result of processing a spec, wire.js produces a **Context**. The context is a Javascript Object that contains the fully realized components that were specified in the wiring spec. The context also has methods for wiring child contexts, resolving references, and destroying the context and all the objects, etc. that were created when it was wired.

lib/context.js

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
(function(define){ 'use strict';
1111
define(function(require) {
1212

13-
var when, mixin, loaderAdapter, relativeLoader, Container;
13+
var when, mixin, loaderAdapter, relativeLoader, Container, specUtils;
1414

1515
when = require('when');
1616
mixin = require('./object').mixin;
1717
loaderAdapter = require('./loader/adapter');
1818
relativeLoader = require('./loader/relative');
1919
Container = require('./Container');
20+
specUtils = require('./specUtils');
2021

2122
/**
2223
* Creates a new context from the supplied specs, with the supplied
@@ -47,7 +48,7 @@ define(function(require) {
4748
options.moduleLoader =
4849
createContextLoader(specLoader, findBaseId(specs));
4950

50-
return mergeSpecs(specLoader, specs).then(function(spec) {
51+
return specUtils.mergeSpecs(specLoader, specs).then(function(spec) {
5152

5253
var container = new Container(parent, options);
5354

@@ -99,22 +100,5 @@ define(function(require) {
99100

100101
return firstId;
101102
}
102-
103-
function mergeSpecs(moduleLoader, specs) {
104-
return when(specs, function(specs) {
105-
return when.resolve(Array.isArray(specs)
106-
? mergeAll(moduleLoader, specs)
107-
: (typeof specs === 'string' ? moduleLoader(specs) : specs));
108-
});
109-
}
110-
111-
function mergeAll(moduleLoader, specs) {
112-
return when.reduce(specs, function(merged, module) {
113-
return typeof module == 'string'
114-
? when(moduleLoader(module), function(spec) { return mixin(merged, spec); })
115-
: mixin(merged, module);
116-
}, {});
117-
}
118-
119103
});
120104
}(typeof define === 'function' ? define : function(factory) { module.exports = factory(require); }));

lib/graph/cyclesTracker.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/** @license MIT License (c) copyright B Cavalier & J Hann */
2+
3+
/**
4+
* cyclesTracker
5+
* @author: brian@hovercraftstudios.com
6+
* @author: younes.ouadi@gmail.com
7+
*/
8+
(function(define) {
9+
define(function(require) {
10+
11+
var findStronglyConnected, formatCycles;
12+
13+
findStronglyConnected = require('./tarjan');
14+
formatCycles = require('./formatCycles');
15+
16+
/**
17+
* Make sure that the new name doesn't introduce a cycle.
18+
*
19+
* @param {string} name the name being used.
20+
* @param {string} onBehalfOf some indication of another name on whose behalf this
21+
* name is being used. Used to build graph and detect cycles
22+
* @return {string} the name being used.
23+
*/
24+
function ensureNoCycles(namesGraph, name, onBehalfOf) {
25+
var stronglyConnected, cycles;
26+
27+
// add the name to the graph
28+
onBehalfOf = onBehalfOf||'?';
29+
namesGraph.addEdge(onBehalfOf, name);
30+
31+
// compute cycles
32+
stronglyConnected = findStronglyConnected(namesGraph);
33+
cycles = stronglyConnected.filter(function(node) {
34+
// Only consider cycles that:
35+
// * have more than one node
36+
// * have one node and that node is not self-referenced
37+
return node.length > 1 || (node.length === 1 && Object.keys(node[0].edges).indexOf(node[0].name) !== -1);
38+
});
39+
40+
// is there a cycle?
41+
if(cycles.length) {
42+
// Cycles detected
43+
throw new Error('Possible circular usage:\n' + formatCycles(cycles));
44+
}
45+
46+
return name;
47+
}
48+
49+
return {
50+
ensureNoCycles: ensureNoCycles
51+
};
52+
});
53+
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));

lib/scope.js

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ define(function(require) {
1212

1313
var when, defer, sequence, array, object, loader, Map,
1414
ComponentFactory, Lifecycle, Resolver, WireProxy, PluginRegistry,
15-
undef;
15+
undef, specUtils, DirectedGraph, cyclesTracker;
1616

1717
when = require('when');
1818
sequence = require('when/sequence');
@@ -25,7 +25,10 @@ define(function(require) {
2525
Resolver = require('./resolver');
2626
WireProxy = require('./WireProxy');
2727
PluginRegistry = require('./plugin/registry');
28-
28+
specUtils = require('./specUtils');
29+
DirectedGraph = require('./graph/DirectedGraph');
30+
cyclesTracker = require('./graph/cyclesTracker');
31+
2932
defer = when.defer;
3033

3134
function Scope(parent, options) {
@@ -169,9 +172,10 @@ define(function(require) {
169172
var self = this;
170173

171174
return this._executeInitializers().then(function() {
172-
var parsed = self._parseSpec(spec);
173-
return self._createComponents(parsed).then(function() {
174-
return self._awaitInstances(parsed);
175+
return self._parseSpec(spec).then(function(parsed){
176+
return self._createComponents(parsed).then(function() {
177+
return self._awaitInstances(parsed);
178+
});
175179
});
176180
});
177181
},
@@ -263,34 +267,15 @@ define(function(require) {
263267
},
264268

265269
_parseSpec: function(spec) {
266-
var instances, components, ready, plugins, id, initialized;
267-
268-
instances = this.instances;
269-
components = this.components;
270-
ready = {};
271-
272-
// Setup a promise for each item in this scope
273-
for (id in spec) {
274-
if(id === '$plugins' || id === 'plugins') {
275-
plugins = spec[id];
276-
} else if (!object.hasOwn(instances, id)) {
277-
// An initializer may have inserted concrete components
278-
// into the context. If so, they override components of the
279-
// same name from the input spec
280-
initialized = defer();
281-
ready = defer();
282-
components[id] = this._createComponentDef(id, spec[id], initialized, ready);
283-
instances[id] = initialized.promise;
284-
ready[id] = ready.promise;
285-
}
286-
}
270+
var self = this;
287271

288-
return {
289-
plugins: plugins,
290-
components: components,
291-
instances: instances,
292-
ready: ready
293-
};
272+
// instantiate the imports graph
273+
var importsGraph = new DirectedGraph();
274+
275+
return processImports(self, spec, importsGraph).then(function(specImports){
276+
// modules of importing spec overrides modules of imported spec.
277+
return processSpec(self, object.mixin(specImports, spec));
278+
});
294279
},
295280

296281
_createComponentDef: function(id, spec, initialized, ready) {
@@ -498,6 +483,66 @@ define(function(require) {
498483

499484
function noop() {}
500485

486+
function processImports(scope, spec, importsGraph, importingModuleId) {
487+
if(!spec || !spec.$imports) {
488+
return when({});
489+
}
490+
491+
if(typeof spec.$imports === 'string') {
492+
spec.$imports = [spec.$imports];
493+
}
494+
495+
importingModuleId = importingModuleId || (typeof spec === 'string' ? spec : undefined);
496+
497+
return when.reduce(spec.$imports, function(currentSpecImports, importedModuleId){
498+
// make sure that there is no cycles
499+
cyclesTracker.ensureNoCycles(importsGraph, importedModuleId, importingModuleId);
500+
501+
// go ahead with the import
502+
return when(scope.getModule(importedModuleId), function(importedSpec){
503+
return processImports(scope, importedSpec, importsGraph, importedModuleId).then(function(importedSpecImports){
504+
// modules of importing spec overrides modules of imported specs.
505+
var importedSpecAndItsImports = object.mixin(importedSpecImports, importedSpec);
506+
507+
// modules in the right overrides modules in the left
508+
currentSpecImports = object.mixin(currentSpecImports, importedSpecAndItsImports);
509+
510+
return currentSpecImports;
511+
});
512+
});
513+
}, {});
514+
}
515+
516+
function processSpec(scope, spec) {
517+
var instances, components, ready, plugins, id, initialized;
518+
519+
instances = scope.instances;
520+
components = scope.components;
521+
ready = {};
522+
523+
// Setup a promise for each item in this scope
524+
for (id in spec) {
525+
if(id === '$plugins' || id === 'plugins') {
526+
plugins = spec[id];
527+
} else if(!object.hasOwn(instances, id)) {
528+
// An initializer may have inserted concrete components
529+
// into the context. If so, they override components of the
530+
// same name from the input spec
531+
initialized = defer();
532+
ready = defer();
533+
components[id] = scope._createComponentDef(id, spec[id], initialized, ready);
534+
instances[id] = initialized.promise;
535+
ready[id] = ready.promise;
536+
}
537+
}
538+
539+
return when.resolve({
540+
plugins: plugins,
541+
components: components,
542+
instances: instances,
543+
ready: ready
544+
});
545+
}
501546
});
502547
})(typeof define == 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }
503548
);

lib/specUtils.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/** @license MIT License (c) copyright B Cavalier & J Hann */
2+
3+
/**
4+
* Licensed under the MIT License at:
5+
* http://www.opensource.org/licenses/mit-license.php
6+
*
7+
* @author: Brian Cavalier
8+
* @author: John Hann
9+
*/
10+
11+
(function(define) { 'use strict';
12+
define(function(require) {
13+
14+
var object, when;
15+
16+
when = require('when');
17+
object = require('./object');
18+
19+
function mergeSpecs(moduleLoader, specs) {
20+
return when(specs, function(specs) {
21+
return when.resolve(Array.isArray(specs)
22+
? mergeAll(moduleLoader, specs)
23+
: (typeof specs === 'string' ? moduleLoader(specs) : specs));
24+
});
25+
}
26+
27+
function mergeAll(moduleLoader, specs) {
28+
return when.reduce(specs, function(merged, module) {
29+
return typeof module == 'string'
30+
? when(moduleLoader(module), function(spec) { return object.mixin(merged, spec); })
31+
: object.mixin(merged, module);
32+
}, {});
33+
}
34+
35+
return {
36+
mergeSpecs: mergeSpecs,
37+
mergeAll: mergeAll
38+
};
39+
});
40+
})(typeof define == 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }
41+
);

0 commit comments

Comments
 (0)