前端开发近两年工程化大幅飙升。随着Nodejs
大放异彩,静态文件处理再也不须要其余语言辅助。主要的两大工具即为基于文件的grunt
,基于流的gulp
。简单来讲,若是须要的只是文件处理,gulp
绝对首选。若是是其余依赖于文件的任务管理,例如测试(karma
,mocha
),推荐使用grunt
。前端
就插件开发难度而言,gulp远低于grunt。若是你只关注如何处理文件,而不关注细节,那么须要依赖Nodejs Transform stream
的实现。可使用官方推荐的through2
,但推荐使用through-gulp
。后者是基于前者,为gulp插件编写精简优化重写而来。千万不要使用through
,这个包时间久远,长时间没有维护,并且部分mock实现的功能,到nodejs 0.10.x
已经原生支持。若是只是想学习如何编写gulp插件,through-gulp
更适合。through-gulp
: https://github.com/bornkiller/through-gulpthrough2
: https://github.com/rvagg/through2.gitthrough
: https://github.com/dominictarr/throughnode
// PLUGIN_NAME: sample var through = require('through-gulp'); function sample() { // creating a stream through which each file will pass var stream = through(function(file, encoding,callback) { // do whatever necessary to process the file if (file.isNull()) { } if (file.isBuffer()) { } if (file.isStream()) { } // just pipe data next, or just do nothing to process file later in flushFunction // never forget callback to indicate that the file has been processed. this.push(file); callback(); },function(callback) { // just pipe data next, just callback to indicate that the stream's over this.push(something); callback(); }); // returning the file stream return stream; }; // exporting the plugin module.exports = sample;
then use the plugin with gulpgit
var gulp = require('gulp'); var sample = require('sample'); gulp.task('sample', function() { gulp.src(['source file']) .pipe(sample()) .pipe(gulp.dest('file destiny')) });
这个sample
是一个plugin的基本模板,一般的内容处理包裹与以下所示部分,因此一般关注重点也在此处。。github
if (file.isBuffer()) { //文件处理 }
须要特别注意的是,若是你须要处理的不一样文件之间没有任何依赖,在第一个函数函数内部处理完后,进行以下调用,便可将该文件的处理结果传递给下一个插件。这种状况下,能够缺省第二个参数flushFunction
。npm
if (file.isBuffer()) { // 文件处理 var data = fileProcess(file); // 传递处理后数据给下一个插件 this.push(data); // 声明该文件处理完毕 callback(); }
若是须要处理的不一样文件之间存在依赖,例如文件合并,须要全部文件所有读完以后再处理,那么第一个参数transformFunction
将每次传递进来的数据保存在内存中(绝对不要在这里调用this.push()
方法),第二个参数flushFunction
统一作处理后,再传递给下一个插件。json
// transformFunction var fileStorage = []; if (file.isBuffer()) { // 文件处理 var data = fileProcess(file); // 保存传递进来的数据 fileStorage.push(data); // 声明该文件处理完毕 callback(); }
// flushFunction function(callback) { var result = ''; var final = null; fileStorage.foreach(function(file, key) { result += file.contents.toString(); }) final = new Buffer(result); this.push(final); callback(); }
基于stream
grunt
基于文件的机制,致使了任务之间没有信息传递。举简单例子说明,任务流程基本上打开文件、处理文件、保存文件、关闭文件,而后执行继续向后执行任务。每个任务都须要作重复的打开、保存、关闭操做无疑影响效率。gulp
的特色在于单入口模式,文件打开、保存、关闭均一次,从内存拿数据,确定比从硬盘拿数据快。gulp
配置项精简grunt
的配置项繁杂算是公认的弊病,options
嵌套表现尚可,扩展模式与非扩展模式,并且自带concat
行为有时让人难以理解。以coffee script
的编译举例dom
// Gruntfile.js // 包装函数 module.exports = function(grunt) { // 任务配置 grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), coffee: { options: { bare: true, sourceMap: true }, compile: { files: { 'storage-coffee/judge.js': ['storage-coffee-source/judge.coffee'], 'storage-coffee/storage.js': ['storage-coffee-source/storage.coffee'] } } } // 任务加载 grunt.loadNpmTasks('grunt-contrib-coffee'); };
// gulpfile.js var gulp = require('gulp'); var gutil = require('gulp-util'); var coffee = require('gulp-coffee'); var sourcemaps = require('gulp-sourcemaps'); gulp.task('coffee', function() { gulp.src('storage-coffee-source/*.coffee') .pipe(sourcemaps.init()) .pipe(coffee({bare: true}).on('error', gutil.log)) .pipe(sourcemaps.write('./')) .pipe(gulp.dest('storage-coffee')); });
grunt
插件基本上都是多功能,如上所示,grunt-contrib-coffee
附带sourcemap
功能,而gulp-coffee
则只负责编译,sourcemap
交由其余插件处理。私觉得,功能单一化能够更好地组合使用。就插件编写的角度而言,through-gulp
方法更加精炼,是为gulp
插件开发而生,而不是为了node stream
开发而生,因此无需理会through2
晦涩的文档,以前有一个不幸的地方,在gulp
插件开发的单元测试中,经常使用的assert-stream
是基于through2
的,可是不影响大局。如今基于through-gulp
编写gulp
插件测试的模块stream-assert-gulp
,目的在于为gulp
插件编写与测试下降难度。基于前二者,编写了gulp-requirejs-optimizer
插件做为使用requirejs
做为AMD加载器的优化工做,基本可用。函数
through-gulp
: https://github.com/bornkiller/through-gulp。stream-assert-gulp
: https://github.com/bornkiller/stream-assert-gulpgulp-requirejs-optimizer
: https://github.com/bornkiller/gulp-requirejs