前端開發近兩年工程化大幅飆升。隨着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