UNPKG

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