-
Notifications
You must be signed in to change notification settings - Fork 2
/
Twine.js
executable file
·217 lines (192 loc) · 5.92 KB
/
Twine.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/**
* @license Copyright (c) 2011 Cello Software, LLC.
* All rights reserved.
* Available via the new BSD License.
*/
/*jshint
asi: false, bitwise: false, boss: false, curly: true, eqeqeq: true, eqnull: false, es5: true,
evil: false, expr: true, forin: true, globalstrict: false, immed: true, indent: 4, latedef: true,
laxbreak: false, loopfunc: true, maxlen: 100, newcap: true, noarg: true, noempty: true,
nonew: true, nomen: false, onevar: true, passfail: false, plusplus: false, shadow: false,
strict: false, sub: false, trailing: true, undef: true, white: true
*/
/*global define: false, require: false*/
define([
'./support/array',
'./support/compose',
'./Kernel',
'./support/promise',
'./util/error'
], function (arr, compose, Kernel, promise, error) {
'use strict';
function getUid() {
return 'twine_' + uid++;
}
function normalizeConfigItems(items) {
return arr.map(items, function (item) {
// a function is handled as a factory/constructor
if (typeof item === 'function') {
return item(); // NOTE: not called with `new`
}
return item;
});
}
function checkDestroyed(container) {
if (container._destroyed) {
throw new error.ContainerDestroyed(container);
}
}
// when constructor is called, compose has already mixed in the options
function Twine() {
// ensure the invariants
if (!this.name) {
// assign an arbitrary name
this.name = getUid();
}
if (!this.kernel) {
// create the default kernel
this.kernel = new Kernel();
}
if (!this.load) {
this.load = typeof module !== 'undefined' ?
// in node, call cb with deps mapped through require
function (deps, cb) {
cb.apply(this, deps.map(require));
}
// anywhere else assume AMD require
: require;
}
}
var uid = 0,
defer = promise.defer,
when = promise.when,
all = promise.all,
slice = [].slice;
// the Twine Class
return compose(
// use compose as a constructor to mixin options passed to constructor
compose,
Twine,
{
// the container's identifier
name: null,
// reference to the kernel instance being used
kernel: null,
// reference to a function to load dependencies
load: null,
// adds another fiber to the twine
addFiber: function (fiber) {
return this.kernel.addFiber(fiber);
},
// configure instance based on a config object. likely an async operation so a promise
// is always returned.
configure: function (config) {
checkDestroyed(this);
config = config || {};
var seq = [],
container = this,
fibers = config.fibers,
installers = config.installers,
components = config.components,
// allow calls to configure to provide their own load function
// default to container's load function
load = config.load || this.load;
// if the config has fibers
if (fibers && fibers.length) {
// configure the fibers and add to the sequence of promises
seq.push(function () {
return when(container._configure(fibers, load), function (fibers) {
// add each fiber to this container
return all(arr.map(fibers, function (fiber) {
return container.addFiber(fiber);
}));
});
});
}
// if the config has installers
if (installers && installers.length) {
// configure the installers and add them to the sequence of promises
seq.push(function () {
return when(container._configure(installers, load),
function (installers) {
// install each of the installers
return all(arr.map(installers, function (installer) {
return container.install(installer);
}));
}
);
});
}
// if the config has components
if (components && components.length) {
// components will be lazy loaded
seq.push(function () {
return all(arr.map(components, function (component) {
// a component may provide it's own load function
if (!component.load) {
// default to (config.load || this.load);
component.load = load;
}
return container.kernel.addComponentModel(component);
}));
});
}
// fibers, installers and components need to be applied in sequence
return when(promise.seq(seq), function () {
return container;
});
},
// fibers and installers can be provided in the config as:
// - a string, considered as a module id to be loaded and then considered again
// - a function, it will be considered a factory and executed
// - anything else, assumed to be an instance of a fiber
_configure: function (items, load) {
items = items || [];
load = load || this.load;
var dfd = defer(),
deps = [],
loaded = [];
// build the list of dependencies to be loaded
arr.forEach(items, function (item) {
// a string is a reference to a module that needs to be loaded
(typeof item === 'string' ? deps : loaded).push(item);
});
// if any dependencies need to be loaded
if (deps.length) {
// load the modules and then resolve the deferred
load(deps, function () {
// NOTE: this changes the order compared to the config
dfd.resolve(normalizeConfigItems(loaded.concat(slice.call(arguments))));
});
}
else {
dfd.resolve(normalizeConfigItems(loaded));
}
return dfd.promise;
},
// add components using installers
install: function (installer) {
var container = this;
checkDestroyed(container);
return promise.whenPromise(installer.install(container), function () {
return container;
});
},
// resolve a component according to the spec provided
resolve: function (spec, args) {
checkDestroyed(this);
return this.kernel.resolve(spec, args);
},
// release a component
release: function (component) {
checkDestroyed(this);
return this.kernel.release(component);
},
// destroy the container
destroy: function () {
this._destroyed = true;
return this.kernel.destroy();
}
}
);
});