gulp是一个非常流行的前端自动化构建工具,主要用来设定程序自动处理静态资源的工作。简单说,gulp就是用来打包项目的。
先看一下 我的前端项目结构,应该跟大多数人的项目结构都相同:
编辑
如上图所示:
- src目录:代码目录
- bower_components目录:静态js目录
- build目录:src目录的编译目录,可删除,编译后会重新生成
- node_modules目录:node环境的固定目录
本文讨论中的最主要的目录:
- 打包文件:gulpfile.js
打包文件的主要内容:
var gulp = require('gulp');
var connect = require('gulp-connect');/*创建本地服务器*/
var modRewrite = require('connect-modrewrite');
var wiredep = require('wiredep').stream;
var usemin = require('gulp-usemin');
var uglify = require('gulp-uglify');/*最小化js文件*/
var minifyHtml = require('gulp-minify-html');
var minifyCss = require('gulp-minify-css');/*最小化css文件*/
var rev = require('gulp-rev');
var clean = require('gulp-clean');
var concat = require('gulp-concat');/*合并文件*/
var copy = require('gulp-copy');/*拷贝文件*/
var argv = require('yargs').argv;
var watch = require('gulp-watch');
var fs = require('fs-sync');
var jshint = require('gulp-jshint');
var map = require('map-stream');
var less = require('gulp-less');/*获取gulp-less插件*/
var replace = require('gulp-replace');
var rename = require('gulp-rename');
var revCollector = require('gulp-rev-collector');
if (argv.env === undefined) {
console.error("Please specify a profile, like `--env staging`. Refer to README.md for more details.");
process.exit();
}
/*定义一个静态集合,主要包含了几个js的路径。这个集合在jsLib这个任务中用到*/
var jsLibList = [ 'bower_components/jquery/dist/jquery.min.js', 'bower_components/jquery.cookie/jquery.cookie.js', "bower_components/base-64/base64.js", "bower_components/clipboard/dist/clipboard.min.js", "bower_components/js-md5/build/md5.min.js"];
/**
* 启动一个Web服务器
* 并且开启 rewrite 功能,配合html5mode
* gulp.task是用来创建一个任务(相当于创建一个方法,类似function关键字)
* gulp.task有3个参数,gulp.task('name3',['name1','name2'],fn)
* 第一个参数name3是当前任务的任务名,类似方法名
* 第三个参数,fn,是当前名为name3的这个任务具体的代码,可省略。
* 第二个参数['name1','name2']是依赖的其他方法的方法名的集合,
* 如果定义了这第二个参数,则表示当前名为name3的这个任务需要先执行名为name1和name2的任务之后,才能其自身的代码fn。
* 如果未定义这第二个参数,则直接执行自身代码fn
* 如果定义了这第二个参数,未定义其自身执行代码fn,则当前这个名为name3的任务只相当于同时调用了 name1和name2的方法,相当于将原本的子任务全部集合到一个主任务上面来,方便管理。
*/
/*创建一个名为websever的任务,主要用于开启本地服务*/
gulp.task('webserver', function() {
/*创建本地连接并,port:端口号,livereload:开启实时刷新,debug:开启调试*/
connect.server({
port: 8001,
livereload: true,
debug: true
});
});
//build wap
/*gulp.task用来创建一个名为build的任务,主要用于集合编译并删除多余文件
第一个参数build,是创建的任务名,即方法名,
第二个参数是 其他任务名的集合,相当于把其他任务名对应的方法放到一起执行,即同时调用这个了5个方法
这个方法相当于把build_index_and_js,images,lib,json和cleanSurplus这5个方法集合起来执行,形成了一个新的方法,这个新的方法的方法名叫build*/
gulp.task('build', ['build_index_and_js', 'images', 'lib','json','cleanSurplus']);
/*gulp.task用来创建一个名为watch的任务,主要用于关联和运行这个gulpfile.js文件中定义的其他任务
第一个参数watch是方法名,是创建的任务名,即方法名,
第二个参数是 其他任务名的集合,相当于把build和webserver两个方法放到一起执行
第三个参数是 watch本身的执行代码,表示在执行完build和webserver方法后,
第三个参数的具体代码即gulp.watch的第一个参数是路径集合,第二个参数是任务集合
表示当第一个参数中的文件内容发生变化的时候,就会再重新调用执行一次第二个参数中任务名所对应的任务,
eg:如果修改了src/activities/collection/index.html修改时,会先执行build_index_and_js方法,再执行cleanSurplus方法删除多余文件*/
gulp.task('watch', ['build', 'webserver'], function() {
gulp.watch(['src/js/**/*.js', 'src/app.js', 'src/activities/**/*.html', 'src/css/**/*.less'], ['build_index_and_js', 'cleanSurplus']);
});
gulp.task('lint', function() {
return gulp.src(['src/js/**/*.js', '!src/js/config.js'])
.pipe(jshint())
.pipe(jshint.reporter('default'));
});
/*创建一个名为clean的任务,主要用于删除文件*/
gulp.task('clean', function() {
/*gulp.src第一个参数:是扫描build及其子目录和子文件,第二个参数{read:false}:是不读取文件,加快程序
*扫描完成后执行clean()方法,可以看到clean方法实际调用的是:gulp-clean插件,即删除扫描到的文件和文件夹*/
return gulp.src('build/', { read: false })
.pipe(clean());
});
/*创建一个名为images的任务,主要用于拷贝src/images/路径下的静态资源*/
gulp.task('images', function() {
/*gulp.src:扫描src/images/路径下的文件和文件夹
*扫描完成后执行copy()方法,可以看到copy方法实际调用的是gulp-copy插件,即将扫描到的文件及其路径拷贝到build目录下*/
return gulp.src(['src/images/**'])
.pipe(copy('build/', { prefix: 1 })); // prefix = 1, ignore src dir
});
/*创建一个名为json的任务,主要用于拷贝src/data/路径下的静态资源*/
gulp.task('json', function() {
/*gulp.src:扫描src/data/路径下的文件和文件夹
*同上,将扫描到的文件及其路径容拷贝到build目录下*/
return gulp.src(['src/data/**'])
.pipe(copy('build/', { prefix: 1 })); // prefix = 1, ignore src dir
});
/*创建一个名为cleanWatchBuild的任务,主要用于删除已经编译过的html、css和js文件*/
gulp.task('cleanWatchBuild', function() {
/*gulp.src:扫描'build/css', 'build/js', 'build/activities'这三个路径下的文件和文件夹
* 扫描完成后调用clean()方法,可以看到clean方法实际调用的是:gulp-clean插件,即删除扫描到的文件和文件夹*/
return gulp.src(['build/css', 'build/js', 'build/activities'])
.pipe(clean());
});
/*创建一个名为txtCopy的任务,主要用于拷贝txt或html文件*/
gulp.task('txtCopy', function() {
/*gulp.src:扫描所有后缀名为txt和html的文件
*同上,将扫描到的文件及其路径拷贝到build目录下 */
return gulp.src(['*.txt','*.html'])
.pipe(copy('build/'));
});
/*创建一个名为css的任务,先调用名为cleanWatchBuild和txtCopy的任务,主要用于编译css文件。
将src目录下的所有的less样式文件转成css,随后压缩并合并成一个名为app.css的文件,对这个文件加上md5版本签名,
生成到build/css路径下,并生成映射文件放到src/css*/
gulp.task('css', ['cleanWatchBuild', 'txtCopy'], function() {
/*gulp.src:扫描src/css目录下所有的less文件,并通过pipe转成文件流
* less():调用gulp-less插件:将less文件编译成css文件
* minifyCss():调用gulp-minify-css插件:将css文件压缩,减少文件大小
* concat('app.css'):调用gulp-concat插件:将多个css文件合并到一个临时文件app.css
* rev():调用gulp-rev插件:根据文件内容,生成md5签名,加在文件名上,不会因为缓存问题,页面无法立即刷新。当文件发生变化后,md5变化,文件名发生变化,浏览器读取新文件,从而解决缓存问题
* gulp.dest('build/css'):调用gulp自带的api即dest方法,将经过上面一系列操作的文件流进行输出生成文件放到build/css路径下
*rev.manifest:调用gulp-rev插件的manifest方法:将app.css的文件名与其 版本号(即md5签名)对应输出到js映射文件,保存到src下的rev-manifest.json文件中,
* 其中base为存放上一个生成的rev-manifest.json文件,然后merge为是否在已有文件后面追加,由于在当前任务之前没有映射输出,所以直接创建一个名为rev-manifest.json的文件
* 最终生成的rev-manifest.json文件内容就是{"app.css": "app-44e278f711.css"}*/
return gulp.src(['src/css/**/*.less'])
.pipe(less())
.pipe(minifyCss())
.pipe(concat('app.css'))
.pipe(rev())
.pipe(gulp.dest('build/css'))
.pipe(rev.manifest({
base: 'src/**',
merge: true
}))
.pipe(gulp.dest("src/css"));
});
/*创建一个名为jsApp的任务,先调用名为css的任务。主要用于编译src目录下的js文件*/
gulp.task('jsApp', ['css'], function() {
/*gulp.src:扫描参数集合中包含的指定js和指定路径下的js文件,转成流
*uglify():调用gulp-uglify插件:压缩扫描到的文件流
*concat('app.js'):调用gulp-concat插件:将上一步处理后的文件流合并成一个文件app.js
*rev():调用gulp-rev插件:给上一步处理后的文件名上加上MD5签名,使得浏览器读取这次生成的新文件,以避免出现缓存问题
*gulp.dest:调用gulp自带的api即dest方法 ,将文件输出到指定目录下
*rev.manifest:调用gulp-rev插件的manifest方法:将app.js与其 版本号(即md5签名)对应输出到js映射文件,保存到src下的rev-manifest.json文件中,
* 其中base为存放上一个生成的rev-manifest.json文件,然后merge为是否在已有文件后面追加,
* 比如当前任务调用了css这个任务,css任务在rev-manifest.json中输出了映射关系"app.css": "app-44e278f711.css",那么追加则为在这个关系后面添加一个如:"app.js": "app-ef93c0cc1c.js"
* 最终生成的rev-manifest.json文件就是{"app.css": "app-44e278f711.css","app.js": "app-ef93c0cc1c.js"}*/
return gulp.src(['src/config.js', 'src/js/app.js', 'src/js/service/*.js', 'src/js/util/*.js'])
.pipe(uglify())
.pipe(concat('app.js'))
.pipe(rev())
.pipe(gulp.dest('build/js/'))
.pipe(rev.manifest({
base: 'src/**',
merge: true
}))
.pipe(gulp.dest("src/js"));
});
/*创建一个名为jsLib的任务,先调用名为jsApp的任务。主要用于编译bower_components目录下的js文件,输出为lib.js*/
gulp.task('jsLib', ['jsApp'], function() {
/*gulp.src:扫描参数集合中包含的指定js,转成流
*uglify():调用gulp-uglify插件:压缩扫描到的文件流
*concat('lib.js'):调用gulp-concat插件:将上一步处理后的文件流合并成一个文件lib.js
*rev():调用gulp-rev插件:给上一步处理后的文件名上加上MD5签名,使得浏览器读取这次生成的新文件,以避免出现缓存问题
*gulp.dest:调用gulp自带的api即dest方法 ,将文件输出到指定目录下
*rev.manifest:调用gulp-rev插件的manifest方法:将lib.js与其 版本号(即md5签名)对应输出到js映射文件,保存到src下的rev-manifest.json文件中,
* 其中base为存放上一个生成的rev-manifest.json文件,然后merge为是否在已有文件后面追加,比如当前任务调用了jsApp这个任务,jsApp任务在rev-manifest.json中输出了映射关系 "app.js": "app-ef93c0cc1c.js",,那么追加则为在这个关系后面添加一个如:"lib.js": "lib-8f9133ddc7.js"
* 最终生成的rev-manifest.json文件就是{"app.css": "app-44e278f711.css","app.js": "app-ef93c0cc1c.js","lib.js": "lib-8f9133ddc7.js"}*/
return gulp.src(jsLibList)
.pipe(uglify())
.pipe(concat('lib.js'))
.pipe(rev())
.pipe(gulp.dest('build/js/'))
.pipe(rev.manifest({
base: 'src/**',
merge: true
}))
.pipe(gulp.dest("build/lib"));
});
/*创建一个名为jsController的任务,先调用名为jsLib的任务。主要用于编译controller文件*/
gulp.task('jsController', ['jsLib'], function() {
/*gulp.src此处扫描的controller文件夹似乎不存在,有可能是路径写错了,暂时不做修改
* 下面步骤跟上一个jsLib的执行步骤一抹一样,不做赘述*/
return gulp.src(['src/js/controller/**/*.js'])
.pipe(uglify({}))
.pipe(rev())
.pipe(gulp.dest('build/js/controller'))
.pipe(rev.manifest({
base: 'src/**',
merge: true
}))
.pipe(gulp.dest("build/jsController"));
});
/*创建一个名为rev的任务,先调用名为jsController的任务。主要用于根据上一步生成的rev-manifest.json文件中的映射关系,替换调html中的资源文件名称*/
gulp.task('rev', ['jsController'], function() {
/*gulp.src:扫描rev-manifest.json映射文件和所有src下的html文件
* revCollector({replaceReved: true,dirReplacements:{"/":"/"}}): 调用gulp-rev-collector插件,将扫描到的所有html文件中携带md5签名的静态资源的资源名称
* 按照rev-manifest.json中对应的关系,替换为最新生成的md5签名。
* replaceReved: 用来说明模板中已经被替换的文件是否还能再被替换
* dirReplacements:路径替换*/
return gulp.src(
[
'rev-manifest.json',
'src/**/*.html',
])
.pipe(revCollector({
replaceReved: true,
dirReplacements: {
'/': '/',
}
}))
.pipe(gulp.dest('build'));
});
/*创建一个名为cleanSurplus的任务,用来将md5签名替换完成后留下来的rev-manifest.json等临时文件删掉,任务的执行顺序是 先替换再删除*/
gulp.task('cleanSurplus', ['rev'], function() {
/*gulp.src:扫描到这两个临时文件,不读取文件内容
* clean():调用gulp-clean插件 删除扫描到的文件*/
return gulp.src(['rev-manifest.json', 'src/config.js'], { read: false })
.pipe(clean());
});
/*创建一个名为 lib的任务,复制src/lib文件夹到build文件夹下*/
gulp.task('lib', function() {
return gulp.src(['src/lib/**'])
.pipe(copy('build/', { prefix: 1 }));
});
/*创建一个名为build_index_and_js的任务,主要用来根据命令行参数判断启动的是正式环境还是测试环境*/
gulp.task('build_index_and_js', function() {
/*将正式环境、测试环境和开发环境的路径定义到map中
* argv.env:调用argv的env参数 获取命令行中设置的env的值。在命令行中输入env dev,就相当于给env这个key设置了value=dev
* fs.copy:调用fs-sync插件的copy方法,同步的将configFile变量指定的路径下的文件复制到src下的config文件,如果config.js已经存在,则覆盖它*/
var envConfigMap = {
'production': 'src/config.production.js',
'staging': 'src/config.staging.js',
'dev': 'src/config.dev.js'
};
var configFile = envConfigMap[argv.env];
fs.copy(configFile, 'src/config.js');
});
相信看完上面这个非常非常非常详细的注释,对于gulp的执行原理、插件作用和执行顺序都了然于胸了。
然后我们启动项目:
- 到项目目录下,打开命令行输入:gulp watch --env dev
(gulp watch --env dev 的命令的通俗意义是:调用gulp中构建的名为watch的这个任务,并且设置一个参数,参数的名称是env,参数的值是dev。eg:有个方法定义时:watch(env){},调用时:this.watch("dev") )
执行完上面这个命令后程序即开始编译,编译过程中,build目录会自动根据src目录自动生成,编译完成后就会自动打开默认浏览器显示出页面。