感觉过gulp.js带来的兴奋事后,你须要的不只仅是它的光鲜,而是切切实实的实例。这篇文章讨论了一些使用gulp.js时常踩的坑,以及一些更加高级和定制化的插件和流的使用技巧。javascript
gulp的基本设置拥有很是友好的语法,让你可以很是方便的对文件进行转换:java
gulp.task('scripts', function() { return gulp.src('./src/**/*.js') .pipe(uglify()) .pipe(concat('all.min.js')) .pipe(gulp.dest('build/')); });
这种方式可以应付绝大多数状况,但若是你须要更多的定制,很快就会遇到麻烦了。这篇将介绍这其中的一些状况并提供解决方案。git
使用gulp时,你可能会陷入“流不兼容”的问题。这主要是由于常规流和Vinyl文件对象有差别,或是使用了仅支持buffer(不支持流)库的gulp插件与常规流不兼容。github
好比说,你不能直接将常规流与gulp和(或)gulp插件相连。咱们建立一个可读流,并尝试使用gulp-uglify和gulp-rename来进行转换,将最后获得的内容交给gulp.dest()
。下面就是个错误的例子:web
var uglify = require('gulp-uglify'), rename = require('gulp-rename'); gulp.task('bundle', function() { return fs.createReadStream('app.js') .pipe(uglify()) .pipe(rename('bundle.min.js')) .pipe(gulp.dest('dist/')); });
为何咱们不能将可读流和一个gulp插件直接相连?gulp难道不就是一个基于流的构建系统吗?是的,但上面的例子忽视了一个事实,gulp插件指望的输入是Vinyl文件对象。你不能直接将一个可读流与一个以Vinyl文件对象做为输入的函数(插件)相连正则表达式
gulp使用了vinyl-fs,它实现了gulp.src()
和gulp.dest()
方法。vinyl-fs使用vinyl文件对象——一种“虚拟文件格式”。若是咱们须要将gulp和(或)gulp插件与常规的可读流一块儿使用,咱们就须要先把可读流转换为vinyl。npm
使用vinyl-source-stream是个不错的选择,以下:gulp
var source = require('vinyl-source-stream'), marked = require('gulp-marked'); fs.createReadStream('*.md') .pipe(source()) .pipe(marked()) .pipe(gulp.dest('dist/'));
另一个例子首先经过browserify封装并最终将其转换为一个vinyl流:app
var browserify = require('browserify'), uglify = require('gulp-uglify'), source = require('vinyl-source-stream'); gulp.task('bundle', function() { return browserify('./src/app.js') .bundle() .pipe(source(‘bundle.min.js)) .pipe(uglify()) .pipe(gulp.dest('dist/')); });
哎呦不错哦。注意咱们再也不须要使用gulp-rename了,由于vinyl-source-stream建立了一个拥有指定文件名的vinyl文件实例(这样gulp.dest方法将使用这个文件名)函数
这个gulp方法建立了一个可写流,它真的很方便。它从新使用可读流中的文件名,而后在必要时建立文件夹(使用mkdirp)。在写入操做完成后,你可以继续使用这个流(好比:你须要使用gzip压缩数据并写入到其余文件)
既然你有兴趣使用gulp,这篇文章假设你已经了解了流的基础知识。不管是buffer仍是流,vinyl的虚拟文件都能包含在内。使用常规可读流时,你能够监听data事件来检测数据碎片的到来:
fs.createReadStream('/usr/share/dict/words').on('data', function(chunk) { console.log('Read %d bytes of data', chunk.length); }); > Read 65536 bytes of data > Read 65536 bytes of data > Read 65536 bytes of data > Read 65536 bytes of data > ...
不一样的是,使用gulp.src()
会将转换成buffer的vinyl文件对象从新写入到流中。也就是说,你得到的再也不是数据碎片,而是将内容转换成buffer后的(虚拟)文件。vinyl文件格式拥有一个属性来表示里面是buffer仍是流,gulp默认使用buffer:
gulp.src('/usr/share/dict/words').on('data', function(file) { console.log('Read %d bytes of data', file.contents.length); }); > Read 2493109 bytes of data
这个例子说明了在文件被完整加入到流以前数据会被转换成buffer。
尽管更加推荐使用流中的数据,但不少插件的底层库使用的是buffer。有时候必须使用buffer,由于转换须要完整的文件内容。好比文本替换和正则表达式的情形。若是使用数据碎片,将会面临匹配失败的风险。一样,像UglifyJS和Traceur Compiler须要输入完整的文件内容(至少须要语法完整的JavaScript字符串)
这就是为何gulp默认使用转换成buffer的流,由于这更好处理。
使用转换成buffer的流也有缺点,处理大文件时将很是低效。文件必须彻底读取,而后才能被加入到流中。那么问题来了,文件的尺寸多大才会下降性能?对于普通的文本文件,好比JavaScript、CSS、模板等等,这些使用buffer开销很是小。
在任何状况下,若是将buffer选项设为false,你能够告诉gulp流中传递的内容到底是什么。以下所示:
gulp.src('/usr/share/dict/words', {buffer: false}).on('data', function(file) { var stream = file.contents; stream.on('data', function(chunk) { console.log('Read %d bytes of data', chunk.length); }); }); > Read 65536 bytes of data > Read 65536 bytes of data > Read 65536 bytes of data > Read 65536 bytes of data > ...
因为所需的输入(输出)流和gulp插件不尽相同,你可能须要将流转换成buffer(反之亦然)。以前已经有过介绍,大多数插件使用buffer(尽管他们的一部分也支持流)。好比gulp-uglify和gulp-traceur。你能够经过gulp-buffer来转换成buffer:
var source = require('vinyl-source-stream'), buffer = require('gulp-buffer'), uglify = require('gulp-uglify'); fs.createReadStream('./src/app.js') .pipe(source('app.min.js')) .pipe(buffer()) .pipe(uglify()) .pipe(gulp.dest('dist/'));
或者另外一个例子:
var buffer = require('gulp-buffer'), traceur = require('gulp-traceur'); gulp.src('app.js', {buffer: false}) .pipe(buffer()) .pipe(traceur()) .pipe(gulp.dest('dist/'));
你也可使用gulp-streamify或gulp-stream将一个使用buffer的插件的输出转化为一个可读流。这样处理以后,跟在使用buffer的插件后面的(只能)使用流的插件也能正常工做了。
var wrap = require('gulp-wrap'), streamify = require('gulp-streamify'), uglify = require('gulp-uglify'), gzip = require('gulp-gzip'); gulp.src('app.js', {buffer: false}) .pipe(wrap('(function(){<%= contents %>}());')) .pipe(streamify(uglify())) .pipe(gulp.dest('build')) .pipe(gzip()) .pipe(gulp.dest('build'));
虽然已经有不少使用且方便的插件,不少任务以及转换能够不使用插件而轻易完成。插件会带来一些问题,你须要依赖一个额外的npm模块,一个插件接口和(反应迟钝?)的维护者,等等。若是一个任务能够不使用插件而使用原生模块就能轻易完成,绝大多数状况下,都建议不要使用插件。可以理解上面所说的概念,并可以在所处的状况下作出正确的决定,这点很是重要。下面来看一些例子:
以前的例子中,咱们已经直接使用了browserify,而不是使用(现已加入黑名单)gulp-browserify插件。这里的关键是使用vinyl-source-stream(或相似的库)进行加工,来将常规的可读流输入使用vinyl的插件。
另外一个例子就是基于字符串的变换。这里有一个很是基础的插件,直接使用了vinyl的buffer:
function modify(modifier) { return through2.obj(function(file, encoding, done) { var content = modifier(String(file.contents)); file.contents = new Buffer(content); this.push(file); done(); }); }
你能够像这样使用这个插件:
gulp.task('modify', function() { return gulp.src('app.js') .pipe(modify(version)) .pipe(modify(swapStuff)) .pipe(gulp.dest('build')); }); function version(data) { return data.replace(/__VERSION__/, pkg.version); } function swapStuff(data) { return data.replace(/(\w+)\s(\w+)/, '$2, $1'); }
这个插件并无完成,并且也不能处理流(完整版本)。然而,这个例子说明,能够很轻易地经过一些基本函数来建立新的变换。through2库提供了很是优秀的Node流封装,而且容许像上面那样使用转换函数。
若是你须要去运行一些定制化或动态的任务,了解gulp所使用的Orchestrator模块会颇有帮助。gulp.add
方法其实就是Orchestrator.add
方法(事实上全部的方法都是从Orchestrator继承而来的)。但为何你须要这个?
请注意,gulp(或grunt)并不老是当前情境下的最佳工具。好比说,若是你须要拼接并使用uglify压缩一系列的JavaScript文件,又或者你须要编译一些SASS文件,你可能须要考虑使用makefile或npm run,经过命令行来实现。减小依赖,减小配置,才是正解。
阅读经过npm run来实现任务自动化来了解更多信息。你须要明确经过一系列的“自定义构建”后须要获得什么,而哪一个工具最合适。
不过,我以为gulp是一个伟大的构建系统,我很喜欢使用它,它展示了Node.js中流的强大。