Node.js程序员我爱编程

Node.js 自动化工具 - Gulp

2017-06-30  本文已影响149人  Max_Law

接着上篇的《 Node.js 自动化工具 - Bower 》我们开始学习另一个强大的自动化工具吧!

打开它的官网 Gulp 中文网,当我点开它的那刻是激动的,居然有中文!浏览完网站后,风格简洁呀!对,不止 display 简洁,文档也真的是很简洁,本来以为能轻松学习的,没想到后面还是要补充很多其他知识点 -_-||

这次 logo 终于是不动物了

“ 一个服务于前端的管道式构建系统 ”
自动化的构建项目,实现 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 与 Gulp 的不同:

// 这个就是控制流中最重要的管道
.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 我们需要完成怎么样的任务

手动创建不够装逼,补充下小知识用命令行创建:

  • 输入命令
    $ 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
返回 task 执行时间为 10 ms

可以看到 dist 文件中已经生成了压缩好的 app.js 文件。
这就是最基本的 Gulp 工作流程,接下来我们研究其 API 深入学习各项配置功能。


API

1. gulp.src(globs[, options])

管道入口,两个参数 [ globs , options ]

字符 官档说明 解释
* 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'));
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.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 吧;

注意默认的,task 将以最大的并发数执行,也就是说,gulp 会一次性运行所有的 task 并且不做任何等待。如果你想要创建一个序列化的 task 队列,并以特定的顺序执行,你需要做两件事:

  1. 给出一个提示,来告知 task 什么时候执行完毕
  1. 并且再给出一个提示,来告知一个 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 ]

var Q = require('q');
gulp.task('somename', function() {
var deferred = Q.defer();
// 执行异步的操作
setTimeout(function() {
deferred.resolve();
}, 1);

return deferred.promise;
});

promise 是 [ECMAScript 6 ](http://www.ecma-international.org/publications/standards/Ecma-262.htm) 发布用来传递异步操作的 API 这里的具体我就不说了,可以参考阮老师的文章 [《 阮一峰的ES6 教程 》](http://es6.ruanyifeng.com/#docs/promise),后续有时间再自己为 [ES6](http://www.jianshu.com/p/8c98fc4bbbd2) 填坑吧。

- 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](https://www.npmjs.com/package/gulp-uglify) | js文件压缩 | `$ npm install --save-dev gulp-uglify` |  `.pipe(uglify())`
[gulp-rename](https://www.npmjs.com/packages/gulp-rename) | 文件重命名 | `$ npm install --save-dev gulp-rename` |  ` .pipe(rename('jquery.min.js'))`
[gulp-minify-html](https://www.npmjs.com/packages/gulp-minify-html) | css文件压缩 | `$ npm install --save-dev gulp-minify-html` |  `.pipe(minifyHtml())`
[gulp-jshint](https://www.npmjs.com/package/gulp-jshint) | js代码检查 | `$ npm install --save-dev jshint gulp-jshint` |  `.pipe(jshint())` 它依赖于 jshint ,再安装时需要注意
[gulp-concat](https://www.npmjs.com/package/gulp-concat) | 文件合并 | `$ npm install --save-dev gulp-concat` |  `.pipe(concat('all.js'))`
[gulp-imagemin](https://github.com/sindresorhus/gulp-imagemin) | 图片压缩 | `$ npm install --save-dev gulp-imagemin` |  ` .pipe(imagemin({ progressive: true, use: [pngquant()]  }))  //使用pngquant来压缩png图片,具体见 npm `
[gulp-livereload](https://github.com/vohof/gulp-livereload) | 自动刷新 | `$ npm install --save-dev gulp-livereload` |  `gulp.task('watch', function() { livereload.listen(); gulp.watch('less/*.less', ['less']); });`  // *可用 Chrome 的 [livereload chrome extension](https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei) 辅助实现*
[gulp-load-plugins](https://www.npmjs.com/package/gulp-load-plugins) | 自动加载插件 | `$ npm install --save-dev gulp-load-plugins` |  类似于 require.js 的 gulp 插件管理

好啦,基本上 Gulp 的学习就是到这里了
下篇我们来学习[《 Node.js 自动化工具 - webpack 》](http://www.jianshu.com/p/8c98fc4bbbd2)

-----------------------------------------------------------------------------------------
该篇收录于文集:[Node教程](http://www.jianshu.com/nb/11262966)
上一篇下一篇

猜你喜欢

热点阅读