gulp-用gulp做一个略完整的前端打包工作

我们的官网要改版,会从以前的单一产品变成系列产品的官网,也就是现在的官网会是之后官网的一个子模块。

趁着这个机会,正好重新梳理了一下结构。加上罪恶之主管的一些要求,具体的需求如下:

分模块,每个模块都有独立的页面和静态文件,并将所有静态文件打在一个文件夹下,

  1. 一些常用变量可以进行替换,并可进行简单的页面动态生成,

  2. 生产环境打包与线上环境打包分开进行,

  3. 静态文件进行压缩合并,加md5以及cdn,

  4. wap的静态文件与web端分离,wap的页面文件没有的继承web端的页面文件,有的要用它本身。

  5. 打包由grunt换成gulp。

 

整体的路径要像这样:

gulp-用gulp做一个略完整的前端打包工作

 

好了,那我们开始,首先当然是要安装gulp

npm install gulp --save-dev

然后你需要在你的环境里加一个gulpfile.js的文件。

然后我们为了高大上一点,给弄个简易的手册出来:

gulp-用gulp做一个略完整的前端打包工作

直接console.log输出就好了···


// 说明 gulp.task('help', function() {   console.log('gulp build    文件打包');   console.log('gulp watch    文件监控打包');   console.log('gulp help     gulp参数说明');   console.log('gulp server    测试server');   console.log('gulp -p      生产环境(默认生产环境)');   console.log('gulp -d      开发环境');   console.log('gulp -m <module> 部分模块打包(默认全部打包)'); }); /* 默认 */ gulp.task('default', function() {   gulp.start('help'); });

 

然后在你的控制台输入 gulp help或者 gulp build 就可以看到效果啦!

然后我们要建立我们的build任务啦~

首先我们需要接收参数判断是开发打包还是生产打包。这里需要一个插件-- yargs,以及我们以后需要用到的工具类的插件:lodash(用于操作数组,当然它不仅仅是有这样的用处)和 path(用于写路径),用npm像上面那样加到你的环境中,并且在你的gulpfile文件中声明它们:


/* 载入工具 */ var argv = require('yargs').argv,    _ = require('lodash'),    path = require('path');

 

记得之后载入的插件都需要这样声明。

我们新建一个叫build的task


//创建任务 gulp.task('build', function() {  

});

 

在里面获取一下控制台输入的参数:

var evr = argv.p || !argv.d; //生产环境为true,开发环境为false,默认为true

var mod = argv.m || 'all';//模块明,默认为全部

 

这样你在控制台输入 gulp build -p 或者 gulp build -p -m t1这样就可以在里面获得参数啦。

 

然后我们需要进行一些打包的配置,先在写一个叫FileConfig的js文件,在里面写我们需要的文件配置信息:


"use strict"; var path = require('path'); /* 环境信息 */ var source = 'source',   develop = 'develop',   production = 'production'; /* src路径 */ var src = {   tpl: 'tpl/**',   css: 'styles/**/*.less',   js: 'lib/**/*.js',   html: '/**.html',   img: 'images/**' } /* 模块信息 */ var modules = {   "t1": {     src: 't1',     dest: 't1',     name: 't1',     css_files: 'styles/app.less'   },   "t2": {     src: 't2',     dest: 't2',     name: 't2'   },   "index": {     src: 'index',     dest: 'index',     name: 'index'   },   "common": {     src: 'common',     dest: 'common',     name: 'common'   } };

 

然后在下面写个方法:

var FileConfig  = function () {

};

为了省事儿,直接在exports的时候就new了它···

module.exports = new FileConfig();

之后我们的一些配置信息全部从FileConfig这个方法里取,比如要模块信息的话:

FileConfig.prototype.modules = function () {

  return modules;

};

之后的具体实现就不再啰嗦啦。

然后我们回到gulpfile.js文件下,声明他,之后会用到。

然后我们就要对html,css,js,img进行处理啦,这里会用到很多插件,一次性的列举出来,用法我在后面的注释中写了:


// 载入外挂 var gulp = require('gulp'),   browserify = require('browserify'), //这里用不上,管理js依赖的   source = require('vinyl-source-stream'), //同样这里用不上,和上面那个一起的   uglify = require('gulp-uglify'), //混淆js   clean = require('gulp-clean'), //清理文件   notify = require('gulp-notify'), //加控制台文字描述用的   buffer = require('vinyl-buffer'),   less = require('gulp-less'), //转换less用的   autoprefixer = require('gulp-autoprefixer'), //增加私有变量前缀   minifycss = require('gulp-minify-css'), //压缩   concat = require('gulp-concat'), //合并   fileinclude = require('gulp-file-include'), // include 文件用   template = require('gulp-template'), //替换变量以及动态html用   rename = require('gulp-rename'), //重命名   webserver = require('gulp-webserver'), //一个简单的server,用python的SimpleHttpServer会锁文件夹   imagemin = require('gulp-imagemin'), //图片压缩   gulpif = require('gulp-if'), //if判断,用来区别生产环境还是开发环境的   rev = require('gulp-rev'), //加MD5后缀   revReplace = require('gulp-rev-replace'), //替换引用的加了md5后缀的文件名,修改过,用来加cdn前缀   addsrc = require('gulp-add-src'), //pipeline中途添加文件夹,这里没有用到   del = require('del'), //也是个删除···   vinylPaths = require('vinyl-paths'), //操作pipe中文件路径的,加md5的时候用到了   runSequence = require('run-sequence'); //控制task顺序

 

好啦,为了省事儿,写个对象字面量的方法集:


var teemoGulp = {   /* html打包 */   buildHtml: function() {},   /* css 打包 */   buildCss: function() {},   /* js打包 */   buildJs: function() {},   /* img打包 */   buildImg: function() {},   /* md5打包 */   buildMd5: function() {} }

 

然后我们先说简单的,js打包,只需要初始路径和打包后路径就好啦:


/* js打包 */ buildJs: function() {   var src = arguments[0],     dest = arguments[1],     flag = arguments[2];   return gulp.src(src)   .pipe(gulp.dest(dest))   .pipe(uglify())   .pipe(rename({     suffix: '.min'   }))   .pipe(gulp.dest(dest)); }

 

其中flag是之前的这个var evr = argv.p || !argv.d; //生产环境为true,开发环境为false,默认为true,这个比较简单,先把原始文件拷贝一份,在混淆压缩加.min后缀存一份。

img打包也很简单


/* img打包 */ buildImg: function() {   var src = arguments[0],     dest = arguments[1],     flag = arguments[2]   return gulp.src(src)     .pipe(gulpif(flag, imagemin()))     .pipe(gulp.dest(dest)); },

 

这里注意一下用到了 gulpif  根据环境来判定是否需要做 图片的压缩操作,因为这项操作比较费时间,开发环境下就不进行了。

 

css打包和html打包需要用到一些存好的变量,比如,如果我们需要每个页面的keywords是一样的:

<meta name="keywords" content="<%- keywords  %>"> 

我们就需要给这些变量一个配置的地方,这个和fileconfig是一样的,就不再说了。然后替换这些变量我们要用到 gulp-template,他继承了lodash的template的用法,也就是说,他可以识别js代码以及lodash的一些用法。

而当我们想增加一些文件时,就要用到gulp-file-include,或者你直接改lodash,让他支持html代码也可以,不过我不赞成这样做。

他的用法类似这样:

@@include('t1/tpl/menu.html')

整体的html打包代码是这样:


/* html打包 */ buildHtml: function() {   var src = arguments[0],     dest = arguments[1],     flag = arguments[2],     options = arguments[3];   return gulp.src(src)   .pipe(fileinclude({     basepath: basePath.source   }))   .pipe(template(options, {     //interpolate: /\{-([\s\S]+?)-\}/g   }))   .pipe(gulp.dest(dest)) },

 

template默认的包裹变量的标示是 <%-%>当然你可以用 interpolate去配置这些。

好啦,方法我们写好了,下面是引用他们,我们需要在写一个方法,


var build = funciton () {

}

 

然后获取参数信息,并进行必要的配置,让他们可以按照我们想要的方式输出。


var evr = options.evr,   mod = options.mod; if (!modules[mod]) mod = 'all'; /* 路径初始化 */ fileConfig.init(evr); gulp.task('clean', function() {   if (mod === 'all') {     var clean_path = path.join(evr && basePath.production || basePath.develop, '/');     return gulp.src([clean_path], {       read: false     })     .pipe(clean());   } }); /* 获取要build的模块 */ var parts = []; if (mod === 'all') {   parts = modules; } else {   parts = _.pick(modules, mod); }

 

之后我们循环遍历modules,建立相应的task


/* 分别对模块进行建立 */ var list = []; for (var key in parts) {   (function(key) { //闭个包     var options = fileConfig.getModule(parts[key]);     /* js建立 */     gulp.task(key + '_js', function() {       return teemoGulp.buildJs(options.js.src, options.js.dest, evr);     });     /* css建立 */     gulp.task(key + '_css', function() {       return teemoGulp.buildCss(options.css.src, options.css.dest, evr, teemoConfig, options.css.filename);     });     /* html建立 */     gulp.task(key + '_html', function() {       return teemoGulp.buildHtml(options.html.src, options.html.dest, evr, teemoConfig);     });     /* img建立 */     gulp.task(key + '_img', function() {       return teemoGulp.buildImg(options.img.src, options.img.dest, evr);     });     /* 模块建立 */     gulp.task(key + '_module', function(cb) {       runSequence(         [key + '_html', key + '_css', key + '_js', key + '_img'],         cb       );     });   })(key);   list.push(key + '_module'); }

 

这里用到了runSequence,它制定了task的执行顺序,因为如果不这样做,gulp默认是尽可能的并发执行,就有可能出现 clean没执行完,后续就开始执行的结果,这里注意吧task的回调放在runSequence的最后,保证内部的task执行完,再执行完本身。

当然如果比较简单的,你可以用task的第二个参数来控制task的执行顺序,这里不啰嗦啦。

然后把list返还给你最初的 build


var list = Build({   evr: evr,   mod: mod }); var md5 = evr && 'md5' || []; runSequence(   'clean', list, 'md5', cb );

 

然后顺序执行即可。

之后到了md5的阶段,md5打包用rev插件即可,但是他默认的md5码是10个,你可以去修改gulp-rev\node_modules\rev-hash路径下的index.js文件,或者干脆做个变量在你的filegulp里配置,单我觉得没必要,就偷懒这里改一下啦。我喜欢用16位在之后需要把这些加了md5的文件名字换给你引用这些文件的地方,就用到了 rev-replace。

rev在改路径时候会生成一个映射表,你用rev.manifest()即可储存在相应的目录里,然后rev-replace用这个文件替换掉你目标文件的路径即可。

至于cdn,也可以用revReplace里的prefix属性去加前缀,不过他只支持字符串,如果我们是有多个cdn域名的话,返回给他的可能是一个方法,所以这里我们需要改一下rep-replace文件。

找到gulp-rev-replace下的index.js文件。

在第36行加一句

var prefix = typeof options.prefix === 'function' ? options['prefix'](path.basename(file.path)) : options.prefix;

用prefix替换掉一下用到options.prefix的地方。你需要在第60行左右做同样的事情:


if (options.manifest) {   // Read manifest file for the list of renames.   options.manifest.on('data', function(file) {     var manifest = JSON.parse(file.contents.toString());     Object.keys(manifest).forEach(function(srcFile) {       var prefix = typeof options.prefix === 'function'         ? options['prefix'](path.basename(srcFile))         : options.prefix; //这句是加的       renames.push({         unreved: canonicalizeUri(srcFile),         reved: prefix + canonicalizeUri(manifest[srcFile]),         prefix: prefix //这句也是后加的       });     });   });   options.manifest.on('end', replaceContents); } else {   replaceContents(); }

 

而且你会发现在之后替换的时候也用到了这个,所以你要他当局部变量传进renames里去。想上面写的那样,因为下面一句用到了他:


if (rename.prefix) {   contents = contents.split('/' + rename.prefix).join(rename.prefix + '/'); //这句也改了 }

 

它这替换方式也挺丧良心的···

这样,我们就可以往里面传方法了~

网上找个算cdn域名的方法:


/*生成hash  */ hashFnv32a : function(str, asString, seed) {   /*jshint bitwise:false */   var i, l,     hval = (seed === undefined) ? 0x811c9dc5 : seed;   for (i = 0, l = str.length; i < l; i++) {     hval ^= str.charCodeAt(i);     hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);   }   if( asString ){     // Convert to 8 digit hex string     return ("0000000" + (hval >>> 0).toString(16)).substr(-8);   }   return hval >>> 0; }

 

然后我们就可以进行md5加cdn的打包操作了:


/* md5打包 */ buildMd5: function () {   var vp = vinylPaths();   return gulp.src(path.join(basePath.production, '/**/{images,lib,styles}/**/*.*'))     .pipe(vp)     .pipe(rev())     .pipe(gulp.dest(basePath.production))     .pipe(rev.manifest())     .pipe(gulp.dest(basePath.production))     .on('end',function () {       var manifest = gulp.src(path.join(basePath.production, "/rev-manifest.json"));       gulp.src(path.join(basePath.production, '/**/*.{css,html}'))         .pipe(revReplace({           manifest: manifest,           prefix: function (f) {             //cdn域名             var cdns = ["s1","s2","s3","s4"];             var fileHashCode = teemoGulp.hashFnv32a(f);             if(fileHashCode < 0 ){               fileHashCode = 0;             }             var cdn = cdns[fileHashCode % cdns.length];             return 'http://'+cdn+'.tm.sogoucdn.com/w/20141209'           }         }))         .pipe(gulp.dest(basePath.production))         .on('end',function () {           del(vp.paths);           del(path.join(basePath.production, "/rev-manifest.json"));         })     }) }

 

这样,整个的打包就完成了啦,压缩混淆合并加md5加cdn就全都搞定了。

至于加wap的文件夹,就在做一遍之前的操作即可,记得html要在做一遍覆盖操作,也就是三遍。

需要注意的是你改过的插件需要自己存到特定的包管理的地方去,或者存到自己的github上也行,配好ssh就能下。

最后你需要一个 测试的server,这个很好配:


/* server */

gulp.task('server', function() {

 var evr = argv.p || !argv.d;

 gulp.src(evr?basePath.production:basePath.develop)

   .pipe(webserver({

     livereload: true,

     directoryListing: true,

     open: true

   }));

});

 

不过如果你要测试cdn的话,就只能自己配nginx了,gulp-server上倒是能配代理,不过我没用过,应该也好用。

这样,所有的工作就完成了,这样做还是很方便的,比如,主管要求生产环境和开发环境的路径上要在家 一层路径,那么我们直接修改fileConfig里面的配置就可以:


/* 环境信息 */

var source = 'source',    develop = 'build/develop',    production = 'build/production';

转自:https://sanwen8.cn/p/18aFAJV.html