前端开发近两年工程化大幅飙升。随着Nodejs
大放异彩,静态文件处理不再需要其他语言辅助。主要的两大工具即为基于文件的grunt
,基于流的gulp
。简单来说,如果需要的只是文件处理,gulp
绝对首选。如果是其他依赖于文件的任务管理,例如测试(karma
,mocha
),推荐使用grunt
。
gulp常用api:
gulp.src(globs[,options]) gulp.dest(path[,options]) gulp.task(name[,deps], fn) gulp.watch(glob [, opts], tasks) or gulp.watch(glob [, opts, cb]) gulp.start(["param"]);
一、gulp plugin开发依赖
就插件开发难度而言,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-gulp
through2: https://github.com/rvagg/through2.git
through: https://github.com/dominictarr/through
二、利用through-gulp开发gulp plugin
依赖API
var through = require('through-gulp'); var stream = through(transformFunction, flushFunction);
结构
// PLUGIN_NAME: sample var through = require('through-gulp'); function sample() { //通过through创建流stream var stream = through(function(file, encoding,callback) { //进程文件判断 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(); }); //返回这个流文件 return stream; }; // exporting the plugin module.exports = sample;
这里
through(function(file, encoding,callback){})发现file是一个对象,含有如下许多属性,但是我们常用的通常是file.path获取文件路径,file.contents获取文件内容
使用:
var gulp = require('gulp'); var sample = require('sample'); gulp.task('sample', function() { return gulp.src(['source file']) .pipe(sample()) .pipe(gulp.dest('file destiny')) });
从以上我们可以看到,through-gulp插件写法,其实就是读取转换流,存储流,导出流的一个过程(一个文件一个文件的过去),如果我们不需要导出流进行链式写法,其实直接module.exports = sample就可以直接单向使用。
下面来看一下简单的 gulp-pf-replace插件,理解原理:
//Gulp默认使用buffer var through = require("through-gulp"); //引入gulp插件模块 var fs = require("fs"); var http = require("http"); var request = require("request"); var path = require("path"); var source = require('vinyl-source-stream'); //常规流转换为gulp支持的Vinyl文件格式 var gutil = require('gulp-util'); //gulp多功能的插件,可以替换扩展名,log颜色日志,模板 var chalk = require('chalk'); //设置颜色 chalk.blue('Hello world!'); // 类型判断 function isType(type){ return function(o){ return Object.prototype.toString.crall(o) === '[object ' + type + ']'; } } var isString = isType("String"); var isObject = isType("Object"); var isArray = isType("Array"); gutil.log('stuff happened', 'Really it did', gutil.colors.magenta('123')); var i=0; //gulp插件原理就是一个流进入,流处理完出来 function replace(modReplace) { //通过through创建流stream var stream = through(function(file, encoding,callback) { //file为对象,含有path,clone,pipe,inspect,history,isNull,isDirectory 等,常用的是path //console.log(isObject(file)); //进程文件判断 if (file.isNull()) { throw "NO Files,Please Check Files!" } //buffer对象可以操作 if (file.isBuffer()) { //拿到单个文件buffer var content = modReplace(file.contents.toString("utf-8")); //console.log(contents); file.contents = new Buffer(content,"utf-8"); //可以通过buffer.toString("utf-8")转换成字符串 //contents = file.contents.toString("utf-8") } //stream流是不能操作的,可以通过fs.readFileSync if (file.isStream()) { //同步读取 var content = modReplace(fs.readFileSync(file.path).toString("utf-8")); file.contents = new Buffer(content,"utf-8"); } // 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(); i++; },function(callback) { gutil.log( gutil.colors.red(i) , gutil.colors.green("已经处理完毕!")); // just pipe data next, just callback to indicate that the stream's over // this.push(something); callback(); }); //返回这个流文件 return stream; }; // 导出插件 module.exports = replace;
使用:
gulp.task("pfDefault",function(){ return gulp.src("./tianzun/*.+(html|htm)",{buffer: true}) .pipe(pfDefault(ypReplace)) .pipe(gulp.dest("./out"))
.on("finish",function(){
console.log("处理完成")
}) }); //替换方法 function ypReplace(data){ return data.replace(/helloword/,"123") }
上面注解比较多,应该大多数人看得懂,这里我就不再做解释。
三、利用through2开发gulp plugin
结构如下:
// through2 是一个对 node 的 transform streams 简单封装 var through = require('through2'); var gutil = require('gulp-util'); var PluginError = gutil.PluginError; // 常量 const PLUGIN_NAME = 'gulp-prefixer'; function prefixStream(prefixText) { var stream = through(); stream.write(prefixText); return stream; } // 插件级别函数 (处理文件) function gulpPrefixer(prefixText) { if (!prefixText) { throw new PluginError(PLUGIN_NAME, 'Missing prefix text!'); } prefixText = new Buffer(prefixText); // 预先分配 // 创建一个让每个文件通过的 stream 通道 return through.obj(function(file, enc, cb) { if (file.isNull()) { // 返回空文件 cb(null, file); } if (file.isBuffer()) { file.contents = Buffer.concat([prefixText, file.contents]); } if (file.isStream()) { file.contents = file.contents.pipe(prefixStream(prefixText)); } cb(null, file); }); }; // 暴露(export)插件主函数 module.exports = gulpPrefixer;
推荐阅读:
Gulp思维——Gulp高级技巧 理解gulp底层处理是buffer、还是Vinyl文件格式流
从零单排之gulp实战 理解gulp的相关原理