UNPKG

15.5 kBJavaScriptView Raw
1/**
2 * Created by Rodey on 2017/6/29.
3 */
4
5'use strict';
6
7const gulp = require('gulp'),
8 gulpClean = require('gulp-clean'),
9 prettyTime = require('pretty-hrtime'),
10 extend = require('extend'),
11 EventEmitter = require('events').EventEmitter,
12 util = require('../utils'),
13 T = require('../tools'),
14 serconf = require('../serconf'),
15 DeployManager = require('../deploy/deployManager'),
16 TaskNode = require('./taskNode'),
17 TaskNodeFun = require('./taskNodeFun');
18
19process.setMaxListeners(0);
20
21//获取配置
22const //是否启动本地服务
23 isStartServer = T.hasArg(['s', 'server']),
24 isOpenBrowser = T.hasArg(['o', 'open-browser']),
25 isSkipBackup = T.hasArg('skip-backup');
26
27class Gupack extends EventEmitter {
28 constructor(config, gulpInstance) {
29 super();
30 this.env = process.env.NODE_ENV || T.getArg(['e', 'env']) || 'local';
31 this.project = T.getArg(['p', 'project']);
32 this.basePath = T.getArg('cwdir') || process.cwd();
33 this.sourceDir = 'src';
34 this.buildDir = 'dist';
35 this.indexFile = '';
36 this.host = serconf.servers.host;
37 this.port = serconf.servers.port;
38 this.sport = serconf.servers.sport;
39 this.liveDelay = serconf.servers.liveDelay;
40 this.devServer = isStartServer;
41 this.openBrowser = isOpenBrowser;
42 this.hostname = null;
43 // 默认编译前清空编译输出路径下所有文件
44 this.startClean = true;
45 this.isRunAloneTask = false;
46 this.watch = true;
47 this.watchOption = {};
48 this.cleans = [];
49 this.cleansFilter = ['!.svn', '!.git'];
50 this.buildTasks = [];
51 this.tasks = [];
52 this.relies = {};
53 this.watchers = {};
54 this.basePath = process.cwd();
55 this.gulp = gulpInstance || gulp;
56 this.server = null;
57 this.runIndex = 0;
58 this.taskStartCallback = null;
59 this.taskStopCallback = null;
60 this.allTaskDoneCallback = null;
61 this.isAllTaskDone = true;
62 this.isDeploy = T.getArg('skip-deploy');
63 this.deploy = null;
64 this.deploies = [];
65
66 for (let prop in config) {
67 if (config.hasOwnProperty(prop)) {
68 this[prop] = config[prop];
69 }
70 }
71
72 this.init();
73 }
74
75 init() {
76 // 初始化路径
77 this.initPaths();
78 // 初始化发布部署相关
79 this.initDeploy();
80 // 初始化tasks
81 this.initTasks();
82 // 重组tasks,按照rely
83 // this.recombineTasks();
84 // 监听文件变化
85 this.watch && this.initWatch();
86 // 在执行任务之前进行清理
87 this.initClean();
88 }
89
90 initPaths() {
91 if (!T.isAbsolutePath(this.sourceDir)) {
92 this.sourceDir = T.Path.resolve(this.basePath, this.sourceDir || 'src');
93 }
94 if (!T.isAbsolutePath(this.buildDir)) {
95 this.buildDir = T.Path.resolve(this.basePath, this.buildDir || 'dist');
96 }
97 }
98
99 // 初始化部署相关对象(SFTP)
100 initDeploy() {
101 if (util.isObject(this.deploy)) {
102 if (!this.deploy['localPath']) {
103 this.deploy.localPath = T.Path.join(this.buildDir, '/**/*');
104 }
105 this.deploies.push(this.deploy);
106 } else if (util.isArray(this.deploy)) {
107 this.deploies = this.deploy.map((d) => {
108 if (!d['localPath']) {
109 d.localPath = T.Path.join(this.buildDir, '/**/*');
110 }
111 return d;
112 });
113 }
114 const deploies = extend(true, [], this.deploies);
115 this.deployManager = new DeployManager(deploies, this.watch);
116 }
117
118 initClean() {
119 if (!util.isArray(this.cleans)) return false;
120 this.cleansFilter = this.cleansFilter.concat(this.buildDir).map((cf) => {
121 return '!' + cf.replace(/^!/i, '');
122 });
123 if (this.cleans.length > 0) {
124 this.cleans = this.cleans.concat(this.cleansFilter);
125 this.gulp.task('build._cleans', () => {
126 return this.gulp.src(this.cleans).pipe(
127 gulpClean({
128 read: true
129 })
130 );
131 });
132 //添加清理任务
133 this.tasks.unshift('build._cleans');
134 }
135 }
136
137 initTasks() {
138 if (!util.isObject(this.buildTasks)) {
139 T.log.error(
140 ${new TypeError(
141 '参数 buildTask 未找到或类型错误(buildTask必须是一个对象Object)' +
142 '\n× 请您检查项目的gupack-config.js文件配置'
143 )}`
144 );
145 return false;
146 }
147
148 let tasks = this.buildTasks;
149 for (let key in tasks) {
150 if (tasks.hasOwnProperty(key)) {
151 const task = tasks[key];
152 const taskNode = !util.isFunction(task)
153 ? new TaskNode(task, this)
154 : new TaskNodeFun(task);
155 taskNode.name = key;
156 this.buildTasks[key] = taskNode;
157 }
158 }
159 this.setTask();
160
161 this.tasks = Object.keys(this.buildTasks).map((taskName) => {
162 let tasker = this.buildTasks[taskName];
163
164 if (!tasker.fun) {
165 this.gulp.task(taskName, tasker['rely'], (done) => {
166 return !!tasker.run === true ? tasker.getTaskFunction(done) : done();
167 });
168 } else {
169 this.gulp.task(taskName, tasker['rely'], tasker.fun);
170 }
171
172 return taskName;
173 });
174 }
175
176 initWatch() {
177 // 如果config.watch中配置了监听列表,则直接使用config.watch
178 if (
179 util.isArray(this.watch) ||
180 util.isString(this.watch) ||
181 util.isObject(this.watch)
182 ) {
183 let watchSource = [],
184 ts = null;
185 if (util.isArray(this.watch)) {
186 watchSource = this.watch.map((w) => T.Path.resolve(this.sourceDir, w));
187 }
188 if (util.isString(this.watch)) {
189 watchSource = [T.Path.resolve(this.sourceDir, this.watch)];
190 }
191 if (util.isObject(this.watch)) {
192 ts = [];
193 Object.keys(this.watch).map((w) => {
194 watchSource.push(T.Path.resolve(this.sourceDir, this.watch[w]));
195 ts.push(w);
196 });
197 }
198
199 this.gulp.task('build._watch', () => {
200 this.gulp.watch(watchSource, this.watchOption || {}, ts || this.tasks);
201 });
202 //添加监听任务
203 return this.tasks.push('build._watch');
204 }
205
206 // 否则将根据buildTask中的单个任务配置中的watch字段
207 let watchers = this.watchers,
208 relies = this.relies;
209
210 Object.keys(watchers).length > 0 &&
211 (() => {
212 this.gulp.task('build._watch', () => {
213 let source, watcher;
214
215 Object.keys(watchers).forEach((k) => {
216 source = watchers[k];
217 let ts = [];
218 //查找依赖,如: build.html的rely包含 build.css
219 //当build.css的watch中的文件变化,将反转执行task(build.css -> build.html)
220 Object.keys(relies).forEach((rely) => {
221 if (relies[rely] && relies[rely].indexOf(k) !== -1) {
222 ts.unshift(rely);
223 }
224 });
225 ts.push(k);
226 watcher = this.gulp.watch(source, this.watchOption || {}, ts);
227 });
228 });
229 //添加监听任务
230 this.tasks.push('build._watch');
231 })();
232 }
233
234 recombineTasks() {
235 //先存储所有被 rely的
236 let paiallels = [], //并行
237 sequences = []; //串行
238
239 Object.keys(this.relies).forEach((rely) => {
240 if (util.isArray(this.relies[rely]) && this.relies[rely].length !== 0) {
241 this.relies[rely].forEach((task) => {
242 if (paiallels.indexOf(task) === -1) {
243 paiallels.unshift(task);
244 }
245 });
246 }
247 });
248 //根据rely和taskName重组顺序
249 Object.keys(this.relies).forEach((rely) => {
250 if (this.relies[rely] && paiallels.indexOf(rely) !== -1) {
251 let index = paiallels.indexOf(rely);
252 let removeTemp = paiallels.splice(index, 1);
253 sequences.unshift(removeTemp[0]);
254 } else if (paiallels.indexOf(rely) === -1) {
255 sequences.push(rely);
256 }
257 });
258 paiallels.length !== 0 && sequences.unshift(paiallels);
259 this.tasks = sequences;
260 }
261
262 setTask() {
263 let tasks = this.buildTasks;
264 let tns = Object.keys(tasks);
265
266 tns.forEach((tn) => {
267 let task = tasks[tn];
268 task.name = tn;
269 if (task.watch && !this.nowatch) {
270 this.watchers[tn] = task.watch;
271 }
272 this.relies[tn] = task.rely;
273 task.dest && task.beforeClean !== false && this.cleans.push(task.dest);
274 });
275 }
276
277 addEvent() {
278 let self = this,
279 logCache = [],
280 // 开始编译状态,emit socket browser
281 isStart = false,
282 tasks = Object.keys(this.buildTasks),
283 finishMsg = `√ Builded finish OK =^_^= (^_^) =^_^= !!! \n\r`;
284
285 this.gulp.on('task_start', (e) => {
286 this.isAllTaskDone = false;
287 let name = e.task,
288 task = this.buildTasks[name],
289 isRun =
290 (task instanceof TaskNode || task instanceof TaskNodeFun) &&
291 task.run === true;
292 if (util.isFunction(this.taskStartCallback)) {
293 return this.taskStartCallback.call(this, e);
294 }
295 if (tasks.indexOf(name) > -1 && logCache.indexOf(name) === -1 && isRun) {
296 T.log.green(`→ [${T.getTime()}] Starting '${name}' ...`);
297 }
298 if (!isStart) {
299 isStart = true;
300 this.server && this.server.emitBuilding();
301 }
302 });
303 this.gulp.on('task_stop', (e) => {
304 this.isAllTaskDone = false;
305 let name = e.task,
306 task = this.buildTasks[name],
307 isRun =
308 (task instanceof TaskNode || task instanceof TaskNodeFun) &&
309 task.run === true,
310 duration = prettyTime(e.hrDuration);
311 if (util.isFunction(this.taskStopCallback)) {
312 return this.taskStopCallback.call(this, e);
313 }
314 if (tasks.indexOf(name) > -1 && logCache.indexOf(name) === -1 && isRun) {
315 T.log.green(
316 `√ [${T.getTime()}] Finished '${e.task}', after ${T.msg.yellow(
317 duration
318 )}`
319 );
320 logCache.push(name);
321 this.emit('finish_task', {
322 task: e.task,
323 name,
324 duration
325 });
326 }
327 });
328
329 this.gulp.on('stop', (e) => {
330 isStart = false;
331 logCache = [];
332
333 // 单独执行task
334 if (this.isRunAloneTask) {
335 return process.exit(0);
336 }
337
338 if (util.isFunction(this.allTaskDoneCallback)) {
339 this.allTaskDoneCallback.call(this, e);
340 _doneNext.apply(this);
341 return false;
342 } else {
343 T.log.yellow(finishMsg);
344 }
345 this.emit('finish_all_task', {
346 event: e,
347 message: finishMsg
348 });
349
350 // 判断watch
351 _doneNext.apply(this);
352 });
353
354 this.gulp.on('task_err', (e) => {
355 self.server && self.server.stop();
356 T.log.error(
357 `'${e.task}' errored after ${prettyTime(
358 e.hrDuration
359 )} \n\r ${Gupack.formatError(e)}`
360 );
361 });
362
363 this.gulp.on('task_not_found', (err) => {
364 self.server && self.server.stop();
365 T.log.error(
366 `Task \'' + err.task + '\' is not found \n\r Please check the documentation for proper gupack-config file formatting`
367 );
368 });
369
370 function _doneNext() {
371 this.emit('finish_all_task_next_before', {
372 gupack: this
373 });
374
375 // 开启本地live静态服务器
376 this.devServer && this.startServer();
377
378 const deploies = extend(true, [], this.deploies);
379 if (this.isDeploy) {
380 if (deploies.length > 0) {
381 T.log.yellow(
382 `→ has deploys. but now skip deploy(arg: --skip-deploy).`
383 );
384 }
385 return T.log.end();
386 }
387 if (!this.deployManager) {
388 this.deployManager = new DeployManager(deploies);
389 } else {
390 this.deployManager.setDeploy(deploies);
391 }
392 this.deployManager.startDeploy(isSkipBackup, this.watch);
393 this.emit('finish_all_task_next_after', {
394 gupack: this
395 });
396 this.isAllTaskDone = true;
397 }
398 }
399
400 startServer() {
401 if (!this.devServer || this.runIndex) return;
402 process.nextTick(() => {
403 // 可自定义devServer,开发环境热更新服务器
404 if (util.isFunction(this.devServer)) {
405 this.devServer.call(this);
406 }
407 if (this.devServer === true) {
408 this.server = require('../gupackServer');
409 this.server.createLiveServer(this);
410 }
411 this.runIndex++;
412 });
413 }
414
415 setDefaultTask() {
416 this.gulp.task('default', this.tasks);
417 // this.gulp.task('default', runSequence.apply(this.gulp, this.tasks));
418 }
419
420 run() {
421 let _run = () => {
422 if (!this.isAllTaskDone) {
423 return T.log.yellow(`» The last task has not been completed )====( `);
424 }
425 this.addEvent();
426 //清理成功后执行任务列表
427 this.setDefaultTask();
428 this.gulp.start.apply(this.gulp, this.tasks);
429 };
430
431 if (this.startClean) {
432 Gupack.cleanBuildDir((e) => {
433 _run();
434 }, this.buildDir);
435 } else {
436 _run();
437 }
438 }
439
440 runTask(name) {
441 let ts = [];
442 if (util.isArray(name)) {
443 name.forEach((nm) => {
444 ts.push(nm);
445 });
446 } else {
447 ts = [name];
448 }
449
450 ts = ts.filter((t) => {
451 let isIndex = util.isObject(this.buildTasks[t]);
452 if (!isIndex) T.log.red(`× not fount task: '${t}'`);
453 return isIndex;
454 });
455
456 if (this.startClean) {
457 ts.forEach((t) => {
458 let taskNode = this.buildTasks[t],
459 dest = taskNode.dest;
460 if (!T.fsa.removeSync(dest))
461 T.log.green(`√ '${dest}' clean successfully !!!`);
462 else T.log.green(`× '${dest}' clean failed (╯︵╰,)`);
463 });
464 }
465
466 if (ts.length === 0) {
467 T.log.error(${new TypeError('not fount task')}`);
468 } else {
469 this.isRunAloneTask = true;
470 this.addEvent();
471 this.gulp.task('default', ts);
472 this.gulp.start.apply(this.gulp, ts);
473 }
474 }
475
476 runDeploy() {
477 this.deployManager.startDeploy(isSkipBackup, false);
478 }
479
480 runBackup() {
481 this.deployManager.startBackup(null);
482 }
483
484 runRollback() {
485 this.deployManager.startRollback();
486 }
487
488 tree() {
489 const str = [];
490 const self = this;
491
492 function getRelys(task = []) {
493 task.forEach((rely) => {
494 str.push(T.msg.gray(`\n -→ ${rely}`));
495 if (self.relies[rely]) {
496 getRelys(self.relies[rely]);
497 }
498 });
499 }
500
501 Object.keys(this.relies).forEach((name, i) => {
502 str.push(T.msg.green(`\n<${i + 1}>${name}`));
503 this.relies[name] && getRelys(this.relies[name]);
504 });
505 console.log(str.join(''));
506 }
507
508 static cleanBuildDir(cb, dist) {
509 let startDate = Date.now();
510 T.log.green('→ clean starting ......');
511 T.fsa.remove(T.Path.join(dist, '**/*'), (err) => {
512 if (err) {
513 T.log.error('× clean failed: ' + err.toString());
514 } else {
515 let time = (Date.now() - startDate) * 0.001;
516 time = time.toFixed(2) + ' s';
517 T.log.green(`√ clean successfully !!!, after ${T.msg.yellow(time)}`);
518 cb && cb();
519 }
520 });
521 }
522 static formatError(e) {
523 if (!e.err) {
524 return e.message;
525 }
526 if (typeof e.err.showStack === 'boolean') {
527 return e.err.toString();
528 }
529 if (e.err.stack) {
530 return e.err.stack;
531 }
532 return new Error(String(e.err)).stack;
533 }
534}
535
536module.exports = Gupack;