接着上篇的《 Node.js 自动化工具 - Bower 》我们开始学习另一个强大的自动化工具吧!
打开它的官网 Gulp 中文网,当我点开它的那刻是激动的,居然有中文!浏览完网站后,风格简洁呀!对,不止 display 简洁,文档也真的是很简洁,本来以为能轻松学习的,没想到后面还是要补充很多其他知识点 -_-||
“ 一个服务于前端的管道式构建系统 ”
自动化的构建项目,实现 JavaScript 和 CSS 的版本与依赖问题,文件合并,文件压缩,单元测试等。
易于使用
通过代码优于配置的策略,Gulp 让简单的任务简单,复杂的任务可管理。
构建快速
利用 Node.js 流的威力,你可以快速构建项目并减少频繁的 IO 操作。
插件高质
Gulp 严格的插件指南确保插件如你期望的那样简洁高质得工作。
易于学习
通过最少的 API,掌握 Gulp 毫不费力,构建工作尽在掌握:如同一系列流管道。
这上面是官网给予的 Gulp 四个特点,我们来一点一点的解释一下:
1. 易于使用
到底有多简单?看看官网的入门指南,短短四行说明完毕。确实 Gulp 整个的任务逻辑十分简单明了
// 定义插件的引入
var gulp = require('gulp');
// 创建任务
gulp.task('default', function() {
// 将你的默认的任务代码放在这
});
这一切的一切就基于这两步形成的,很符合我们敲代码的中心思想 , “ 一切为了优雅 ” 。
2. 构建快速
看看这个说明画重点,流 \ IO操作 没错这个就是 Gulp 与 Grunt 的最大差别,也是我什么挑 Gulp 的原因。
举个例子,比如我们要对某个 JS 文件进行校验,压缩,混淆,我们工作流大致是这样的:
由上图就可以看出,Grunt 与 Gulp 的不同:
-
Grunt 每次处理完一个任务(task)都会产生一个临时文件,下个任务开始前再读取临时文件执行,循环如此,直至输出,这其中的读写操作就是: IO操作 。
-
Gulp 就跟水流一样从一个 task 到另一个 task 直至输出,这种模式就是我们现在常用的 流模式,跟视频流,图片流有异曲同工之妙。
// 这个就是控制流中最重要的管道
.pipe()
3. 插件高质
这个呢,我也不太好说,他说高就高吧,毕竟哪里都有高手又不是只有你家有。除了 Gulp 的 插件搜索页 比较好看之外我也不知道差别在哪里,反正最后都跳到 npm 。
4. 易于学习
相对 Grunt 十个类型的,大量的 API 和命令来看,Gulp 更像集天下武功于大乘,以四个 API 就能掌控雷电,毕竟一招 “ 乾坤大挪移 ” 就够它吃尽大半武林了。
// 输入口
gulp.src();
// 输出口
gulp.dest();
// 任务
gulp.task();
// 监察
gulp.watch()
通过上面我们已经可以基本了解到 Gulp 具体是什么样的了,现在动手学习吧~
安装
首先我们在全局安装,方便调用
$ cnpm install --global gulp
接着我们在项目作为依赖再安装一遍
$ cnpm install --save-dev gulp
为什么要在项目中再装一遍呢?这是为了避免每个项目合作者本地的 Gulp 版本不一导致的构建失败,在项目中规定了 Gulp 版本可以有效的避免这样情况。
现在我们假定执行 JS 的文件压缩任务,它依赖的插件为:gulp-uglify
我们继续按照这个插件并依赖到项目 package 中
$ cnpm install --save-dev gulp-uglify
task编写
创建 gulpfile.js 文件,编写 task 用来告诉 Gulp 我们需要完成怎么样的任务
手动创建不够装逼,补充下小知识用命令行创建:
- 我们知道命令 mkdir 能够创建一文件夹,那创建文件要怎样呢?
- 输入命令
$ cd.>gulpfile.js
这样就可以创建出 gulpfile.js 文件了,至于原理是什么?自己去查 Dos 命令吧~
好了,进行到这一步项目结构大概这样(多删少补):
gulp/
├── src/
│ └── app.js (自己随便敲点 js 代码)
├── dist/
├── node_modules/
├── gulpfile.js/
└── package.json
开打 gulpfile.js 输入
var gulp = require('gulp'), // 引入 gulp
uglify = require('gulp-uglify'); // 引入 gulp-uglify 插件
gulp.task('minjs', function () { // task 任务定义,'minjs' 自定义任务名
gulp.src('src/app.js') // src 定义任务文件
.pipe(uglify()) // .pipe() 链式编码,管道过程执行任务
.pipe(gulp.dest('dist')); // dest() 定义成型文件输入地址
});
运行task
$ gulp minjs
可以看到 dist 文件中已经生成了压缩好的 app.js 文件。
这就是最基本的 Gulp 工作流程,接下来我们研究其 API 深入学习各项配置功能。
API
1. gulp.src(globs[, options])
管道入口,两个参数 [ globs , options ]
-
globs
可直接写文件路径指定执行文件,但是这样做只能匹配单独文件。在具体的应用场景中我们通常需要匹配特定规则的文件以自动化执行任务,这时候我们就需要 globs 来完成这项任务。 -
globs 的使用
首先它是属于 node.js 的一个 插件 ,Gulp 中能直接应用,而不需要再次安装,这是它的 GitHub 地址 node-glob,里面有详细的用法介绍。我下面挑一些实用例子说明一下:
字符 | 官档说明 | 解释 |
---|---|---|
* | Matches 0 or more characters in a single path portion | 匹配文件路径中的零个或多个字符(不会匹配路径分隔符) |
? | Matches 1 character | 匹配文件路径中的一个字符(不会匹配路径分隔符) |
[…] | Matches a range of characters, similar to a RegExp range. If the first character of the range is ! or ^ then it matches any character not in the range. | 匹配方括号中出现的字符中的任意一个,当方括号中第一个字符为 ^ 或 ! 时,则表示不匹配方括号中出现的其他字符中的任意一个 |
! | (pattern / pattern / pattern) Matches anything that does not match any of the patterns provided. | 匹配任何与括号中给定的任一模式都不匹配的字符 |
? | (pattern / pattern / pattern) Matches zero or one occurrence of the patterns provided. | 匹配括号中给定的任一模式0次或1次 |
+ | (pattern / pattern / pattern) Matches one or more occurrences of the patterns provided. | 匹配括号中给定的任一模式至少1次 |
* | (a / b / c) Matches zero or more occurrences of the patterns provided | 匹配括号中给定的任一模式0次或多次 |
@ | (pattern / pat* / pat?erN) Matches exactly one of the patterns provided | 匹配括号中给定的任一模式1次 |
** | If a “globstar” is alone in a path portion, then it matches zero or more directories and subdirectories searching for matches. It does not crawl symlinked directories. | 匹配路径中的0个或多个目录及其子目录,需单独出现,混合出现时将不能匹配多级目录。 |
下面以一系列例子来加深理解
* 能匹配 a.js,x.y,abc,abc/,但不能匹配a/b.js
*.* 能匹配 a.js,style.css,a.b,x.y
*/*/*.js 能匹配 a/b/c.js,x/y/z.js,不能匹配a/b.js,a/b/c/d.js
** 能匹配 abc,a/b.js,a/b/c.js,x/y/z,x/y/z/a.b,能用来匹配所有的目录和文件
**/*.js 能匹配 foo.js,a/foo.js,a/b/foo.js,a/b/c/foo.js
a/**/z 能匹配 a/z,a/b/z,a/b/c/z,a/d/g/h/j/k/z
a/**b/z 能匹配 a/b/z,a/sb/z,但不能匹配a/x/sb/z,因为只有单**单独出现才能匹配多级目录
?.js 能匹配 a.js,b.js,c.js
a?? 能匹配 a.b,abc,但不能匹配ab/,因为它不会匹配路径分隔符
[xyz].js 只能匹配 x.js,y.js,z.js,不会匹配xy.js,xyz.js等,整个中括号只代表一个字符
[^xyz].js 能匹配 a.js,b.js,c.js等,不能匹配x.js,y.js,z.js
//使用数组的方式来匹配多种文件
gulp.src(['js/*.js','css/*.css','*.html'])
gulp.src([*.js,'!b*.js']) //匹配所有js文件,但排除掉以b开头的js文件
gulp.src(['!b*.js',*.js]) //不会排除任何文件,因为排除模式不能出现在数组的第一个元素中
// 展开模式
a{b,c}d 会展开为 abd,acd
a{b,}c 会展开为 abc,ac
a{0..3}d 会展开为 a0d,a1d,a2d,a3d
a{b,c{d,e}f}g 会展开为 abg,acdfg,acefg
a{b,c}d{e,f}g 会展开为 abdeg,acdeg,abdeg,abdfg
—— 该例子来自 无双 博客 《 前端构建工具gulpjs的使用介绍及技巧 》
- options
后面的 { base: ‘client’ } 就是 options 其中一个的配置项
options | 类型 | 默认值 | 说明 | 备注 |
---|---|---|---|---|
options.buffer | Boolean | true | 如果该项被设置为 false,那么将会以 stream 方式返回 file.contents 而不是文件 buffer 的形式。这在处理一些大文件的时候将会很有用。**注意:**插件可能并不会实现对 stream 的支持 | 就说 false 时将返回文件目录形式而不是文件缓存形式 |
options.read | Boolean | true | 如果该项被设置为 false, 那么 file.contents 会返回空值(null) | 就说是并不会去读取文件 |
options.base | String | 将会加在 glob 之前 | 指定输出文件位置 | 就是说不是按原 glob 的路径,可以自己指定路径,具体看上图 |
2. gulp.dest(path[, options])
管道出口,两个参数 [ path , options ]
能在 pipe 中实现输出(emits)数据,在一个任务流中,你可以将其 pipe 到多个文件夹里。如果某文件夹不存在,将会自动创建它。
gulp.src('./client/templates/*.jade')
.pipe(jade())
.pipe(gulp.dest('./build/templates'))
.pipe(minify())
.pipe(gulp.dest('./build/minified_templates'));
-
path
类型: String or Function
文件将被写入的路径(输出目录)。也可以传入一个函数,在函数中返回相应路径,这个函数一般是利用 vinyl 这个插件。
同时我们只能指定路径并不能指定文件名,因为生成的文件名是由导入到它的文件流决定的。想改变文件名用 gulp-rename 实现。 -
vinyl
一个文件包含连个基本信息 【 路径 , 内容 】,vinyl 就是负责描述文件的基本属性的工具
var Vinyl = require('vinyl');
var jsFile = new Vinyl({
cwd: '/',
base: '/test/',
path: '/test/file.js',
contents: new Buffer('var x = 123')
});
var file = new File({
cwd: '/',
base: '/test/',
path: '/test/file.js'
});
console.log(file.extname); // .js
file.extname = '.txt';
console.log(file.extname); // .txt
console.log(file.path); // /test/file.txt
这里面的 API 跟用法比较多,我在这里就不累述了,开个番外篇 《 Gulp 插件 - vinyl 》
- options
options | 类型 | 默认值 | 说明 | 备注 |
---|---|---|---|---|
options.cwd | String | process.cwd() | 输出目录的 cwd 参数,只在所给的输出目录是相对路径时候有效。 | 为下一个任务服务 |
options.mode | String | 0777 | 八进制权限字符,用以定义所有在输出目录中所创建的目录的权限 | 这里面就涉及了 linux 权限字符串,有兴趣看开番外篇《 八进制权限字符 》 |
- 输出例子
原则:生成的文件名是由导入到它的文件流决定的
由此我们可以得到以下规则:
序号 | 规则 | 例子 |
---|---|---|
1 | 没有通用匹配符 | gulp.src('src/app.js').pipe(gulp.dest('dist')); // 最后生成的文件路径为 dist/app.js' |
2 | 出现通配符 **/* | gulp.src('src/**/app.js').pipe(gulp.dest('dist')); // 最后生成的文件路径为 dist/js/app.js' |
3 | 当 gulp.src() 指定 base 值时 | gulp.src(script/lib/*.js).pipe(gulp.dest('build')) // 最后生成的文件路径为 build/jquery.js / gulp.src(script/lib/*.js, {base:'script'}) .pipe(gulp.dest('build')) // 最后生成的文件路径为 build/lib/jquery.js |
3. gulp.task(name[, deps], fn)
它是基于 orchestrator 实现,具体看看它的 Github 吧;
-
name
规定 task 的名字,当其值为 “default” 时则为默认 task ,可直接$ gulp
执行; -
deps
类型为数组,是当前任务的依赖任务列表,即在执行该任务前会,先执行数组中的任务。但其都是按异步方式执行的,即不会等待依赖任务完成再执行下一个任务,若任务间不存在依赖无妨,若存在依赖需要利用使 callback,或者返回一个 promise 或 stream。
注意: 默认的,task 将以最大的并发数执行,也就是说,gulp 会一次性运行所有的 task 并且不做任何等待。如果你想要创建一个序列化的 task 队列,并以特定的顺序执行,你需要做两件事:
- 给出一个提示,来告知 task 什么时候执行完毕
- 并且再给出一个提示,来告知一个 task 依赖另一个 task 的完成
我们先看看官网给的例子
var gulp = require('gulp');
// 返回一个 callback,因此系统可以知道它什么时候完成
gulp.task('one', function(cb) {
// 做一些事 -- 异步的或者其他的
cb(err); // 如果 err 不是 null 或 undefined,则会停止执行,且注意,这样代表执行失败了
});
// 定义一个所依赖的 task 必须在这个 task 执行之前完成
gulp.task('two', ['one'], function() {
// 'one' 完成后
});
gulp.task('default', ['one', 'two']);
其他部分都是司空见惯的,唯一不同是的,task one 中多了一个 cb() 这个就是通过 callback 实现告知 Gulp 该 task 执行完毕。还有另外两种方法:[ promise , stream ]
- promise
var Q = require('q');
gulp.task('somename', function() {
var deferred = Q.defer();
// 执行异步的操作
setTimeout(function() {
deferred.resolve();
}, 1);
return deferred.promise;
});
promise 是 ECMAScript 6 发布用来传递异步操作的 API 这里的具体我就不说了,可以参考阮老师的文章 《 阮一峰的ES6 教程 》,后续有时间再自己为 ES6 填坑吧。
- stream
就我个人来说,我比较推荐用 stream 来处理队列
gulp.task('somename', function() {
var stream = gulp.src('client/**/*.js')
.pipe(minify())
.pipe(gulp.dest('build'));
return stream;
});
为什么?看看上面那段代码,一点违和感都没有呀,多适合 Gulp 的风格,多优雅!
4. gulp.watch(glob [, opts], tasks) 或 gulp.watch(glob [, opts, cb])
监视文件,并且可以在文件发生改动时候做一些事情。它总会返回一个 EventEmitter 来发射(emit) change 事件。
- gulp.watch(glob[, opts], tasks)
前面两个参数 [ glob , opts ] 跟 gulp.src 没有什么不同,后面跟一个监控的 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[, opts, cb])
监控文件是否发生改变,并执行对应函数
gulp.watch('js/**/*.js', function(event) {
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});
解释一下上面两个方法中的 function(event) 中的 event 对象,这个对象描述了所监控到的变动。
参数 | 类型 | 事件 |
---|---|---|
event.type | String | 发生的变动的类型:added, changed 或者 deleted |
event.path | String | 触发了该事件的文件的路径 |
这上面就是 Gulp 全部的用法了, 剩下就是其插件的探讨,我列举一些常用的吧~
插件介绍
具体用法自己点击链接研究
插件 | 介绍 | 安装 | 用法 |
---|---|---|---|
gulp-uglify | js文件压缩 | $ npm install --save-dev gulp-uglify | .pipe(uglify()) |
gulp-rename | 文件重命名 | $ npm install --save-dev gulp-rename | .pipe(rename('jquery.min.js')) |
gulp-minify-html | css文件压缩 | $ npm install --save-dev gulp-minify-html | .pipe(minifyHtml()) |
gulp-jshint | js代码检查 | $ npm install --save-dev jshint gulp-jshint | .pipe(jshint()) 它依赖于 jshint ,再安装时需要注意 |
gulp-concat | 文件合并 | $ npm install --save-dev gulp-concat | .pipe(concat('all.js')) |
gulp-imagemin | 图片压缩 | $ npm install --save-dev gulp-imagemin | .pipe(imagemin({ progressive: true, use: [pngquant()] })) //使用pngquant来压缩png图片,具体见 npm |
gulp-livereload | 自动刷新 | $ npm install --save-dev gulp-livereload | gulp.task('watch', function() { livereload.listen(); gulp.watch('less/*.less', ['less']); }); // 可用 Chrome 的 livereload chrome extension 辅助实现 |
gulp-load-plugins | 自动加载插件 | $ npm install --save-dev gulp-load-plugins | 类似于 require.js 的 gulp 插件管理 |
好啦,基本上 Gulp 的学习就是到这里了
下篇我们来学习《 Node.js 自动化工具 - webpack 》
该篇收录于文集:Node教程