基于gulp的前端自动化构建实例

2018-05-20  本文已影响35人  我的宠物
gulp-logo.png

一、解决的问题

刚入职公司时,公司有一个基于thinkphp框架的网站项目。由于没有任何自动化构建,开发时存在如下痛点:

  • 无法使用es6
  • 无法使用scss
  • 开发时无法动态刷新网页(livereload)
  • 同有js/css的压缩功能
  • 前端资源上传到cdn上,没有根据文件内容自动生成新的hash后缀,每次更新前端代码,都需要手动强制刷新CDN缓存。

本实例完成后,以上痛点都已解决。

二、gulp还是webpack?

gulp是基于流的自动化构建工作,具有易于使用、构建快速、插件高质、易于学习的优点。虽然当前webpack已经是spa页面开发的必备编译打包工具,但是如果是针对服务端渲染的前端资源构建,gulp使用起来也十分方便,并且可编程性更强,更能满足多样的前端构建需求。本项目是基于gulp的一系列插件来实现。

三、用到的gulp插件

插件名称 作用
gulp-sass 编译scss
gulp-cssnano 压缩css
postcss-sprites 生成精灵图片
gulp-postcss 编译post-css
gulp-babel 编译es6
gulp-uglyfly 压缩js
gulp-server-livereload 静态资源代码和页面自动刷新
gulp-mem 将前端资源编译到内存中,而非硬盘上
gulp-sequence gulp任务序列
gulp-watch gulp监听文件改变执行相应任务
gulp-clean 删除文件、文件夹
gulp-rev 生成js、css对应的manifest.json
gulp-rev-collector 根据manifest.json在html页面中生成对应的js、css HASH名后缀
gulp-shell 执行shell命令

四、项目代码结构

文件名 内容
gulpfile.js gulp插件入口文件,放在项目根目录下
backend.js 定义后端服务器
gulp/basic.js 较低层级的子任务
gulp/task.js 较高层级的gulp任务(由gulp/basic.js中的较低层级的子任务组合而成)

五、具体代码

gulpfile.js

//gulpfile.js入口文件,主要定义一些配置选项,和定义gulp默认任务
let gulp  = require('gulp');
let shell = require('gulp-shell');
let requireDir = require("require-dir");

let GulpMem = require("gulp-mem");
const gulpMem = new GulpMem();
gulpMem.enableLog = false;

gulp.opts = {
    srcDir: 'Public/',
    staticDir: '../static/www/',
    gulpMem: gulpMem,
};

requireDir("./gulp");

gulp.task("default",function(){
    gulp.start("dev");
});

backend.js

let ip;
function getIp(){
    if(ip){
        return ip;
    }

    let interfaces = require('os').networkInterfaces();
    for (let devName in interfaces) {
        let iface = interfaces[devName];
        for (let i = 0; i < iface.length; i++) {
            let alias = iface[i];
            if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
                localIp = alias.address;
                return alias.address;
            }
        }
    }
}

function backend() {
    let ip = getIp();
    let server;

    switch (ip){
        case "192.168.9.28":
            //johney IP
            server = `http://${ip}:7010`;
            break;
        //如果是其它人的IP, 请自己添加case条件
        default:
            server = "http://localhost:7010";
            break;
    }

    return server;
}

module.exports = backend;

gulp/basic.js

let gulp = require("gulp");
let uglyfly = require("gulp-uglyfly");
let gulpSass = require('gulp-sass');
let cssnano = require('gulp-cssnano');
let rev = require('gulp-rev');
let cleanFile = require('gulp-clean');
let revCollector = require('gulp-rev-collector');
let gulpSequence = require('gulp-sequence');
let rename = require('gulp-rename');
let sprites = require('postcss-sprites');
let gulpFilter = require("gulp-filter");
let postcss    = require('gulp-postcss');
let babel = require("gulp-babel");
let gulpif = require("gulp-if");
let gutil = require("gulp-util");
let gulpMem = gulp.opts.gulpMem;

gulp.task("clean1",function(cb){
    return gulp.src([
        gulp.opts.srcDir + 'css/',
        gulp.opts.srcDir + 'js/',
        gulp.opts.srcDir + 'images.sprite/',
    ])
        .pipe(cleanFile({force:true}));
});

gulp.task("clean2",function(cb){
    return gulp.src(gulp.opts.staticDir, {read:false})
        .pipe(cleanFile({force:true}));
});

function isJplayer(file){
    //判断是js且并非jplayer.js
    return /jquery\.jplayer\./.test(file.history[0]);
}

gulp.task("js-dev", function (cb) {
    return gulp.src(gulp.opts.srcDir + 'es/**/*.js',{base:gulp.opts.srcDir + 'es'})
    //.pipe(gulpif(isJplayer, gutil.noop(), gutil.noop()))
        .pipe(babel({
            "presets": ["es2015"]
        }))
        .pipe(gulpMem.dest(gulp.opts.srcDir + 'js'));
});


gulp.task("js1", function (cb) {
    return gulp.src(gulp.opts.srcDir + 'es/**/*.js',{base:gulp.opts.srcDir + 'es'})
        //.pipe(gulpif(isJplayer, gutil.noop(), gutil.noop()))
        .pipe(babel({
            "presets": ["es2015"]
        }))
        .pipe(gulp.dest(gulp.opts.srcDir + 'js'));
});

gulp.task("js2",["js1"],function(cb){
    return gulp.src(gulp.opts.srcDir + 'js/**/*.js',{base:gulp.opts.srcDir})
        .pipe(uglyfly())  //js压缩
        .pipe(rev())
        .pipe(gulp.dest(gulp.opts.staticDir))
        .pipe(rev.manifest())
        .pipe(gulp.dest(gulp.opts.staticDir + 'revmanifest/js/'))
});

gulp.task("sprite_scss-dev", function (cb) {
    return gulp.src([gulp.opts.srcDir + 'scss/**/*.sprite.scss'])
        .pipe(gulpSass().on('error', gulpSass.logError))
        .pipe( postcss([ sprites({
            spritePath: './Public/images.sprite/',
            groupBy: function (image) {
                var url = image.styleFilePath.split('/').pop().split('.');
                return Promise.resolve(url[0]);
            }
        }) ]))
        .pipe(gulpMem.dest(gulp.opts.srcDir + 'css/'));
});


gulp.task("sprite_scss1", function (cb) {
    return gulp.src([gulp.opts.srcDir + 'scss/**/*.sprite.scss'])
        .pipe(gulpSass().on('error', gulpSass.logError))
        .pipe( postcss([ sprites({
            spritePath: './Public/images.sprite/',
            groupBy: function (image) {
                var url = image.styleFilePath.split('/').pop().split('.');
                return Promise.resolve(url[0]);
            }
        }) ]))
        .pipe(gulp.dest(gulp.opts.srcDir + 'css/'));
});

gulp.task("sprite_scss2", ["sprite_scss1"] , function (cb) {
    return gulp.src([gulp.opts.srcDir + 'css/**/*.sprite.css'],{base:gulp.opts.srcDir})
        .pipe(cssnano({
            isSafe:true,
            autoprefixer: {
                add: true
            }
        }))
        .pipe(rev())
        .pipe(gulp.dest(gulp.opts.staticDir))
        .pipe(rev.manifest())
        .pipe(gulp.dest(gulp.opts.staticDir + 'revmanifest/css.sprite/'))
});


gulp.task('scss-dev', function (cb) {
    return gulp.src([
        gulp.opts.srcDir + 'scss/**/*.scss',
        "!" + gulp.opts.srcDir + 'scss/**/*.sprite.scss',
    ],{base:gulp.opts.srcDir + 'scss'})
        .pipe(gulpSass().on('error', gulpSass.logError))
        .pipe(gulpMem.dest(gulp.opts.srcDir + 'css/'));
});

gulp.task('scss1', function (cb) {
    return gulp.src([
        gulp.opts.srcDir + 'scss/**/*.scss',
        "!" + gulp.opts.srcDir + 'scss/**/*.sprite.scss',
    ],{base:gulp.opts.srcDir + 'scss'})
        .pipe(gulpSass().on('error', gulpSass.logError))
        .pipe(gulp.dest(gulp.opts.srcDir + 'css/'));
});

gulp.task('scss2',["scss1"], function (cb) {
    return gulp.src([
        gulp.opts.srcDir + 'css/**/*.css',
        "!" + gulp.opts.srcDir + 'css/**/*.sprite.css',
    ],{base:gulp.opts.srcDir})
        .pipe(cssnano({
            isSafe:true,
            autoprefixer: {
                add: true
            }
        }))
        .pipe(rev())
        .pipe(gulp.dest(gulp.opts.staticDir))
        .pipe(rev.manifest())
        .pipe(gulp.dest(gulp.opts.staticDir + 'revmanifest/css/'))
});


gulp.task("copy2",function(cb){
    //实际上要拷备的是images images.sprite 资源
    return gulp.src([gulp.opts.srcDir +"other/*.*",
        gulp.opts.srcDir +"fonts/**/*.*",
        gulp.opts.srcDir +"images/**/*.*",
        gulp.opts.srcDir +"img/**/*.*",
        gulp.opts.srcDir +"images.sprite/**/*.*",

    ],{base:gulp.opts.srcDir})
        .pipe(gulp.dest(gulp.opts.staticDir))
});

gulp.task("html2",function(cb){
    "use strict";
    return gulp.src([
        gulp.opts.staticDir + 'revmanifest/**/*.json',
        gulp.opts.srcDir+'../Home/View/**/*.html'
    ])
        .pipe(revCollector({
            replaceReved: true
        }) )
        .pipe(gulp.dest(gulp.opts.srcDir + '/../Home/View/') );
});

gulp/task.js

let gulp  = require('gulp');
let gulpSequence = require('gulp-sequence');
let gulpWatch = require("gulp-watch");
let gulpSass = require('gulp-sass');
let cssnano = require('gulp-cssnano');
let sprites = require('postcss-sprites');
let gulpFilter = require("gulp-filter");
let postcss    = require('gulp-postcss');
let babel = require("gulp-babel");
let server = require('gulp-server-livereload');
let injectReload = require("gulp-inject-reload");
let gutil = require("gulp-util");
let gulpif = require("gulp-if");
let express = require('express');
let gulpMem = gulp.opts.gulpMem;
let backend = require("../backend");

gulp.task("devbuild", function (cb) {
    return gulpSequence("clean1",["js-dev", "scss-dev","sprite_scss-dev"] ,cb);
});

gulp.task("sandbox", function (cb) {
    return gulpSequence("clean2",["js2","scss2","sprite_scss2",], "copy2", "html2", "clean1",cb);
});


gulp.task("prod", function (cb) {
    return gulpSequence("clean2",["js2","scss2","sprite_scss2",], "copy2", "html2", "clean1",cb);
});


gulp.task('watch', ["devbuild"], function () {
    gulpWatch([
            gulp.opts.srcDir + 'scss/**/*.scss',
            "!" + gulp.opts.srcDir + 'scss/**/*.sprite.scss',
        ],
        {
            base:gulp.opts.srcDir,
            verbose:true,
        },
        function (file, cb) {
            return gulp.src(file.path, {base:gulp.opts.srcDir + "scss/"})
                .pipe(gulpSass().on('error', gulpSass.logError))
                .pipe(gulpMem.dest(gulp.opts.srcDir + 'css/'))

        });

    gulpWatch([gulp.opts.srcDir + 'scss/**/*.sprite.scss'],
        {
            base:gulp.opts.srcDir,
            verbose:true,
        },
        function (file, cb) {
            return gulp.src(file.path, {base:gulp.opts.srcDir + "scss/"})
                .pipe(gulpSass().on('error', gulpSass.logError))
                .pipe( postcss([ sprites({
                    spritePath: './Public/images.sprite/'
                }) ]))
                .pipe(gulpMem.dest(gulp.opts.srcDir + 'css/'))

        });


    function isJplayer(file){
        //判断是js且并非jplayer.js
        return /jquery\.jplayer\./.test(file.history[0]);
    }

    gulpWatch([gulp.opts.srcDir + 'es/**/*.js'],
        {
            base:gulp.opts.srcDir,
            verbose:true,
        },
        function (file, cb) {
            return gulp.src(file.path, {base:gulp.opts.srcDir + "es/"})
                // .pipe(gulpif(isJplayer, gutil.noop(), gutil.noop()))
                .pipe(babel({
                    "presets": ["es2015"]
                }))
                .pipe(gulpMem.dest(gulp.opts.srcDir + 'js'));
        });

});


gulp.task('dev',["watch"], function() {
    let app = express();
    app.use(gulpMem.middleware);
    app.listen(3001);

    return gulp.src(['./Public','./Home/View','./Tpl/Home'])   //会监听的地址
        .pipe(server({
            host:"0.0.0.0",
            port:3000,
            livereload: {
                enable: true,
                filter: function(filename, cb) {
                    cb(/\.(scss|js|png|svg|jpg|jpeg|html|htm)$/.test(filename)); //监控文件改变
                }
            },
            directoryListing: true,
            open: false,
            proxies:[
                {
                    source:"/Public/js",
                    target:"http://127.0.0.1:3001/Public/js"
                },
                {
                    source:"/Public/css",
                    target:"http://127.0.0.1:3001/Public/css"
                },
                {
                    source:"/",
                    target:backend(),
                },
            ]
        }));
});

package.json

{
    "scripts": {
        "dev": "gulp dev",
        "sandbox": "gulp sandbox",
        "prod": "gulp prod"
    }
}

六、需要后端配合的地方

开发时,页面中必须引入livereload.js才能实现动态刷新,而生产环境下,又不能引入livereload.js。我采用的做法是后端php判断当前环境动态引入livereload.js。

项目后端语言为php,框架用了thinkphp
<if condition="$Think.const.ENVIRONMENT_TYPE eq 'Dev' " >
    <script src="http://127.0.0.1:35729/livereload.js"></script>
</if>

七、使用方法

执行npm run dev,浏览器访问http://localhost:3000

执行npm run sandbox,会更新所有Home/View/目录下的html的js后缀名和css后缀名,最终前端资源生成在../static目录下面。
Home/View下面的的html文件更新前:


before-update-hash.png

Home/View下面的的html文件更新后:


after-update-hash.png

PS:本项目的js和css是通过下面的php模板代码引入的。

<volist name="CSSLIST" id="vo">
    <link type="text/css" rel="stylesheet" href="/Public/{$vo}?{:C('CSS_JSS_VERSION')}" >
</volist>
<volist name="JSLIST" id="vo">
    <script type="text/javascript" src="/Public/{$vo}?{:C('CSS_JSS_VERSION')}"></script>
</volist>
//所以页面上只需要定义```$CSSLIST``` ```$JSLIST```变量即可。 

同沙箱

客官觉得写得好就打个赏呗^_^
上一篇 下一篇

猜你喜欢

热点阅读