[转]Gulp思维 —— Gulp高级技巧
感受过gulp.js带来的兴奋过后,你需要的不仅仅是它的光鲜,而是切切实实的实例。这篇文章讨论了一些使用gulp.js时常踩的坑,以及一些更加高级和定制化的插件和流的使用技巧。
基本任务
gulp的基本设置拥有非常友好的语法,让你能够非常方便的对文件进行转换:
gulp.task('scripts', function() {
return gulp.src('./src/**/*.js')
.pipe(uglify())
.pipe(concat('all.min.js'))
.pipe(gulp.dest('build/'));
});
这种方式能够应付绝大多数情况,但如果你需要更多的定制,很快就会遇到麻烦了。这篇将介绍这其中的一些情况并提供解决方案。
流不兼容?
使用gulp时,你可能会陷入“流不兼容”的问题。这主要是因为常规流和Vinyl文件对象有差异,或是使用了仅支持buffer(不支持流)库的gulp插件与常规流不兼容。
比如说,你不能直接将常规流与gulp和(或)gulp插件相连。我们创建一个可读流,并尝试使用gulp-uglify和gulp-rename来进行转换,将最后得到的内容交给gulp.dest()
。下面就是个错误的例子:
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文件对象作为输入的函数(插件)相连
Vinyl文件对象
gulp使用了vinyl-fs,它实现了gulp.src()
和gulp.dest()
方法。vinyl-fs使用vinyl文件对象——一种“虚拟文件格式”。如果我们需要将gulp和(或)gulp插件与常规的可读流一起使用,我们就需要先把可读流转换为vinyl。
使用vinyl-source-stream是个不错的选择,如下:
var source = require('vinyl-source-stream'),
marked = require('gulp-marked');
fs.createReadStream('*.md')
.pipe(source())
.pipe(marked())
.pipe(gulp.dest('dist/'));
另外一个例子首先通过browserify封装并最终将其转换为一个vinyl流:
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.dest
这个gulp方法创建了一个可写流,它真的很方便。它重新使用可读流中的文件名,然后在必要时创建文件夹(使用mkdirp)。在写入操作完成后,你能够继续使用这个流(比如:你需要使用gzip压缩数据并写入到其他文件)
流和buffer
既然你有兴趣使用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。
Gulp默认使用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
> ...
从流到buffer
由于所需的输入(输出)流和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/'));
将buffer转换为流
你也可以使用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(){}());'))
.pipe(streamify(uglify()))
.pipe(gulp.dest('build'))
.pipe(gzip())
.pipe(gulp.dest('build'));
不是所有事都需要插件
虽然已经有很多使用且方便的插件,很多任务以及转换可以不使用插件而轻易完成。插件会带来一些问题,你需要依赖一个额外的npm模块,一个插件接口和(反应迟钝?)的维护者,等等。如果一个任务可以不使用插件而使用原生模块就能轻易完成,绝大多数情况下,都建议不要使用插件。能够理解上面所说的概念,并能够在所处的情况下做出正确的决定,这点非常重要。下面来看一些例子:
vinyl-source-stream
之前的例子中,我们已经直接使用了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任务列表。
- 你需要更多的动态的和(或)可重用的子任务。
最后的思考
请注意,gulp(或grunt)并不总是当前情境下的最佳工具。比如说,如果你需要拼接并使用uglify压缩一系列的JavaScript文件,又或者你需要编译一些SASS文件,你可能需要考虑使用makefile或npm run,通过命令行来实现。减少依赖,减少配置,才是正解。
阅读通过npm run来实现任务自动化来了解更多信息。你需要明确通过一系列的“自定义构建”后需要得到什么,而哪个工具最合适。
不过,我觉得gulp是一个伟大的构建系统,我很喜欢使用它,它展现了Node.js中流的强大。
[转]Gulp思维 —— Gulp高级技巧的更多相关文章
- Gulp思维——Gulp高级技巧
本文翻译自Getting gulpy -- Advanced tips for using gulp.js 感受过gulp.js带来的兴奋过后,你需要的不仅仅是它的光鲜,而是切切实实的实例.这篇文章讨 ...
- 在windows下安装gulp —— 基于 Gulp 的前端集成解决方案(一)
相关连接导航 在windows下安装gulp —— 基于 Gulp 的前端集成解决方案(一) 执行 $Gulp 时发生了什么 —— 基于 Gulp 的前端集成解决方案(二) 常用 Gulp 插件汇总 ...
- 【gulp】gulp + browsersync 构建前端项目自动化工作流
什么是 gulp? gulp.js 是一个自动化构建工具,开发者可以使用它在项目开发过程中自动执行常见任务.gulp.js 是基于 node.js 构建的,利用 node.js 流的威力,你可以快速构 ...
- 利用gulp 插件gulp.spritesmith 完成小图合成精灵图,并自动输出样式文件
安装依赖 yarn add gulp yarn add gulp.spritesmith 本次依赖的版本是: "gulp": "^3.9.1" "gu ...
- 【gulp】Gulp的安装和配置 及 系列插件
注意:要安装俩次gulp(全局和本地):全局安装gulp是为了执行gulp任务,本地安装gulp则是为了调用gulp插件的功能. 之前由大牛帮忙配置的gulp来用.今天时间充裕,就和小伙伴一起动手配置 ...
- [如何在mac下使用gulp] 2. gulp模块的常用方法
常用的gulp模块方法有: gulp.src() gulp.src('client/one.js'); //指定明确的要处理文件 gulp.src('client/*.js'); //处理client ...
- 玩转gulp之gulp编译less
用好gulp grunt webpack让前端编程走向自动化,是作为一个前端开发必须学会的技能,不然逼格怎么提升的上去呢... 然后教大家如何用gulp装逼.一点点的学,都是相通的嘛 1. 安装nod ...
- 前端构建工具之gulp的安装和配置
在选择构建工具时,看到更多人推荐gulp,从此入了gulp的坑- 一.安装node环境 百度谷歌一下就有了,在终端中分别输入 node -v 和 npm -v,若显示node和npm的版本号则说明no ...
- 教你写gulp plugin
前端开发近两年工程化大幅飙升.随着Nodejs大放异彩,静态文件处理不再需要其他语言辅助.主要的两大工具即为基于文件的grunt,基于流的gulp.简单来说,如果需要的只是文件处理,gulp绝对首选. ...
随机推荐
- VIPServer:阿里智能地址映射及环境管理系统详解
http://geek.csdn.net/news/detail/110586 作者: 周遥,阿里技术专家,花名玄胤,毕业于四川大学.六年大型分布式与中间件系统经验,三项国家专利,参加过多次“双十一” ...
- 2013-8-6 ubuntu基本操作
1,apt-get下载文件默认安装路径 apt-get 下载后,软件所在路径是什么?? /var/cache/apt/archives ubuntu 默认的PATH为 PATH=/home/brigh ...
- 大数据工具篇之flume1.4-安装部署指南
一.引言 flume-ng是一个分布式.高可靠和高效的日志收集系统,flume-ng是flume的新版本的意思,其中“ng”意为new generate(新一代),目前来说,flume-ng 1.4是 ...
- C/C++基础----类
IO类属于不能被拷贝的类型,因此只能通过引用来传递.同时读取和写入操作都会改变流的内容,所以接收的是普通引用. 类内的友元声明仅仅指定了访问的权限,需要在友元声明之外再专门对函数进行一次声明. 可变数 ...
- python使用smtplib发送邮件
python要实现发送邮件的功能,需要使用smtplib库. 1. 过程大致如下: 1. 建立和SMTP邮件服务器的连接 # 默认端口25 smtp = smtplib.SMTP(host, port ...
- 服务注册发现consul之五:Consul移除失效服务的正确姿势
spring cloud微服务不定期会出现网络请求失败的错误.于是看了下后台日志,发现有几个请求会报如下的异常: Caused by: feign.RetryableException: Connec ...
- 杂项:Office Visio
ylbtech-杂项:Office Visio Office Visio 是一款便于IT和商务人员就复杂信息.系统和流程进行可视化处理.分析和交流的软件.使用具有专业外观的 Office Visio ...
- Sublime Text 2 - 性感无比的代码编辑器!程序员必备神器!
Sublime Text 2 - 性感无比的代码编辑器!程序员必备神器! http://www.iplaysoft.com/sublimetext.html 代码编辑器或者文本编辑器,对于程序员来说, ...
- Scrapy爬取人人网
Scrapy发送Post请求 防止爬虫被反主要有以下几个策略 动态设置User-Agent(随机切换User-Agent,模拟不同用户的浏览器信息) 禁用Cookies(也就是不启用cookies m ...
- 第10章 线程控制(5)_多线程下的fork
6. 线程和fork 6.1 多线程下的fork (1)历史包袱 ①fork与多线程的协作性很差,这是POSIX系统操作系统的历史包袱. ②长期以来程序都是单线程的,fork运行正常,但引入线程这后, ...