自动化工具gulp
当下的前端开发
- 不再是简单的使用HTML+CSS+js 这些技术构建网页应用
- 我们要提高效率,就必须减少重复的工作
- 使用less之类预处理的css coffeescript
- 提供开发阶段的便利,开发阶段更快捷
- 现在的开发行业优质的开发人员是不应该将精力放在这些重复性的工作上
- gulp就是一种可以自动化完成我们开发过程中大量的重复工作
- 预处理语言的编译
- js css html 压缩混淆
- 图片体积优化
- 除gulp之外还有类似的自动化工具
- grunt
- webpack
gulp的介绍
用自动化构建工具增强你的工作流程!
- 当下最流行的的自动化工具
+ 什么是自动化工具?
+ 自动完成一系列重复的操作
- 链接:
+ 官网;
+ 中文网;
易于使用
通过代码优于配置的策略,Gulp 让简单的任务简单,复杂的任务可管理。
构建快速
利用 Node.js 流的威力,你可以快速构建项目并减少频繁的 IO(input,output) 操作
插件高质
Gulp 严格的插件指南确保插件如你期望的那样简洁高质得工作
易于学习
通过最少的 API,掌握 Gulp 毫不费力,构建工作尽在掌握:如同一系列流管道。
gulp的安装
- 全局安装gulp
npm install --global gulp
// 换镜像
npm config set registry https://registry.npm.taobao.org
- 作为项目的开发依赖(devDependencies)安装
npm install --save-dev gulp
- 在项目根目录下创建一个名为gulpfile.js的文件
var gulp = require('gulp');
gulp.task('default',function(){
// 将你的默认的任务代码放在这
});
- 运行gulp
gulp
注意
默认的名为default的任务将会被运行,在这里,这个任务并未做任何事情。
想要单独执行特定的任务(task),请输入gulp <task> <othertask>
gulp的应用
gulp 是用来帮助我们执行一些重复操作的
一般我们将这些重复的操作划分为不同的任务
如何定义一个 任务
- 第一个参数是任务名
- 第二个参数是任务执行体
gulp.task('hello',function(){
console.log(hello kkcode);
});
运行任务
gulp hello
简单的gulpfile.js文件
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');
var paths = {
script:['js/index.js','js/main.js']
}
gulp.task('default',function(){
// paths 也可以正则匹配,如: 'js/*.js'
gulp.src(paths)
.pipe(uglify)
.pipe(concat('all.min.js'))
.pipe(gulp.dest('build'));
});
gulp-api
简单说来gulp原本不支持任何功能,只提供最基础的Api(4个)。
他们分别是gulp.src, gulp.dest, gulp.task, gulp.watch。
因此,想要简单的使用gulp很容易,但是想要将gulp使用到得心应手的地步并不是一件简单的事情。
而其中最关键的地方在于,对nodejs文件路径匹配模式globs的理解。
- gulp.task创建一个任务
- gulp.src表示创建的任务是针对文件目录中的那些位置
- gulp.dest表示目标文件被处理完毕之后会在哪个位置重新生成
- gulp.watch表示监听那些位置文件的变化
总结:除了task,其他三个api都与文件路径密切相关,
而gulp.src与gulp.watch更是直接以golbs为第一个参数,
gulp的各个插件需要知道自己处理的是那些位置的文件,而golbs则负责指定需要被处理文件的位置
每个人对前端的理解有所差异,造成了前端的项目结构也是各不一样,
因此golbs的配置也就显得更加重要 下面就先了解一下golbs的匹配规则。
Glob-Primer
匹配文件中0个或者多个字符,但是不会匹配路径中的分隔符,除非路径分隔符出现在末尾
// 匹配src目录下所有的js文件
src/*.js
// 匹配src目录下所有的文件
src/*.*
// 只要层级相同,可以匹配任意目录下的任意js文件 比如src/a/b.js
src/*/*.js
匹配文件路径中的0个或者多个层级的目录,需要单独出现,如果出现在末尾,也可匹配文件
// 递归获取src目录下的所有文件及目录及目录下的所有文件
src/**/*.*
如可以匹配到
src/index.html
src/js
src/js/a.js
src/images
src/images/c.png
? 匹配一个字符,不会匹配路径分隔符
// 能匹配文件名只有一个字符的js文件,如a.js, b.js ,但不能匹配文件名为2个字符及其以上的js的文件
src/js/?.js
如可以匹配到:
src/js/a.js
src/js/b.js
不可以匹配到:
src/js/aa.js
src/js/bb.js
[…] 由多个规则组成的数组,可以匹配数组中符合任意一个子项的文件,当子项中第一个字符为!或者^时,表示不匹配该规则
// 匹配src目录下的a0.js, a1.js, a2.js, a3.js
'src/a[0-3].js'
// 除开node_modules目录之外,匹配项目根目录下的所有html文件
['./**/*.html', '!node_modules/**']
{…} 展开模式,根据里面的内容展开为多个规则,能匹配所有展开之后的规则
// 除开build,simple,images,node_modules目录,匹配根目录下所有的html与php文件
['./**/*.{html, php}', '!{build, simple, images, node_modules}/**']
!(pattern|pattern|pattern) 每一个规则用pattern表示,这里指排除符合这几个模式的所有文件
// 匹配排除文件名为一个字符的js文件,以及排除jquery.js之后的所有js文件
'src/js/!(?|jquery).js'
// 排除build与node_modules目录,并排除其他目录下以下划线_开头的html与php文件,匹配其余的html与php文件
// 这种比较复杂的规则在实际开发中会常常用到,需要加深了解
['src/**/!(_)*.{html, php}', '!{build, node_modules}/**']
?(pattern|pattern|pattern) 匹配括号中给定的任一模式0次或者1次
// 匹配src/js目录下的a.js, a2.js, b.js
// 不能组合
// 匹配0次或者1次
'scr/js/?(a|a2|b).js'
@(pattern|pattern|pattern) 匹配多个模式中的任一个
// 匹配src/js目录下的a.js, b.js, c.js
// 不能组合
// 匹配一次,不能为空,注意与?的区别
'./src/js/@(a|b|c).js'
+(pattern|pattern|pattern) 匹配括号中给定任一模式1次或者多次,这几个模式可以组合在一起匹配
// 可以匹配src/js目录下的a.js, a2.js, b.js
// 也可以匹配他们的组合 ab.js, aa2.js, a2b.js等
// 至少匹配一次,为空不匹配
'./src/js/+(a|a2|b).js'
*(pattern|pattern|pattern) 匹配括号中给定任一模式0次或者多次,这几个模式可以组合在一起匹配
// 可以匹配src/js目录下的a.js, b.js, c.js
// 也可以匹配他们的组合 ab.js, bc.js, ac.js
// 匹配0次或者多次
'./src/js/*(a|b|c).js'
几乎能用到的规则就都在上面了,在实际使用的时候,我们需要根据自己的实际情况来使用.
只要掌握了globs模式,距离精通gulp也不太远了。
gulp.src(globs[, options])
在介绍这个API之前我们首先来说一下Grunt.js和Gulp.js工作方式的一个区别。
Grunt主要是以文件为媒介来运行它的工作流的,比如在Grunt中执行完一项任务后,会把结果写入到一个临时文件中,
然后可以在这个临时文件内容的基础上执行其它任务,执行完成后又把结果写入到临时文件中,
然后又以这个为基础继续执行其它任务…就这样反复下去。
而在Gulp中,使用的是Nodejs中的stream(流),首先获取到需要的stream,
然后可以通过stream的pipe()方法把流导入到你想要的地方,
比如Gulp的插件中,经过插件处理后的流又可以继续导入到其他插件中,当然也可以把流写入到文件中
。所以Gulp是以stream为媒介的,它不需要频繁的生成临时文件,这也是Gulp的速度比Grunt快的一个原因。
再回到正题上来,gulp.src()方法正是用来获取流的,但要注意这个流里的内容不是原始的文件流,而是一个虚拟文件对象流(Vinyl files),
这个虚拟文件对象中存储着原始文件的路径、文件名、内容等信息,这个我们暂时不用去深入理解。gulp.src指定gulp任务的目标文件位置 它的参数中,第一个参数globs为必选。
中括号表示可选参数,options为一个配置json对象 globs用于指定目标文件的位置,
在上面已经做过介绍,这里对option做一个简单的介绍即可
gulp.src('./**/!(_).{php, html}', {
buffer: true,
read: true,
base: './'
})
options.buffer
类型: Boolean 默认:true 如果该项被设置为false,那么将会以stream方式返回file.contents,而不是文件buffer的形式。 这在处理一些大文件的时候非常有用。 另外需要注意点是,gulp插件可能不支持stream
options.read
类型: Boolean 默认: true 如果被设置为false,那么file.contents会返回空值(null) 也就是并不会去读取文件
options.base
类型: String 默认值: 第一个参数目录中,glob匹配模式之前的路径 理解base至关重要,它将影响到文件处理完毕后,新文件生成的目录 我们需要结合gulp.dest来理解它。
比如在一个路径为static/scripts/libs的目录中,有一个js组件叫做dialog.js
// 匹配static/scripts/libs/dialog.js,并将base解析成 static/scripts
gulp.src('static/scripts/**/*.js')
// pipe() 让文件流顺着管道向下流,支持‘点’操作符
.pipe(minify())
// 处理完毕的文件,将会用build替换掉base,即为 build/libs/dialog.js
.pipe(gulp.dest('build'));
// 手动设置base的值
gulp.src('static/scripts/**/*.js', { base: 'static' })
.pipe(minify())
// 使用build替换base,结果为 build/scripts/libs/dialog.js
.pipe(gulp.dest('build'));
gulp.dest(path[, options])
// 拷贝文件
gulp.task('dest',function(){
// 获取src目录下的所有html文件拷贝到dist目录下
gulp.src('src/*.html').pipe(gulp.dest('dist/'));
});
// default
gulp.task('default',function(){
// 监听,如若src目录下文件发生变化则执行dest任务
gulp.watch('src/*',['dest']);
});
// command line
gulp default
gulp.task(name[, deps], fn)
- name 类型:string 任务的名字,如果你需要在命令行中运行你的某些任务,那么不要在任务名字中使用空格
- deps 类型: array 一个包含任务列表的数组,这些任务会在你当前任务运行之前完成
定义gulp任务
gulp.task('somename', function() {
// dosomething
})
gulp.task('mytask', ['array', 'of', 'task', 'names'], function() {
// dosomething
//请一定要确保你所依赖的任务列表中的任务都使用了正确的异步执行方式:
// 使用一个 callback,或者返回一个 promise 或 stream。
})
- fn 该函数定义任务所需要执行的一些操作。通常来说,它会是这样中形式
gulp.src().pipe(someplugin())
异步任务支持 如果fn能够做到以下其中一点,就可以异步执行了
接受一个callback
// 在 shell 中执行一个命令
var exec = require('child_process').exec;
gulp.task('jekyll', function(cb) {
// 编译 Jekyll
exec('jekyll build', function(err) {
if (err) return cb(err); // 返回 error
cb(); // 完成 task
});
});
返回一个stream
gulp.task('somename', function() {
var stream = gulp.src('client/**/*.js')
.pipe(minify())
.pipe(gulp.dest('build'));
return stream;
});
或者返回一个promise
var Q = require('q');
gulp.task('somename', function() {
var deferred = Q.defer();
// 执行异步的操作
setTimeout(function() {
deferred.resolve();
}, 1);
return deferred.promise;
});
总结:默认的,task 将以最大的并发数执行,也就是说,gulp 会一次性运行所有的 task 并且不做任何等待。
如果你想要创建一个序列化的 task 队列,并以特定的顺序执行,你需要做两件事:
- 给出一个提示,来告知 task 什么时候执行完毕
- 并且再给出一个提示,来告知一个 task 依赖另一个 task 的完成。
对于这个例子,让我们先假定你有两个 task,”one” 和 “two”,并且你希望它们按照这个顺序执行:
a 在 “one” 中,你加入一个提示,来告知什么时候它会完成:可以再完成时候返回一个 callback,
或者返回一个 promise 或 stream,这样系统会去等待它完成。b 在 “two” 中,你需要添加一个提示来告诉系统它需要依赖第一个 task 完成。
因此,这个例子的实际代码将会是这样:
var gulp = require('gulp');
// 返回一个 callback,因此系统可以知道它什么时候完成
gulp.task('one', function(cb) {
// 做一些事 -- 异步的或者其他的
// 如果 err 不是 null 或 undefined,则会停止执行,且注意,这样代表执行失败了
cb(err);
});
// 定义一个所依赖的 task 必须在这个 task 执行之前完成
gulp.task('two', ['one'], function() {
// 'one' 完成后
});
gulp.task('default', ['one', 'two']);
gulp.watch(glob [, options], tasks) 或 gulp.watch(glob [, options, cb])
监视文件,并且可以在文件发生改动时候做一些事情。它总会返回一个 EventEmitter 来发射(emit) change 事件。
- gulp.watch(glob[, options], tasks)
glob与options略过。
tasks 类型: 数组 需要在文件变动后执行的一个或者多个通过 gulp.task() 创建的 task 的名字,
var watcher = gulp.watch('js/**/*.js', ['uglify','reload']);
watcher.on('change', function(event) {
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});
- gulp.watch(glob[, options, cb])
glob与options略过
cb(event) 类型: Function 每次变动需要执行的回调函数
gulp.watch('js/**/*.js', function(event) {
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});
callback 会被传入一个名为 event 的对象。这个对象描述了所监控到的变动:
event.type 类型: String 发生变动的行为类型: added, changed, deleted
event.path 类型: String 触发该事件的文件路径
一些常用的gulp插件
gulp的插件数量虽然没有grunt那么多,但也可以说是应有尽有了,下面列举一些常用的插件。
自动加载插件
如果我们要使用的插件非常多,可以使用
gulp-load-plugins
把插件自动加载进来
- 安装:
npm install --save-dev gulp-load-plugins
要使用的gulp插件比较多的情况下,我们的gulpfile.js文件开头可能就会是这个样子的:
var gulp = require('gulp'),
a = require('gulp-a'),
b = require('gulp-b'),
c = require('gulp-c'),
d = require('gulp-d'),
e = require('gulp-e'),
//更多的插件...
z = require('gulp-z');
虽然这没什么问题,但会使我们的gulpfile.js文件变得很冗长,看上去不那么舒服。
gulp-load-plugins插件正是用来解决这个问题。
gulp-load-plugins这个插件能自动帮你加载package.json文件里的gulp插件。
例如假设你的package.json文件里的依赖是这样的:
{
"devDependencies": {
"gulp": "~3.6.0",
"gulp-rename": "~1.2.0",
"gulp-ruby-sass": "~0.4.3",
"gulp-load-plugins": "~0.5.1"
}
}
然后我们可以在gulpfile.js中使用gulp-load-plugins来帮我们加载插件:
var gulp = require('gulp');
//加载gulp-load-plugins插件,并马上运行它
var plugins = require('gulp-load-plugins')();
然后我们要使用gulp-rename和gulp-ruby-sass这两个插件的时候,就可以使用plugins.rename和plugins.rubySass来代替了,也就是原始插件名去掉gulp-前缀,之后再转换为驼峰命名。
实质上gulp-load-plugins是为我们做了如下的转换
plugins.rename = require(‘gulp-rename’);
plugins.rubySass = require(‘gulp-ruby-sass’);
gulp-load-plugins并不会一开始就加载所有package.json里的gulp插件,而是在我们需要用到某个插件的时候,才去加载那个插件。
最后要提醒的一点是,因为gulp-load-plugins是通过你的package.json文件来加载插件的,所以必须要保证你需要自动加载的插件已经写入到了package.json文件里,并且这些插件都是已经安装好了的。
重命名
用来重命名文件流中的文件。用gulp.dest()方法写入文件时,文件名使用的是文件流中的文件名。
如果要想改变文件名,那可以在之前用gulp-rename
插件来改变文件流中的文件名。
- 安装:
npm install --save-dev gulp-rename
var gulp = require('gulp'),
rename = require('gulp-rename'),
uglify = require("gulp-uglify");
gulp.task('rename', function () {
gulp.src('js/jquery.js')
.pipe(uglify()) //压缩
.pipe(rename('jquery.min.js')) //会将jquery.js重命名为jquery.min.js
.pipe(gulp.dest('js'));
//关于gulp-rename的更多强大的用法请参考https://www.npmjs.com/package/gulp-rename
});
js文件压缩
用来压缩js文件,使用的是uglify引擎,插件
gulp-uglify
- 安装:
npm install --save-dev gulp-uglify
var gulp = require('gulp'),
uglify = require("gulp-uglify");
gulp.task('minify-js', function () {
gulp.src('js/*.js') // 要压缩的js文件
.pipe(uglify()) //使用uglify进行压缩,更多配置请参考:
.pipe(gulp.dest('dist/js')); //压缩后的路径
});
css文件压缩
要压缩css文件时可以使用插件
gulp-minify-css
- 安装:
npm install --save-dev gulp-minify-css
var gulp = require('gulp'),
minifyCss = require("gulp-minify-css");
gulp.task('minify-css', function () {
gulp.src('css/*.css') // 要压缩的css文件
.pipe(minifyCss()) //压缩css
.pipe(gulp.dest('dist/css'));
});
html文件压缩
用来压缩html文件的插件
gulp-minify-html
- 安装:
npm install --save-dev gulp-minify-html
var gulp = require('gulp'),
minifyHtml = require("gulp-minify-html");
gulp.task('minify-html', function () {
gulp.src('html/*.html') // 要压缩的html文件
.pipe(minifyHtml()) //压缩
.pipe(gulp.dest('dist/html'));
});
js代码检查
用来检查js代码的插件
gulp-jshint
- 安装:
npm install --save-dev gulp-jshint
var gulp = require('gulp'),
jshint = require("gulp-jshint");
gulp.task('jsLint', function () {
gulp.src('js/*.js')
.pipe(jshint())
.pipe(jshint.reporter()); // 输出检查结果
});
文件合并
用来把多个文件合并为一个文件,我们可以用它来合并js或css文件等,这样就能减少页面的http请求数了。
插件gulp-concat
- 安装:
npm install --save-dev gulp-concat
var gulp = require('gulp'),
concat = require("gulp-concat");
gulp.task('concat', function () {
gulp.src('js/*.js') //要合并的文件
.pipe(concat('all.js')) // 合并匹配到的js文件并命名为 "all.js"
.pipe(gulp.dest('dist/js'));
});
less和sass的编译
less使用
gulp-less
sass使用gulp-sass
- 安装:
npm install --save-dev gulp-less
- 安装:
npm install --save-dev gulp-sass
var gulp = require('gulp'),
less = require("gulp-less"),
sass = require("gulp-sass");
gulp.task('compile-less', function () {
gulp.src('less/*.less')
.pipe(less())
.pipe(gulp.dest('dist/css'));
});
gulp.task('compile-sass', function () {
gulp.src('sass/*.sass')
.pipe(sass())
.pipe(gulp.dest('dist/css'));
});
图片压缩
压缩jpg、png、gif等图片的插件
gulp-imagemin
安装:npm install –save-dev gulp-imagemin
var gulp = require('gulp');
var imagemin = require('gulp-imagemin');
var pngquant = require('imagemin-pngquant'); //png图片压缩插件
gulp.task('default', function () {
return gulp.src('src/images/*')
.pipe(imagemin({
progressive: true,
use: [pngquant()] //使用pngquant来压缩png图片
}))
.pipe(gulp.dest('dist'));
});
css自动添加前缀
gulp-autoprefixer
插件,自动can i use 检测css,然后自动添加前缀
- 安装:
npm install --save-dev gulp-autoprefixer
const gulp = require('gulp');
const autoprefixer = require('gulp-autoprefixer');
gulp.task('default', () =>
gulp.src('src/app.css')
.pipe(autoprefixer({
browsers: ['last 2 versions'],
cascade: false
}))
.pipe(gulp.dest('dist'))
);
文件名修改成hash值
gulp-rev
插件,文件名自动加hash值,来达到清除缓存的目的。
- 安装:
npm install gulp-rev --save-dev
const gulp = require('gulp');
var rev = require('gulp-rev');
gulp.task('rev',function(){
gulp.src(['./dist/**/*.css','./dist/**/*.js','./dist/**/*'],{base:'./'})
/* 转换成新的文件名 */
.pipe(rev())
.pipe(gulp.dest('./dist'))
/*收集原文件名与新文件名的关系*/
.pipe(rev.manifest())
// 将文件以json的形式存在当前项目下的 ./rev 目录下
.pipe(gulp.dest('./rev'));
});
替换文件路径插件
gulp-rev-collector
插件
- 安装:
npm install gulp-rev-collector --save-dev
const gulp = require('gulp');
var revCollector = require('gulp-rev-collector');
/* 使用这个模块,需要依赖rev任务,所以需要注入rev任务,如果不注入需要先执行rev任务 */
gulp.task('revCollector',['rev'],function(){
// 根据生成的json文件,去替换html里的路径
gulp.src(['./rev/*.json','./dist/*.html'])
.pipe(revCollector())
.pipe(gulp.dest('./dest'));
})
优化gulp命令
从上面看到,我们每每去执行一条任务,就要运行一条命令,这样显然是不合理的,所以我们需要进行优化。
优化的方式就是任务注入以及 return 的使用
var gulp = require('gulp');
var autoprefixer = require('gulp-autoprefixer');
var concat = require('gulp-concat');
var imagemin = require('gulp-imagemin');
var htmlmin = require('gulp-htmlmin');
var rev = require('gulp-rev');
var revCollector = require('gulp-rev-collector');
var uglify = require('gulp-uglify');
// 处理CSS
gulp.task('css', function () {
return gulp.src('./css/*.css', {base: './'})
.pipe(autoprefixer())
.pipe(gulp.dest('./dist'));
});
// 此处就不做CSS压缩的演示了,原理相同。
// 压缩js
gulp.task('js', function() {
return gulp.src('./js/*.js', {base: './'})
.pipe(uglify())
.pipe(gulp.dest('./dist'));
});
// 压缩图片
gulp.task('image', function () {
return gulp.src('./images/*', {base: './'})
.pipe(imagemin())
.pipe(gulp.dest('./dist'));
});
// 压缩html
gulp.task('html', function () {
return gulp.src('./*.html')
.pipe(htmlmin({
removeComments: true,
collapseWhitespace: true,
minifyJS: true
}))
.pipe(gulp.dest('./dist'));
});
// 生成hash文件名
gulp.task('rev',['css', 'js', 'image', 'html'], function () {
// 其中加!前缀的是表示过滤该文件
return gulp.src(['./dist/**/*', '!**/*.html'], {base: './dist'})
.pipe(rev())
.pipe(gulp.dest('./dist'))
.pipe(rev.manifest())
.pipe(gulp.dest('./rev'));
});
// 替换文件路径
gulp.task('revCollector',['rev'], function () {
// 根据生成的json文件,去替换html里的路径
return gulp.src(['./rev/*.json', './dist/*.html'])
.pipe(revCollector())
.pipe(gulp.dest('./dist'));
});
// gulp中默认以default为任务名称
gulp.task('default', ['revCollector']);
执行任务,在命令行中,我们只需要执行下面的命令
$ gulp
或者是
$ gulp default
参考
- 原文地址-gulp排除文件(文件路径匹配模式globs)
- 有的没有列出来,原文作者如有发现,请联系我添加。