UNPKG

6.2 kBJavaScriptView Raw
1'use strict';
2
3var util = require('util');
4var Emitter = require('component-emitter');
5
6var utils = require('./lib/utils');
7var Task = require('./lib/task');
8var noop = require('./lib/noop');
9var map = require('./lib/map-deps');
10var session = require('./lib/session');
11var flowFactory = require('./lib/flow');
12
13/**
14 * Composer constructor. Create a new Composer
15 *
16 * ```js
17 * var composer = new Composer();
18 * ```
19 *
20 * @api public
21 */
22
23function Composer(name) {
24 Emitter.call(this);
25 this.name = name || 'composer';
26 this.tasks = {};
27}
28
29/**
30 * Mix in `Emitter` methods
31 */
32
33Emitter(Composer.prototype);
34
35/**
36 * Register a new task with it's options and dependencies.
37 *
38 * Options:
39 *
40 * - `deps`: array of dependencies
41 * - `flow`: How this task will be executed with it's dependencies (`series`, `parallel`, `settleSeries`, `settleParallel`)
42 *
43 * ```js
44 * composer.task('site', ['styles'], function () {
45 * return app.src('templates/pages/*.hbs')
46 * .pipe(app.dest('_gh_pages'));
47 * });
48 * ```
49 *
50 * @param {String} `name` Name of the task to register
51 * @param {Object} `options` Options to set dependencies or control flow.
52 * @param {String|Array|Function} `deps` Additional dependencies for this task.
53 * @param {Function} `fn` Final function is the task to register.
54 * @return {Object} Return `this` for chaining
55 * @api public
56 */
57
58Composer.prototype.task = function(name/*, options, dependencies and task */) {
59 // return the currently running task
60 // when no name is given
61 if (arguments.length === 0) {
62 return session(this.name).get('task');
63 }
64
65 var deps = [].concat.apply([], [].slice.call(arguments, 1));
66 var options = {};
67 var fn = noop;
68 if (typeof deps[deps.length-1] === 'function') {
69 fn = deps.pop();
70 }
71
72 if (deps.length && utils.isobject(deps[0])) {
73 options = deps.shift();
74 }
75
76 options.deps = deps
77 .concat(options.deps || [])
78 .map(map.bind(this));
79
80 var task = new Task({
81 name: name,
82 options: options,
83 fn: fn,
84 session: session(this.name),
85 app: this
86 });
87
88 // bubble up events from tasks
89 task.on('starting', this.emit.bind(this, 'starting'));
90 task.on('finished', this.emit.bind(this, 'finished'));
91 task.on('error', this.emit.bind(this, 'error'));
92
93 this.tasks[name] = task;
94 return this;
95};
96
97/**
98 * Build a task or list of tasks.
99 *
100 * ```js
101 * composer.build('default', function (err, results) {
102 * if (err) return console.error(err);
103 * console.log(results);
104 * });
105 * ```
106 *
107 * @param {String|Array|Function} `tasks` List of tasks by name, function, or array of names/functions.
108 * @param {Function} `cb` Callback function to be called when all tasks are finished building.
109 * @api public
110 */
111
112Composer.prototype.build = function(/* list of tasks/functions to build */) {
113 var args = [].concat.apply([], [].slice.call(arguments));
114 var done = args.pop();
115 if (typeof done !== 'function') {
116 throw new TypeError('Expected the last argument to be a callback function, but got `' + typeof done + '`.');
117 }
118 var fn = this.series.apply(this, args);
119 return fn(done);
120};
121
122/**
123 * Compose task or list of tasks into a single function that runs the tasks in series.
124 *
125 * ```js
126 * composer.task('foo', function (done) {
127 * console.log('this is foo');
128 * done();
129 * });
130 *
131 * var fn = composer.series('foo', function bar(done) {
132 * console.log('this is bar');
133 * done();
134 * });
135 *
136 * fn(function (err) {
137 * if (err) return console.error(err);
138 * console.log('done');
139 * });
140 * //=> this is foo
141 * //=> this is bar
142 * //=> done
143 * ```
144 *
145 * @param {String|Array|Function} `tasks` List of tasks by name, function, or array of names/functions.
146 * @return {Function} Composed function that may take a callback function.
147 * @api public
148 */
149
150Composer.prototype.series = flowFactory('series');
151
152/**
153 * Compose task or list of tasks into a single function that runs the tasks in parallel.
154 *
155 * ```js
156 * composer.task('foo', function (done) {
157 * setTimeout(function () {
158 * console.log('this is foo');
159 * done();
160 * }, 500);
161 * });
162 *
163 * var fn = composer.parallel('foo', function bar(done) {
164 * console.log('this is bar');
165 * done();
166 * });
167 *
168 * fn(function (err) {
169 * if (err) return console.error(err);
170 * console.log('done');
171 * });
172 * //=> this is bar
173 * //=> this is foo
174 * //=> done
175 * ```
176 *
177 * @param {String|Array|Function} `tasks` List of tasks by name, function, or array of names/functions.
178 * @return {Function} Composed function that may take a callback function.
179 * @api public
180 */
181
182Composer.prototype.parallel = flowFactory('parallel');
183
184
185/**
186 * Watch a file, directory, or glob pattern for changes and build a task or list of tasks
187 * when changes are made. Watch is powered by [chokidar][] so the glob pattern may be
188 * anything that [chokidar.watch](https://github.com/paulmillr/chokidar#api) accepts.
189 *
190 * ```js
191 * var watcher = composer.watch('templates/pages/*.hbs', ['site']);
192 * ```
193 *
194 * @param {String|Array} `glob` Filename, Directory name, or glob pattern to watch
195 * @param {Object} `options` Additional options to be passed to [chokidar][]
196 * @param {String|Array|Function} `tasks` Tasks that are passed to `.build` when files in the glob are changed.
197 * @return {Object} Returns an instance of `FSWatcher` from [chokidar][]
198 * @api public
199 */
200
201Composer.prototype.watch = function(glob, options/*, fns/tasks */) {
202 var self = this;
203 var len = arguments.length - 1, i = 0;
204 var args = new Array(len + 1);
205 while (len--) args[i] = arguments[++i];
206 args[i] = done;
207
208 var opts = {};
209 if (typeof options === 'object' && !Array.isArray(options)) {
210 args.shift();
211 opts = utils.extend(opts, options);
212 }
213
214 var building = true;
215 function done (err) {
216 building = false;
217 if (err) console.error(err);
218 }
219
220 var watch = utils.chokidar.watch(glob, opts);
221
222 // only contains our `done` function
223 if (args.length === 1) {
224 return watch;
225 }
226
227 watch
228 .on('ready', function () {
229 building = false;
230 })
231 .on('all', function () {
232 if (building) return;
233 building = true;
234 self.build.apply(self, args);
235 });
236
237 return watch;
238};
239
240/**
241 * Expose Composer
242 */
243
244module.exports = Composer;