资讯专栏INFORMATION COLUMN

Gulp思维——Gulp高级技巧

ad6623 / 1139人阅读

摘要:我们创建一个可读流,并尝试使用和来进行转换,将最后得到的内容交给。它重新使用可读流中的文件名,然后在必要时创建文件夹使用。使用常规可读流时,你可以监听事件来检测数据碎片的到来不同的是,使用会将转换成的文件对象重新写入到流中。

本文翻译自Getting gulpy -- Advanced tips for using gulp.js

感受过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(){<%= contents %>}());"))
    .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中流的强大。

希望这些能够帮到你!如果你有任何反馈或其他提议,请在评论中告诉我,或者加我的twitter:@webprolific

小广告:更多内容欢迎来我的博客,共同探讨

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87638.html

相关文章

  • [Laya游戏开发]小技巧使Laya构建速度提高10倍

    摘要:为何选择引擎微信小游戏推出之后,很多公司也相应的进入到微信小游戏这个领域,现在市场上的游戏开发引擎,如都对小游戏有了很好的兼容性。 1. 为何选择Laya引擎 微信小游戏推出之后,很多公司也相应的进入到微信小游戏这个领域,现在市场上的游戏开发引擎,如Cocos、Egret、Laya都对小游戏有了很好的兼容性。目前公司技术栈主要是使用Cocos和Laya,经过几个项目的接触,考量了引擎在...

    Harpsichord1207 评论0 收藏0
  • 个人分享--web前端学习资源分享

    摘要:前言月份开始出没社区,现在差不多月了,按照工作的说法,就是差不多过了三个月的试用期,准备转正了一般来说,差不多到了转正的时候,会进行总结或者分享会议那么今天我就把看过的一些学习资源主要是博客,博文推荐分享给大家。 1.前言 6月份开始出没社区,现在差不多9月了,按照工作的说法,就是差不多过了三个月的试用期,准备转正了!一般来说,差不多到了转正的时候,会进行总结或者分享会议!那么今天我就...

    sherlock221 评论0 收藏0
  • Vue ES6 Jade Scss Webpack Gulp

    摘要:主有前端后端,并加,各一名。本着工欲善其事,必先利其器的理念,一直以来在工作效率这块,略怀执念一个问题不应该被解决两次。下图为开发项目机制所涉及到的插件工欲善其事,必先利其器,语言,框架皆可以归结为器而不当仅局限于开发工具以及机。 原文链接: http://www.jeffjade.com/2016/05/08/106-vue-es6-jade-scss-webpack-gulp/ 一...

    rickchen 评论0 收藏0
  • 重读 Gulp

    摘要:当接收一个回调函数的时候,一定要注意回调函数中的参数。主要作用就是用来读取文件或者文件夹中的数据。表示文件的名称指的是发生的变化使用技巧的进一步使用,可以参照中文官网中的技巧集。 Gulp 简介 Gulp 对现在的前端而言,是一个稍微老旧的工具了,但是,为了复习以前学过的内容,还是把它翻出来,放在自己的博客中。说不定哪天又用到了呢。 需要说明的是,这里使用的 Gulp 版本是 3.9....

    vpants 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<