重构你的 gulpfile

Posted by Sir0xb on 2016-08-12 16:18:55 +0800

用 Node.js 的 Stream 概念,gulp 实现了一套自己的 pipe,再包裹上 Promise 机制,让每个功能在一个接一个的管道中流过,宏观上实现我们需要它完成的 Task。Task 很好用,于是用了很多,结果 gulpfile 变得很长,每次修改就要费劲找好长时间,径管理也比较麻烦,很不好维护。是时候彻底重构一下了。

用 es6 替代 coffee

脚本原来是用 coffee 维护的,既能减少代码长度,又能避免未返回 gulp 对象所引起的断流问题。不过毕竟 coffee 已经不维护了,也没有自带的模块开发功能,所以这次决定改版成 es6 方式。

先引入 babel-corebabel-preset-es2015 这两个包

配置 package.json

...
"babel"{
    "presets": [
        "es2015"
    ]
}
...

es6 用的脚本名称不再是 gulpfile.js,需要改成 gulpfile.babel.js

es6 环境搭建完成。

 

目录结构优化

fescripts 文件夹作为脚本主目录,只放一些配置文件及任务文件夹。

tasks 文件夹存放需要根据环境调用的任务。像 default.task.js 平时开发时调用,build.task.js 在服务器发布环境中运行。

cleanercompiledebug 等文件夹存放具体小功能(就是原来 gulpfile.js 里的 task)。

 

配置文件说明 gulp.config.js

'use strict';
 
import Q from 'q';
 
module.exports = function ({ isDev = false, stage = "test" } = {}) {
    let config = {
        isDev: isDev,
        system_list: (() => {
            if (isDev) {
                return [
                    "alphago/livecast/livecast-web/livecast-crm-webapp/src/main/webapp",
                    "alphago/livecast/livecast-web/livecast-student-webapp/src/main/webapp",
                    "alphago/livecast/livecast-web/livecast-teacher-webapp/src/main/webapp"
                ];
            } else {
                return [
                    `./build-repo/build-${stage}/livecast-crm-webapp/webroot`,
                    `./build-repo/build-${stage}/livecast-student-webapp/webroot`,
                    `./build-repo/build-${stage}/livecast-teacher-webapp/webroot`
                ];
            }
        })(),
        great_promise: function(gulpJob, prams = true) {
            let deferred = Q.defer();
            let promiseArray = [];
 
            this.system_list.forEach((base) => {
                let def = Q.defer();
 
                gulpJob(base, def);
 
                promiseArray.push(def.promise);
            });
 
            Q.all(promiseArray).then(function () {
                deferred.resolve(prams);
            });
 
            return deferred.promise;
        },
        colorful: function (word, $) {
            let message = '';
            Array.from(word, function (x, i) {
                message += $.chalk[['red', 'yellow', 'green', 'blue', 'magenta', 'cyan', 'gray'][i%7]](x);
            });
            console.info(`[${ $.chalk.gray($.dateformat(new Date(), 'H:MM:ss')) }] >>> ${ message } <<<`);
        },
        jobStart: function ($, time, taskName, base = null) {
            console.info(`[${ $.chalk.gray($.dateformat(time, 'H:MM:ss')) }${ $.chalk.red('Starting') } '${ $.chalk.green(taskName) }' ... ${ base != null ? ' ==> ' + $.chalk.yellow(base) : '' }`);
        },
        jobEnd: function ($, time, taskName, defTime, base = null) {
            console.info(`[${ $.chalk.gray($.dateformat(time, 'H:MM:ss')) }${ $.chalk.blue('Finished') } '${ $.chalk.green(taskName) }' in ${ $.chalk.magenta(defTime + ' ms') } ${ base != null ? ' ==> ' + $.chalk.yellow(base) : '' }`);
        }
    };
 
    return config;
};

参数说明

isDev 开发与发布环境辨别的参数,默认值给了false,也就是开发环境。

stage 发布环境状态的不同环境区分,例如: test、staging、release等

内部函数说明

system_list: 是立即执行函数,根据环境返回不同目录,最终是个数组。

great_promise: 解决同一任务便利不同系统目录,并并发直行用的。

jobStart & jobEnd:除了服务器脚本,其他所有的脚本都直接用 promise 实现,所以任务信息需要自己打出。

 

入口脚本配置 gulpfile.babel.js

'use strict';
 
import gulp         from 'gulp';
import $            from 'gulp-load-plugins';
import config       from './fescripts/gulp.config.js';
import Clean        from './fescripts/tasks/clean.task.js';
import Build        from './fescripts/tasks/build.task.js';
import Default      from './fescripts/tasks/default.task.js';
 
gulp.task("clean", function () {
    return Clean(gulp, config(), $({
        pattern: ['gulp-*', 'gulp.*', 'chalk', 'dateformat', 'q'],
        lazy: true
    }), gulp.env.taskName || 'all');
});
 
gulp.task("build", function () {
    return Build(gulp, config({
        stage: process.argv.length >= 5 && process.argv[4] || 'test'
    }), $({
        pattern: ['gulp-*', 'gulp.*', 'chalk', 'dateformat', 'q'],
        lazy: true
    }));
});
 
gulp.task("watch", ['default'], function () {
    return gulp.watch(['alphago/livecast/livecast-web/**/*.scss'], ['default']);
});
 
gulp.task("default", function () {
    return Default(gulp, config({
        isDev: true
    }), $({
        pattern: ['gulp-*', 'gulp.*', 'chalk', 'dateformat', 'q'],
        lazy: true
    }));
});

这里的 $ 不是 jQuery 的缩写,是 gulp 的一个插件 gulp-load-plugins

这插件会默认把 package.jsongulp- & gulp.* 开头的插件全部加载到环境中。

如果需要,可以根据自己的需要修改 pattern 配置,加载更多其他插件。

 

编译脚本 build.task.js

'use strict';
 
import clean    from './clean.task.js';
import css      from './compile/css.compile.js';
import tools    from './compile/tools.compile.js'
import js       from './compile/js.compile.js';
import html     from './compile/html.compile.js';
import formal   from './debug/formal.mode.js';
 
module.exports = function (gulp, config, $, param = true) {
    return $.q.fcall(() => true).then(function () {
        return clean(gulp, config, $);
    }).then(function () {
        return css(gulp, config, $);
    }).then(function () {
        return tools(gulp, config, $);
    }).then(function () {
        return js(gulp, config, $);
    }).then(function () {
        return html(gulp, config, $);
    }).then(function () {
        return config.isDev ? formal(gulp, config, $) : true;
    }).then(function () {
        return param;
    });
};

这里用到了 Node.js 的 q 模块

 

小任务脚本 css.compile.js

'use strict';
 
module.exports = function (gulp, config, $, param = true) {
    return $.q.fcall(() => true).then(function () {
        return config.great_promise(function (base, def) {
            let startTime = new Date();
            config.jobStart($, startTime, 'Make css version', base);
 
            gulp.src([
                `${base}/public/css/*.css`,
                `!${base}/public/css/*-*.css`
            ])
            .pipe($.plumber())
            .pipe($.sourcemaps.write('maps', {
                includeContent: false
            }))
            .pipe($.rev())
            .pipe(gulp.dest(`${base}/public/css`))
            .pipe($.rev.manifest())
            .pipe(gulp.dest(`${base}/public/css`))
            .on('end', function () {
                let endTime = new Date();
                config.jobEnd($, endTime, 'Make css version', endTime.getTime() - startTime.getTime(), base);
 
                def.resolve();
            });
        });
    }).then(function () {
        return config.great_promise(function (base, def) {
            let startTime = new Date();
            config.jobStart($, startTime, 'Change links', base);
 
            gulp.src([
                `${base}/public/css/rev-manifest.json`,
                `${base}/WEB-INF/livecast/layout/*links.ftl`
            ])
            .pipe($.plumber())
            .pipe($.if(/\.ftl$/, $.replace(/main(\S*).css/g, 'main.css')))
            .pipe($.revCollector())
            .pipe(gulp.dest(`${base}/WEB-INF/livecast/layout/`))
            .on('end', function () {
                let endTime = new Date();
                config.jobEnd($, endTime, 'Change links', endTime.getTime() - startTime.getTime(), base);
 
                def.resolve();
            });
        });
    }).then(function () {
        return param;
    });
};

 

运行后效果

可以看到 Recover links 任务开始的顺序是 crm -> student -> teacher,最后完成顺序是 crm -> teacher -> student。

每个具体任务间是串行,三个项目目录间同一个任务是并行的。