说起来惭愧,一直用公司内部的工具,没有用这些红得发紫的东西。今天东抄西拼终于搞出第一个gulp应用。gulp是做什么的,好处在哪儿我不废话了。直入主题吧。
先在D盘下建立一个xxxx目录,然后打开控制台,直接将npm install gulp。 里面多出一个node_modules目录安装成功。
然后xxxx目录下面建一个src目录,里面建一个index.html文件,内容如下或你自己乱写一点东西,我们这个例子主要测试压缩html。
< title >TODO supply a title</ title > |
< meta name = "viewport" content = "width=device-width" > |
< p >本来是想写个如何编写gulp插件的科普文的,突然探究欲又发作了,于是就有了这篇东西。。。翻了下源码看了下< code >gulp.src()</ code >的实现,不禁由衷感慨:肿么这么复杂。。。</ p > |
< p >首先我们看下< code >gulpfile</ code >里面的内容是长什么样子的,很有express中间件的味道是不是~< br />我们知道< code >.pipe()</ code >是典型的流式操作的API。很自然的,我们会想到< code >gulp.src()</ code >这个API返回的应该是个Stream对象(也许经过层层封装)。本着一探究竟的目的,花了点时间把gulp的源码大致扫了下,终于找到了答案。</ p > |
< pre class = "hljs-dark" >< code class = "hljs javascript" >< span class = "hljs-keyword" >var gulp = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘gulp‘), |
preprocess = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘gulp-preprocess‘); |
gulp.task(< span class = "hljs-string" >‘default‘, < span class = "hljs-function" >< span class = "hljs-keyword" >function< span class = "hljs-params" >() { |
gulp.src(< span class = "hljs-string" >‘src/index.html‘) |
.pipe(preprocess({USERNAME:< span class = "hljs-string" >‘程序猿小卡‘})) |
.pipe(gulp.dest(< span class = "hljs-string" >‘dest/‘)); |
</ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ code ></ pre > |
< p >此处有内容剧透,如有对剧透不适者,请自行跳过本段落。。。</ p > |
< p >gulp.src() 的确返回了定制化的Stream对象。可以在github上搜索< code >ordered-read-streams</ code >这个项目。</ p > |
< p >大致关系是:< br />ordered-read-streams --> glob-stream --> vinyl-fs --> gulp.src()</ p > |
< pre class = "hljs-dark" >< code class = "hljs js" >< span class = "hljs-keyword" >var inst = < span class = "hljs-keyword" >new Gulp(); |
< span class = "hljs-built_in" >module.exports = inst; |
</ span ></ span ></ span ></ code ></ pre > |
< pre class = "hljs-dark" >< code class = "hljs js" >< span class = "hljs-keyword" >var vfs = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘vinyl-fs‘); |
< span class = "hljs-comment" >// 省略很多行代码 |
Gulp.prototype.src = vfs.src; |
</ span ></ span ></ span ></ span ></ code ></ pre > |
< pre class = "hljs-dark" >< code class = "hljs js" >< span class = "hljs-pi" >‘use strict‘; |
< span class = "hljs-built_in" >module.exports = { |
src: < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘./lib/src‘), |
dest: < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘./lib/dest‘), |
watch: < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘glob-watcher‘) |
</ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ code ></ pre > |
< p >vinyl-fs/lib/src/index.js</ p > |
< pre class = "hljs-dark" >< code class = "hljs js" >< span class = "hljs-keyword" >var defaults = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘lodash.defaults‘); |
< span class = "hljs-keyword" >var through = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘through2‘); |
< span class = "hljs-keyword" >var gs = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘glob-stream‘); |
< span class = "hljs-keyword" >var File = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘vinyl‘); |
< span class = "hljs-comment" >// 省略非重要代码若干行 |
< span class = "hljs-function" >< span class = "hljs-keyword" >function < span class = "hljs-title" >src< span class = "hljs-params" >(glob, opt) { |
< span class = "hljs-comment" >// 继续省略代码 |
< span class = "hljs-keyword" >var globStream = gs.create(glob, options); |
< span class = "hljs-comment" >// when people write to use just pass it through |
< span class = "hljs-keyword" >var outputStream = globStream |
.pipe(through.obj(createFile)) |
.pipe(getStats(options)); |
< span class = "hljs-keyword" >if (options.read !== < span class = "hljs-literal" >false) { |
outputStream = outputStream |
.pipe(getContents(options)); |
< span class = "hljs-comment" >// 就是这里了 |
< span class = "hljs-keyword" >return outputStream |
</ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ code ></ pre > |
< p >glob-stream/index.js</ p > |
< pre class = "hljs-dark" >< code class = "hljs js" >< span class = "hljs-keyword" >var through2 = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘through2‘); |
< span class = "hljs-keyword" >var Combine = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘ordered-read-streams‘); |
< span class = "hljs-keyword" >var unique = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘unique-stream‘); |
< span class = "hljs-keyword" >var glob = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘glob‘); |
< span class = "hljs-keyword" >var minimatch = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘minimatch‘); |
< span class = "hljs-keyword" >var glob2base = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘glob2base‘); |
< span class = "hljs-keyword" >var path = < span class = "hljs-built_in" >require(< span class = "hljs-string" >‘path‘); |
< span class = "hljs-comment" >// 必须省略很多代码 |
< span class = "hljs-comment" >// create 方法 |
create: < span class = "hljs-function" >< span class = "hljs-keyword" >function< span class = "hljs-params" >(globs, opt) { |
< span class = "hljs-comment" >// 继续省略代码 |
< span class = "hljs-comment" >// create all individual streams |
< span class = "hljs-keyword" >var streams = positives.map(< span class = "hljs-function" >< span class = "hljs-keyword" >function< span class = "hljs-params" >(glob){ |
< span class = "hljs-keyword" >return gs.createStream(glob, negatives, opt); |
< span class = "hljs-comment" >// then just pipe them to a single unique stream and return it |
< span class = "hljs-keyword" >var aggregate = < span class = "hljs-keyword" >new Combine(streams); |
< span class = "hljs-keyword" >var uniqueStream = unique(< span class = "hljs-string" >‘path‘); |
< span class = "hljs-comment" >// TODO: set up streaming queue so items come in order |
< span class = "hljs-keyword" >return aggregate.pipe(uniqueStream); |
</ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ code ></ pre > |
< p >ordered-read-streams/index.js</ p > |
< pre class = "hljs-dark" >< code class = "hljs js" >< span class = "hljs-function" >< span class = "hljs-keyword" >function < span class = "hljs-title" >OrderedStreams< span class = "hljs-params" >(streams, options) { |
< span class = "hljs-keyword" >if (!(< span class = "hljs-keyword" >this < span class = "hljs-keyword" >instanceof(OrderedStreams))) { |
< span class = "hljs-keyword" >return < span class = "hljs-keyword" >new OrderedStreams(streams, options); |
< span class = "hljs-keyword" >if (!< span class = "hljs-built_in" >Array.isArray(streams)) { |
options.objectMode = < span class = "hljs-literal" >true; |
Readable.call(< span class = "hljs-keyword" >this, options); |
< span class = "hljs-comment" >// stream data buffer |
< span class = "hljs-keyword" >this._buffs = []; |
< span class = "hljs-keyword" >if (streams.length === < span class = "hljs-number" >0) { |
< span class = "hljs-keyword" >this.push(< span class = "hljs-literal" >null); < span class = "hljs-comment" >// no streams, close |
< span class = "hljs-keyword" >return; |
streams.forEach(< span class = "hljs-function" >< span class = "hljs-keyword" >function < span class = "hljs-params" >(s, i) { |
< span class = "hljs-keyword" >if (!s.readable) { |
< span class = "hljs-keyword" >throw < span class = "hljs-keyword" >new < span class = "hljs-built_in" >Error(< span class = "hljs-string" >‘All input streams must be readable‘); |
s.on(< span class = "hljs-string" >‘error‘, < span class = "hljs-function" >< span class = "hljs-keyword" >function < span class = "hljs-params" >(e) { |
< span class = "hljs-keyword" >this.emit(< span class = "hljs-string" >‘error‘, e); |
}.bind(< span class = "hljs-keyword" >this)); |
< span class = "hljs-keyword" >var buff = []; |
< span class = "hljs-keyword" >this._buffs.push(buff); |
s.on(< span class = "hljs-string" >‘data‘, buff.unshift.bind(buff)); |
s.on(< span class = "hljs-string" >‘end‘, flushStreamAtIndex.bind(< span class = "hljs-keyword" >this, i)); |
}, < span class = "hljs-keyword" >this); |
</ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ code ></ pre > |
< p >兜兜转转一大圈,终于找到了< code >gulp.src()</ code >的源头,大致流程如下,算是蛮深的层级。代码细节神马的,有兴趣的同学可以深究一下。</ p > |
< p >ordered-read-streams --> glob-stream --> vinyl-fs --> gulp.src()</ p > |
好了,继续安装另一个插件gulp-htmlmin。照着readme安装就是,不过一下安装这么多依赖,黑压压一坨,着实吓人!
然后在xxxx目录下,建立一个gulpfile.js文件,内容直接抄gulp-htmlmin的readme:
var gulp = require( ‘gulp‘ ); |
var htmlmin = require( ‘gulp-htmlmin‘ ); |
gulp.task( ‘minify‘ , function () { |
.pipe(htmlmin({collapseWhitespace: true })) |
然后控制台运行gulp命令,报错,说什么“ Task ‘default‘ is not in your gulpfile”。只好求助谷歌,发现这个东西
var gulp = require( ‘gulp‘ ); |
var coffee = require( ‘gulp-coffee‘ ); |
gulp.task( ‘scripts‘ , function () { |
gulp.task( ‘watch‘ , function () { |
gulp.watch( ‘src/*.coffee‘ , [ ‘scripts‘ ]); |
gulp.task( ‘default‘ , [ ‘scripts‘ , ‘watch‘ ]); |
于是将原来的代码改装一下:
var gulp = require( ‘gulp‘ ); |
var htmlmin = require( ‘gulp-htmlmin‘ ); |
gulp.task( ‘minify‘ , function () { |
.pipe(htmlmin({collapseWhitespace: true })) |
gulp.task( ‘watch‘ , function () { |
gulp.watch( ‘src/*.html‘ , [ ‘minify‘ ]); |
gulp.task( ‘default‘ , [ ‘minify‘ , ‘watch‘ ]); |
运行gulp命令,生成dest目录,里面的index.html已经成功被压缩。并且有了watch任务,以后我们每次修改html,都会同步到dest中去。
估计default任务应该是类似C语言的main方法那样的东西,没有它是无法带动其他任务的。
接着我们好好学一下其基础吧。
gulp有5个基本方法:src、dest、task、run、watch
gulp.src()
gulp模块的src方法,用于产生数据流。它的参数表示所要处理的文件,一般有以下几种形式:
js/app.js
:指定确切的文件名
js/*.js
:某个目录所有后缀名为js的文件
js/**/*.js
:某个目录及其所有子目录中的所有后缀名为js的文件
!js/app.js
:除了js/app.js以外的所有文件
*.+(js|css)
:匹配项目根目录下,所有后缀名为js或css的文件
src方法的参数还可以是一个数组,用来指定多个成员:
gulp.src([ ‘js/**/*.js‘ , ‘!js/**/*.min.js‘ ]); |
gulp.dest()
gulp模块的dest方法,可以用来传送文件,同时写入文件到指定目录。可以重复的发送传递给它的数据,因此可以将文件传送到多个目录中。简单的例子:
gulp.src( ‘./client/templates/*.jade‘ ) |
.pipe(gulp.dest( ‘./build/templates‘ )) |
.pipe(gulp.dest( ‘./build/minified_templates‘ )); |
gulp.task()
gulp模块的task方法,用于定义具体的任务。它的第一个参数是任务名,第二个参数是任务函数。下面是一个非常简单的任务函数:
gulp.task( ‘greet‘ , function () { |
console.log( ‘Hello world!‘ ); |
task方法还可以指定按顺序运行的一组任务:
gulp.task( ‘build‘ , [ ‘css‘ , ‘js‘ , ‘imgs‘ ]); |
上面代码先指定build任务,它按次序由css、js、imgs三个任务所组成。注意:由于每个任务都是异步调用,所以没有办法保证js任务的开始运行的时间,正好是css任务运行结束时间。
如果希望各个任务严格按次序运行,可以把前一个任务写成后一个任务的依赖模块:
gulp.task( ‘css‘ , [ ‘greet‘ ], function () { |
上面代码表明,css任务依赖greet任务,所以css一定会在greet运行完成后再运行。
如果一个任务的名字为default,就表明它是“默认任务”,在命令行直接输入gulp命令,就会运行该任务:
gulp.task( ‘default‘ , function () { |
gulp.run()
gulp模块的run方法,表示要执行的任务。可能会使用单个参数的形式传递多个任务。注意:任务是尽可能多的并行执行的,并且可能不会按照指定的顺序运行:
gulp.run( ‘scripts‘ , ‘copyfiles‘ , ‘builddocs‘ ); |
gulp.run( ‘scripts‘ , ‘copyfiles‘ , ‘builddocs‘ , function (err) { |
可以使用gulp.run
在其他任务中运行任务。也可以在默认任务中使用gulp.run
组织多个更小的任务为一个大任务。
gulp.watch()
gulp模块的watch方法,用于指定需要监视的文件。一旦这些文件发生变动,就运行指定任务:
gulp.task( ‘watch‘ , function () { |
gulp.watch( ‘templates/*.tmpl.html‘ , [ ‘build‘ ]); |
上面代码指定,一旦templates目录中的模板文件发生变化,就运行build任务。
watch方法也可以用回调函数,代替指定的任务:
gulp.watch( ‘templates/*.tmpl.html‘ , function (event) { |
console.log( ‘Event type: ‘ + event.type); |
console.log( ‘Event path: ‘ + event.path); |
另一种写法是watch方法所监控的文件发生变化时(修改、增加、删除文件),会触发change事件,可以对change事件指定回调函数:
var watcher = gulp.watch( ‘templates/*.tmpl.html‘ , [ ‘build‘ ]); |
watcher.on( ‘change‘ , function (event) { |
console.log( ‘Event type: ‘ + event.type); |
console.log( ‘Event path: ‘ + event.path); |
除了change事件,watch方法还可能触发以下事件:
- end:回调函数运行完毕时触发。
- error:发生错误时触发。
- ready:当开始监听文件时触发。
- nomatch:没有匹配的监听文件时触发。
watcher对象还包含其他一些方法:
watcher.end()
:停止watcher对象,不会再调用任务或回调函数。
watcher.files()
:返回watcher对象监视的文件。
watcher.add(glob)
:增加所要监视的文件,它还可以附件第二个参数,表示回调函数。
watcher.remove(filepath)
:从watcher对象中移走一个监视的文件。
学完这些就可以到其官网上找插件了,毕竟插件才是王道。
转自:http://www.cnblogs.com/rubylouvre/p/4286638.html