Starter
Gulp 基于流的自动化构建工具 (功能同Grunt)
安装
安装全局gulp
> npm install gulp -g
> gulp help
在项目中使用gulp
# 安装gulp
> npm install gulp --save-dev
# 编写gulpfile.js文件
> vim gulpfile.js
...
# 执行task
> gulp xxx
API
gulp.src(globs[, options])
options.buffer
:true(默认),false表示stream方式读取文件内容options.read
:true(默认), false表示不读取文件内容options.base
:everything before a glob starts(默认)eg:
'client/js/**/*.js'
,则base默认为client/js
;client/js/test.js
, base默认为client/js
gulp.src('client/js/**/*.js') // Matches 'client/js/somedir/somefile.js' and resolves `base` to `client/js/` .pipe(minify()) .pipe(gulp.dest('build')); // Writes 'build/somedir/somefile.js' gulp.src('client/js/**/*.js', { base: 'client' }) .pipe(minify()) .pipe(gulp.dest('build')); // Writes 'build/js/somedir/somefile.js'
gulp.dest(path[, options])
- options.cwd:process.cwd() 前脚本的工作目录的路径,当文件输出路径为相对路径将会用到
- options.mode:0777 (默认),指定被创建文件夹的权限
gulp.task(name[, deps], fn)
- name:任务名
- deps :为一个数组,是当前定义的任务需要依赖的其他任务(可不设置),默认依赖任务会并行执行
- fn:任务函数(deps中的任务先执行)
gulp.task('mytask', ['array', 'of', 'task', 'names'], function() { // Do stuff });
gulp.watch(glob [, opts], tasks)
gulp.watch('js/**/*.js', ['uglify','reload']);
gulp.watch(glob [, opts, cb])
gulp.watch('js/**/*.js', function(event){ console.log(event.type); //变化类型 added为新增,deleted为删除,changed为改变 console.log(event.path); //变化的文件的路径 });
说明:
globs
: String or Array- 文件匹配模式(类似正则表达式),Gulp内部使用了
node-glob
模块来实现其文件匹配功能 **
,*
,?
,!
,[...]
,[^...]
,{...}
- 文件匹配模式(类似正则表达式),Gulp内部使用了
SyncTask
想等待异步任务中的异步操作完成后再执行后续的任务,有三种方法可以实现:
- Passing in a callback
gulp.task('sync', function (cb) { // setTimeout could be any async task setTimeout(function () { cb(); }, 1000); });
- Returning a stream (适用于任务就是操作gulp.src获取到的流的情况)
gulp.task('sync', function () { return gulp.src('js/*.js') .pipe(concat('script.min.js') .pipe(uglify()) .pipe(gulp.dest('../dist/js'); });
- Returning a Promise
var Q = require('q'); gulp.task('one',function(){ var deferred = Q.defer(); setTimeout(function() { deferred.resolve(); }, 5000); return deferred.promise; });
测试:
gulp.task('secondTask', ['sync'], function () {
// this task will not start until the sync task is all done!
console.log('secondTask is done');
});
注意: 默认情况下,依赖任务们会并行执行 例如:
gulp.task('otherTask',function(){
//no dependent tasks
...
});
//sync,otherTask会同时执行,然后执行default的fn
gulp.task('default', ['sync','otherTask'], function () {
// do stuff
});
解决方案:
gulp.task('otherTask',["sync"],function(){
//depends on sync
...
});
//先执行sync,然后执行otherTask,最后执行default的fn
gulp.task('default', ['sync','otherTask'], function () {
// do stuff
});
书写太麻烦,推荐插件run-sequence
var runSequence = require('run-sequence');
gulp.task('some-task', function() {
runSequence(
['task-1', 'task-2', 'task-3'], // These 3 can be done in parallel
'task-4', // ...then just do this
['task-5', 'task-5'], // ...then do these things in parallel
'task-6', // ...then do this
// ....
);
});
Vinyl
vinyl 文件对象 (一种虚拟文件格式)
- 有一个
contents
属性来存放文件内容 - 内容可以是以下三种形式:
Stream
Buffer
null
- 判断 contents 属性存放内容的类型:
isBuffer()
: Returns true if file.contents is a Buffer.isStream()
: Returns true if file.contents is a Stream.isNull()
: Returns true if file.contents is null.
Gulp:
- gulp 中的流,预期操作的是 vinyl 文件对象
- 不兼容:
- 常规流与vinyl文件对象流是不同的
- gulp api ,plugin 中操作vinyl对象内容类型(stream,buffer)可能会不同
示例
fs.createReadStream(...)
常规读取流,得到的是数据块(chunk)gulp.src(...)
默认得到的是把内容转换成 buffer 的 vinyl 文件对象,而不是数据块(chunk),可以通过设置buffer: false
选项,可以让 gulp 禁用 bufferfs.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('/usr/share/dict/words').on('data', function(file) { console.log('Read %d bytes of data', file.contents.length); }); 输出结果: > Read 2493109 bytes of data
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 > ...
解决流不兼容问题:
使用第三方插件:
- vinyl-source-stream:常规流 => vinyl文件对象(Stream)
vinyl-buffer:vinyl文件对象(Stream) => vinyl文件对象(Buffer)
var fs=require("fs"); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); var uglify = require('gulp-uglify'); fs.createReadStream('./src/app.js') .pipe(source('app.min.js')) // 常规流 => vinyl 对象(stream) .pipe(buffer()) // vinyl 对象(stream) => vinyl文件对象(Buffer) .pipe(uglify()) // gulp-uglify 只支持处理vinyl文件对象(Buffer) .pipe(gulp.dest('dist/')); gulp.src('app.js', {buffer: false}) .pipe(buffer()) // vinyl 对象(Stream) => vinyl文件对象(Buffer) .pipe(uglify()) .pipe(gulp.dest('dist/')); gulp.src('app.js') .pipe(uglify()) .pipe(gulp.dest('dist/'));
使用gulp插件:
- gulp-buffer:vinyl文件对象(Stream) => vinyl文件对象(Buffer)
- gulp-streamify:vinyl文件对象(Stream) => vinyl文件对象(Buffer) => 其他处理 => vinyl文件对象(Stream)
使用
through2
自定义处理var modify=modify(opts){ return through2.obj(function(file,encoding,next){ //file为上游读流产生的文件数据 //next用于向下游传递处理后的数据 console.log(file.path); console.log(file.isNull()); console.log(file.isStream()); var contents=String(file.contents); file.contents=new Buffer(contents); next(null,file); // this.push(file); next(); }); } fs.createReadStream('/tmp/important.dat') .pipe(modify()) .pipe(fs.createWriteStream('/tmp/out.txt')); gulp.src('/tmp/important.dat') .pipe(modify()) .pipe(gulp.dest('dist/'));
综合测试:
fs.createReadStream("./posts/articles/"+filename)
.pipe(source("2013-11-13-a1.md"))
.pipe(gulp.dest("./dist/articles"))
.pipe(through2.obj(function(file,enc,next){
console.log("Now is "+(file.isStream()?"Stream":"Buffer"));
next(null,file);
}))
.pipe(buffer())
.pipe(through2.obj(function(file,enc,next){
console.log("Now is "+(file.isStream()?"Stream":"Buffer"));
next(null,file);
}))
;
gulp.src("./posts/articles/????-??-??-*.md")
.pipe(through2.obj(function(file,enc,next){
console.log(file.path);
console.log(path.basename(file.path,".md"));
//console.log(path.basename(file.path));
if(file.isNull()) {
console.log("This file is Empty!");
}
else if(file.isStream()){
console.log("This function is not support Stream!");
}
else if (file.isBuffer()){
console.log("Do Marked!");
var result=marked(String(file.contents));
console.log(result.props);
file.contents = Buffer(result.content);
}
next(null, file);
}))
.pipe(gulp.dest("./dist/articles"));
PS: though2
还可以用于自定义gulp插件中
Doc
常用插件
- css编译器: gulp-sass,gulp-less
- file合并:gulp-concat
- file最小化:gulp-uglify 压缩js,gulp-minifycss 压缩css,gulp-imagemin 压缩图像
- file重命名:gulp-rename
- file删除:gulp-clean
- 静态服务器:gulp-server-livereload
使用举例:
重命名,压缩文件:
var rename=require("gulp-rename");
var uglify=require("gulp-uglify");
gulp.task('mini',function(){
gulp.src(Config.react.dest+"/app.js")
.pipe(rename("app.min.js"))
.pipe(uglify())
.pipe(gulp.dest(Config.react.prodDest));
});
实时编译sass文件:
var sass = require('gulp-sass');
function sassBuild(opts,filename){
if(!filename)
filename=opts.default;
var sassOptions = {
errLogToConsole: true,
outputStyle: 'expanded',
loadPath:opts.loadPath
};
function build(event){
if(event)
console.log('File ' + event.path + ' was ' + event.type);
gulp.src(opts.src+filename+".scss")
.pipe(sass(sassOptions).on('error', sass.logError))
//.pipe(concat('style.css'))
.pipe(gulp.dest(opts.dest));
}
build();
gulp.watch(Config.sass.src+"**/*.scss",build)
/*.on("change",function(event){
console.log('File ' + event.path + ' was ' + event.type);
})*/
;
}
gulp.task('sass', function () {
sassBuild(Config.sass,argv.n);
});
架起静态服务器,实时预览:
var server = require('gulp-server-livereload');
function server(){
var filterFun=function(filePath,cb){
Config.server.filterReg.map(function(reg){
if((new RegExp(reg)).test(filePath))
cb(true);
})
/*if(/app.js/.test(filePath) || /style.css/.test(filePath)) {
cb(true)
} */
}
var serverOpts={
livereload: {
enable: true,
filter:filterFun
},
open: true
};
gulp.src(Config.dist)
.pipe(server(serverOpts));
}
gulp.task('server',function(){
server();
});