UNPKG

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