常用gulp插件介绍

2016-08-09 曹强 更多博文 » 博客 » GitHub »

javascript

原文链接 https://ronghuaxueleng.github.io/2016/08/09/gulp-%E5%B8%B8%E7%94%A8gulp%E6%8F%92%E4%BB%B6%E4%BB%8B%E7%BB%8D/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


这里介绍一些gulp比较常用的插件,包括util工具类、stream相关、inject相关、Angular相关、压缩工具类、server相关、特定语言相关等。 <!-- more -->

util工具类

这个分类下主要介绍一些辅助工具类的插件。

gulp-load-plugins

顾名思义,本插件的功能就是帮你自动require你在package.json中声明的依赖。只要一句var $ = require('gulp-load-plugins')(),则package.json中声明的gulp-gulp.开头的插件就会自动被放在变量$下面。如$.util就等于require('gulp-util'),而有两个连字符的插件则会自动命名为驼峰格式,如$.taskListing则等于require('gulp-task-listing')。有了这个插件,就不用一个一个的require了。这个插件还有一些常用的参数配置,这里列几个常用的:

  • lazyload: true,用到这个插件的时候再去require,默认为true。
  • rename: {'gulp-task-listing': 'list'},如果有些插件名字太长,可以使用该参数重命名。
  • scope: ['dependencies'],本插件默认会扫描package.json里的所有dependence,可以使用该参数进行限制。

要使用这些参数只要在require的时候传入即可,如require('gulp-load-plugins')({lazy: true})

gulp-task-loader

这个插件的作用很简单,就是可以将gulpfile.js拆分成多个文件,放到目录下,是任务更清晰,结构更分明,使用方法如下: clear.js

'use strict';
var del = require('del');

module.exports = function() {
    return del.sync(this.opts.destDir);
};

copy.js

'use strict';

module.exports = function() {
    return this.gulp.src(this.opts.publicDir)
     .pipe(this.gulp.dest(this.opts.destDir));
};
module.exports.dependencies = ['clean'];

gulpfile.js

'use strict';
var gulp = require('gulp');
var config = {
    pkg: require('./package.json'),
    publicDir: ['./{public,public/**}'],
    destDir: 'dest',
    version: "0.01"
};

require('gulp-task-loader')(config);
gulp.task('build', ['clean', 'copy');

可以看到copy.js中最后有这样一行代码module.exports.dependencies = ['clean'];,这个是用做任务依赖的,只有前一个任务执行完毕后才能执行当前任务,使用起来还是很简单的

gulp-task-listing

这个插件的作用也很容易猜,它可以打印出gulpfile.js中定义的所有task,这个插件我们在重构你的gulpfile这篇文章的最后介绍过,值得一提的是它还可以根据task的名字确定它是不是一个子task,比如带有:-_的task就被认为是子task。我一般把这个插件作为默认的task来调用,如

gulp.task('default', ['help']);
gulp.task('help', $.taskListing);

这样,如果只执行gulp的话就会打印出所有定义好的task,非常实用。

yargs

严格的说,yargs不是专门用于gulp的,它是Node中处理命令行参数的通用解决方案。只要一句代码var args = require('yargs').argv;就可以让命令行的参数都放在变量args上,非常方便。它可以处理的参数类型也是多种多样的:

  • 单字符的简单参数,如传入-m=5-m 5,则可得到args.m = 5
  • 多字符参数(必须使用双连字符),如传入--test=5--test 5,则可得到args.test = 5
  • 不带值的参数,如传入--mock,则会被认为是布尔类型的参数,可得到args.mock = true

除此之外,还支持很多其他类型的传参方式,具体可参考它的文档

gulp-util

gulp-util带有很多方便的函数,其中最常用的应该就是log了。$.util.log()支持传入多个参数,打印结果会将多个参数用空格连接起来。它与console.log的区别就是所有$.util.log的结果会自动带上时间前缀。另外,它还支持颜色,如$.util.log($.util.colors.magenta('123'));打印出来的123是品红色的。其实$.util.colors就是一个chalk的实例,而chalk是专门用来处理命令行打印着色的一个工具。

del

grunt自身提供一个grunt-contrib-clean用来处理支持glob匹配的删除,而del就是gulp上对应的工具。del支持和gulp.src参数同样的匹配,除此之外,它的第二个参数还支持一个回调函数,当删除完成以后执行,所以这是一个异步的删除。常用的调用方法为:del([xxx], callback)

gulp-bytediff

这是一个统计文件大小变化的工具,通常与压缩类工具放在一起实用,比如

gulp.src('**/*.html')
    .pipe($.bytediff.start())
    .pipe($.minifyHtml({empty: true}))
    .pipe($.bytediff.stop(bytediffFormatter))
    .pipe(gulp.dest('dist'));

function bytediffFormatter (data) {
    var difference = (data.savings > 0) ? ' smaller.' : ' larger.';
    return data.fileName + ' went from ' +
        (data.startSize / 1000).toFixed(2) + ' kB to ' +
        (data.endSize / 1000).toFixed(2) + ' kB and is ' +
        formatPercent(1 - data.percent, 2) + '%' + difference;
}

在压缩的pipe前后加上$.bytediff.start()$.bytediff.stop(callback),即可统计压缩前后文件的变化。在callback中传入的参数data上,可以访问到很多变量,如文件名,变化前后的大小,变化百分比等等。

gulp-print

这个插件的作用很简单,打印出stream里面的所有文件名,通常调试的时候比较需要。

gulp-bump

这个插件也可以顾名思义:用来升级版本用的,废话不说,直接看例子吧:

return gulp
    .src('package.json')
    .pipe($.bump(options))
    .pipe(gulp.dest('dist'));

重点来看这里的options,我们可直接传递一个具体的version进去,也可以按照Node的版本规范传递一个type进去,让其自己生成对应的version:

  • version,直接传递要升级到的版本号,如1.2.3
  • type,可接受的值包括下面四个,倘若现在的版本号为1.2.3,则对应的新版本号为:
    • prerelease:1.2.3-0
    • patch:1.2.4
    • minor:1.3.0
    • major:2.0.0

最终这个升级后的版本号会反映在package.json中,当然,你也可以在gulp.src中传入更多的文件(如bower.json)来替换更多的文件。

gulp-header

这个工具用来在压缩后的JS、CSS文件中添加头部注释,你可以包含任意想要的信息,通常就是作者、描述、版本号、license等,比如:

function getHeader () {
    var pkg = require('package.json');
    var template = ['/**',
        ' * <%= pkg.name %> - <%= pkg.description %>',
        ' * @authors <%= pkg.authors %>',
        ' * @version v<%= pkg.version %>',
        ' * @link <%= pkg.homepage %>',
        ' * @license <%= pkg.license %>',
        ' */',
        ''
    ].join('\n');
    return $.header(template, {
        pkg: pkg
    });
}

这个函数将package.json中的各种信息提取出来,变成头部注释,只要在压缩的pipe中调用.pipe(getHeader())即可。

stream相关

这个部分主要介绍一些跟stream操作有关的插件。

gulp-filter

gulp-filter可以把stream里的文件根据一定的规则进行筛选过滤。比如gulp.src中传入匹配符匹配了很多文件,可以把这些文件pipe给gulp-filter作二次筛选,如gulp.src('**/*.js').pipe($.filter(**/a/*.js)),本来选中了所有子文件下的js文件,经过筛选后变成名为a的子文件夹下的js文件。那有人要问了,为什么不直接将需要的筛选传入gulp.src,干嘛要多筛选一步呢?这里面有两种情况:

  • gulp.src$.filter中间可能需要别的处理,比如我对所有文件做了操作1以后,还需要筛选出一部分做操作2。
  • 第二种情况就要谈到gulp-filter的另外一个特性:筛选之后还可以restore回去。比如我对所有文件做了操作1,筛选了一部分做操作2,最后要把所有的文件都拷贝到最终的位置。代码如下:
var filter = $.filter('**/a/*.js');
gulp.src('**/*.js')
    .pipe(action1())
    .pipe(filter)
    .pipe(action2())
    .pipe(filter.restore())
    .pipe(gulp.dest('dist'))

可以看到,如果没有restore这个操作,那么拷贝到最终位置的文件将只包含被过滤出来的文件,这样一restore,所有的文件都被拷贝了。

gulp-flatten

gulp-flatten非常实用,可能知道别的库中flatten函数的同学已经猜到它是干嘛的了。比如gulp.src('**/*.js')匹配了很多文件,包括a/b/c.jsd/e.jsf/g/h/i/j/k.jsl.js,这些文件的层级都不一样,一旦我们将这个文件pipe给$.flatten(),则所有的文件夹层级都会去掉,最终的文件将是c.jse.jsk.jsl.js,在一些场景下还是非常有用的。

gulp-plumber

这个插件的作用简单来说就是一旦pipe中的某一steam报错了,保证下面的steam还继续执行。因为pipe默认的onerror函数会把剩下的stream给unpipe掉,这个插件阻止了这种行为。那它一般用于哪种场景呢?比如,代码每次build之前要跑一遍jshint和jscs来确保所有代码都符合规范,但一旦某些代码不符合规范,整个build流程就会停止,这个时候就需要gulp-plumber出场了。如:

gulp.task('build', ['jslint', 'xxxx']);
gulp.task('jslint', function () {
    return gulp
        .src(config.js.all)
        .pipe($.plumber())
        .pipe($.jshint())
        .pipe($.jscs()); 
});

这样,一旦jshint或jscs报错,整个build流程还是可以继续走下去的,而且gulp-plumber会给出一个报错提醒我们:

[16:52:36] Plumber found unhandled error:
 Error in plugin 'gulp-jshint'
Message:
    JSHint failed for: xxxx.js

gulp-if

这个插件的功能也很简单,可以条件性的添加stream,如.pipe($.if(flag, action1())),则只会在flag变量为true时才会将action1()添加到stream中去。其实不用这个插件也可以达到类似的效果,那就是gulp-util里有一个函数叫做noop(),也就是no operation,这个函数其实是返回一个什么都不干的空stream。利用这个函数我们可以这么写:.pipe(flag ? action1() : $.util.noop()),与上例的效果是一样的。

merge-stream

一个gulp的task只能返回一个stream,但有的时候有这么一种情景:有两类文件,它们的原始位置和处理后的位置都是不同的,但它们的处理流程相同。由于gulp.srcgulp.dest的参数不同,我们就需要写两个task来分别完成这个任务,一方面略显重复,另一方面逻辑上来讲这两个task本来就是处理同样的事情的。这种情况就需要merge-stream登场了,它的作用就是将多个stream合成一个返回。比如下面这个例子:

var merge = require('merge-stream');
gulp.task('jade', function () {
    var stream1 = jade(src1, dest1);
    var stream2 = jade(src2, dest2);
    return merge(stream1, stream2);
});

function jade (src, dest) {
    return gulp
        .src(src)
        .pipe($.jade())
        .pipe(gulp.dest(dest));
}

可以看到,处理的流程被提取出来放入一个函数,它接受两个参数,分别是src和dest。然后在task中直接调用这个函数生成两个stream,然后返回merge-stream合并后的结果。

run-sequence

gulp里的task都是异步并发执行的,有的时候我们需要一连串的task按顺序执行,这时就需要run-sequence登场了。它的调用很简单:runSequence('task1', 'task2', ['task3', 'task4'], 'task5'),这里的task都是gulp定义好的task名称,task1完成后才会执行task2,以此类推。注意到task3和task4被放在中括号里了,这表明,task3和task4可以并发执行的,但两个都执行完后才会执行task5。这里要说明的是,每个task要么返回一个stream,即return gulp.src().pipe().pipe(),要么支持回调函数,即gulp.task('task1', function (done) { action1(done); }),满足了这两点才能保证正常的执行顺序,因为这是gulp对异步task的基本要求

inject相关

这个部分主要介绍一些将JS/CSS自动插入到HTML的相关插件。

wiredep

wiredep就是wire dependence的意思,它的作用就是把bower.json中声明的dependence自动的包含到HTML中去。要插入文件,wiredep需要解决两个问题:

  • 插入的位置:wiredep通过识别HTML中的注释来识别插入位置,如
<!-- bower:css -->
<!-- endbower -->
<!-- bower:js -->
<!-- endbower -->

不同类型的文件被插入到不同的区块。

  • 插入什么文件:要插入的文件列表自然来自bower.json,每个bower安装的依赖库,根目录下边都有一个自己的bower.json文件,其中的main字段指明了使用这个库需要包含的文件,wiredep最终包含的文件列表就来自这个字段。有些情况下,库自身的bower.json的main字段可能会多包含文件或少包含文件,如果想要定制这个列表,则可以在自己的bower.json中使用overrides字段,如下面的代码覆盖了mdi这个库的main字段。
"overrides": {
  "mdi": {
    "main": [
      "css/materialdesignicons.css"
    ]
  }
},

wiredep插件支持很多参数,常用的主要有两个:

  • bowerJson:指定bower.json的内容,注意这个字段不是bower.json文件的位置,这个参数需要使用require后的结果赋值:require('bower.json')
  • directory:指定存放bower安装后的依赖包的路径,通常是bower_components。注意最终插入到HTML中的文件列表的路径是index.html文件相对于本文件夹的相对路径。

使用wiredep也比较简单,直接把它传入到stream中即可,如gulp.src('index.html').pipe(wiredep(options))

gulp-inject

这个插件的作用与wiredep类似,不同的是可以自己任意指定需要插入文件的列表。它同样是利用注释来寻找插入的位置,它识别的默认注释为<!-- inject:js -->,但更加智能:

  • 支持各种模板语言:可以根据gulp.src指定的源文件自动识别注释和插入内容,除了支持HTML外,还支持jade、haml等。若源为jade文件,则识别的注释为//- inject:js,插入的内容为:script(src="<filename>.js")
  • 配置非常灵活:
    • name:默认识别的注释标签格式为<!-- name:ext -->,这里的name默认值就是“inject”,而ext的默认值是要插入的文件的扩展名。那么name属性可配置意味着可以添加自定义的插入区块,如<!-- production:js -->,这个标签可以只插入生产环境需要包含的JS文件。
    • starttag和endtag:支持自定义需要识别的注释内容。
    • addPrefix和addSuffix:支持在插入文件的路径上自定义前缀、后缀。
    • relative:指定插入文件的路径是否为相对路径。
    • ingorePath:指定插入文件的路径前面会忽略掉指定的路径。
    • read:这个参数通常给false,不需要真正的去读取文件。

这个插件的使用场景通常是,我们需要index里有多个区块,比如上面name的例子,只有当为production环境编译的时候才去包含相关的文件。

gulp-userefgulp-revgulp-rev-replace

这三个工具之所以放在一起讲,是因为它们一般都是一起使用的。它们要解决什么问题呢?通过上面的wiredep也好,gulp-inject也好,插入了一堆JS、CSS文件到HTML中,一旦部署到生产环境,这么多文件必然是要合并压缩的。光是压缩还不够,为了解决缓存问题,每次合并压缩后要给最终的文件加hash,这样每次文件内容一变动,hash也会跟着变动,就不存在浏览器依然使用缓存的老文件的问题。这样得到最终的文件以后,肯定还要将这个文件替换回HTML中去,一大堆的script和link标签替换成最终合并压缩带hash的版本。

前面啰啰嗦嗦的一大堆工作就是这三个插件要解决的问题了。首先,gulp-useref根据注释将HTML中需要合并压缩的区块找出来,对区块内的所有文件进行合并。注意:它只负责合并,不负责压缩!所以合并出来的文件我们要自行压缩,压缩以后调用gulp-rev负责在文件名后追加hash。最后调用gulp-rev-replace负责把最终的文件名替换回HTML中去。扯了大半天,还是直接上例子吧。先来看看HTML中的注释:

<!-- build:css static/styles/lib.css -->
<!-- bower:css -->
<!-- endbower -->
<!-- endbuild -->

<!-- build:css static/styles/app.css -->
<!-- inject:css -->
<!-- endinject -->
<!-- endbuild -->

<!-- build:js static/js/lib.js -->
<!-- bower:js -->
<!-- endbower -->
<!-- endbuild -->

<!-- build:js static/js/app.js -->
<!-- inject:js -->
<!-- endinject -->
<!-- endbuild -->

gulp-useref识别的就是build开头的注释,build后面首先跟的是类型扩展名,然后后面的路径就是build区块中的所有文件进行合并后的文件路径,这个相对路径是相对于这个HTML的路径。上面的例子中我们用build区块把bower和inject进来的文件包起来,这些文件就可以被gulp-useref合并了。再来看gulp中useref相关task的定义:

var assets = $.useref.assets({searchPath: 'app/src/'});

var cssFilter = $.filter('**/*.css');
var jsAppFilter = $.filter('**/app.js');
var jslibFilter = $.filter('**/lib.js');

return gulp
    .src('index.html')
    .pipe(assets)
    .pipe(cssFilter)
    .pipe($.csso())
    .pipe(cssFilter.restore())
    .pipe(jsAppFilter)
    .pipe($.uglify())
    .pipe(getHeader())
    .pipe(jsAppFilter.restore())
    .pipe(jslibFilter)
    .pipe($.uglify())
    .pipe(jslibFilter.restore())
    .pipe($.rev())
    .pipe(assets.restore())
    .pipe($.useref())
    .pipe($.revReplace())
    .pipe(gulp.dest('dist'));

首先一上来,先调用$.useref.assets()函数,这个函数返回一个stream,包含已经合并后的文件。可以尝试在第9行后面加上前面介绍过的gulp-print插件.pipe($.print()),打印出stream里的文件,发现就是前面HTML中4个build注释块后面的4个文件。注意这里调用的时候跟了一个searchPath的参数,它的用处就是指定从哪个路径开始寻找build区块底下的文件。比如build区块底下有这么一行`,那最终gulp-useref将从这个路径app/src/static/js/a.js找到这个文件。第3到5行定义了3个filter,这主要是为了后面压缩准备的。下面正式看stream的pipe流程。先选出要处理的HTML文件,然后调用刚才得到的assets得到合并后的4个文件,第10到12行筛选出合并后的CSS文件进行压缩(压缩类插件下篇文章再讲),第13到16行筛选出app.js进行压缩,第17到19行筛选出lib.js进行压缩。之所以要区别对待app.js和lib.js,是因为app.js是我们自己写的代码,压缩后要加上header(第15行,使用前面介绍过的gulp-header插件),而lib.js是第三方的各种库,直接压缩即可。后面调用gulp-rev给压缩后的4个文件加hash,然后调用assets.restore()将src源换回HTML文件,这是为了后面调用$.useref(),因为$.useref()`做替换的src源是HTML文件,同样后面调用gulp-rev-replace将带hash的文件替换回HTML,它要求的src源也必须是HTML文件。这里的顺序很重要,因为这几个插件接受的源不一样,gulp-rev接受的是JS、CSS文件,而gulp-useref和gulp-rev-replace接受的是HTML。还有一个问题:gulp-rev-replace是怎么知道gulp-rev进行hash前后的文件名对应关系呢?其实gulp-rev会生成一个manifest的文件,内容是类似下面的JSON:

{
    "static/styles/lib.css": "static/styles/lib-d41d8cd98f.css"
    "static/js/lib.js": "static/js/lib-273c2cin3f.js"
}

当然这个文件默认是不会生成在文件系统里的,可以通过.pipe($.rev.manifest())将这个文件保存到本地。有了这个文件,gulp-rev-replace甚至可以脱离gulp-rev独立工作哦!

gulp-html-replace

gulp-html-replace同样是识别以build开头的注释,与gulp-useref不同的是不会对build区块中的所有文件进行合并,而是根据配置直接替换掉build区块中的内容下面以一个例子做说明:

html片段

<html>
    <head>
        <!-- build:css -->
        <link href="css/one.css" rel="stylesheet">
        <link href="css/two.css" rel="stylesheet">
        <!-- endbuild -->
    </head>
    <body>
        <!-- build:vendorJs -->
        <script type="text/javascript" src="js/one.js"></script> 
        <script type="text/javascript" src="js/two.js"></script>
        <!-- endbuild -->
    </body>
</html>

相关任务:

    gulp.src('index.html')
        .pipe(htmlreplace({
            'css': 'styles.min.css',
            'vendorJs': '/js/vendor.js'
        }))
        .pipe(gulp.dest('dest/'));

执行任务后的html片段:

<html>
    <head>
        <link rel="stylesheet" href="styles.min.css">
    </head>
    <body>
        <script src="/js/vendor.js"></script>
    </body>
</html>

很简单,不用再做过多的说明

gulp-rev-hash2

这个插件是在引用的静态文件后面添加版本号的,这个版本号是被引用的文件的md5值,所以说这个插件和实用,不多说,直接上代码

gulpfile.js

var gulp = require('gulp');
var rev = require('gulp-rev-hash2');

gulp.task('rev', function () {
    gulp.src('template.html')
        .pipe(rev({
          'cwd': 'public/assets',
          'suffix': 'rev',
          'fileTypes': ['css']
        }))
        .pipe(gulp.dest('.'));
});

输入:

<link rel="stylesheet" href="main.min.css"/>

<script src="abc.js"></script>
<script src="def.js"></script>

输出:

<link rel="stylesheet" href="main.min.css?rev=9d58b7441d92130f545778e418d1317d">

<script src="abc.js"></script>
<script src="def.js"></script>

用法也是很简单的,具体的可以看api

gulp-file-concat

顾名思义,文件合并,这个插件可以合并通过document.write引入的js和通过@import引入的css,下面看一个例子: index.js:

(function() {
    document.write('<script src="a.js"><\/script>');
    document.write('<script src="b.js"><\/script>');
}());

index.css:

@import url("a.css");
@import url("b.css");

gulpfile.js:

var gulp = require('gulp');
var fileconcat = require('gulp-file-concat');
var uglify = require('gulp-uglify');
var ngAnnotate = require('gulp-ng-annotate');
var rename = require('gulp-rename');

gulp.task('default', function() {
  gulp.src('index.js')
    .pipe(fileconcat({
        relativeUrls: './'
    }))
    .pipe(ngAnnotate())
    .pipe(uglify())
    .pipe(rename(function(path){
        path.basename = 'app';
        path.extname = '.js'
    }))
    .pipe(gulp.dest('build/'));

  gulp.src('index.css')
      .pipe(fileconcat())
      .pipe(gulp.dest('build/'));
});

这个一个文件合并的插件,为什么要把这个插件放在这里来介绍,其实我觉得这个可以和gulp-html-replace联合使用,至于怎么用,请细细品味吧

Angular相关

这个部分介绍与Angular相关的一些插件。

gulp-angular-templatecache

Angular自带的$templateCache服务可以把Angular中用到的所有模板缓存下来,而这个插件的功能就是直接将指定的HTML模板文件以JS字符串的形式注册在$tempalteCache服务中,这样所有的模板就会随JS文件直接一次性下载下来。这个插件使用起来也非常简单,gulp.src传入需要缓存的HTML模板文件,然后.pipe($.angularTemplatecache(filename, options))即可。其中filename表示生成后的js文件的名字,默认为templates.js,常用的options有:

  • module:指定希望将这个模板放入哪个Angular的module中。
  • root:指定注册后的模板路径前缀。

生成后的文件如下:

``` javascript templates.js angular.module("module name").run([$templateCache, function($templateCache) { $templateCache.put("template1.html", // template1.html content (escaped) ); $templateCache.put("template2.html", // template2.html content (escaped) ); // etc. } ]);


#### [gulp-ng-annotate](https://www.npmjs.com/package/gulp-ng-annotate)

这个插件是[ng-annotate](https://github.com/olov/ng-annotate)的gulp插件版,它解决的是Angular中依赖注入的小问题。Angular中通过参数名来进行依赖注入,一旦压缩,参数名就会变化导致注入失败,所以官方推荐通过添加字符串进行注入。比如:

``` javascript
angular
    .module('app.dashboard')
    .controller('DashboardController', DashboardController);

DashboardController.$inject = ['userAPI'];
/* @ngInject */
function DashboardController (userAPI) {}

上面的例子中我们定义了一个叫DashboardController的controller,它依赖一个userAPI的service。这个插件的作用就是根据第6行的注释/* @ngInject */来帮你生成第5行的内容。当然是在你忘记写的情况下,如果你自己写了它不会重复生成。除了这种使用$inject赋值的方式,它同样支持inline定义的方式,如

/* @ngInject */
.controller('DashboardController', function (userAPI) {});

会生成

.controller('DashboardController', ['userAPI', function (userAPI) {}]);

它常用的参数就是{add: true},表明仅在不存在的情况下才会进行添加。

推荐在HTML头上使用ng-strict-di属性,这样即便在不压缩的情况下,一旦你忘记显式的用字符串声明依赖,Angular将立刻报错。

gulp-protractor

Angular的e2e测试工具protractor的配套插件,可以通过它非常方便的在gulp中调用protractor。有了这玩意,你就不需要手动在gulp中调用protractor的可执行文件,然后处理进程神马的,只要一句简单的.pipe($.protractor.protractor(options)即可。常用的options包括:

  • configFile:即protractor的配置文件路径。
  • args:调用protractor时传入的参数,是个数组。最常用的就是指定protractor只跑一个suite了,如['--suite', 'loginSuite'],这样protractor只会跑配置文件中定义的loginSuite所包括的spec文件了。

gulp-order

这个插件严格来说不是专门给Angular用的,但非常适合用在Angular的场景下。如果你的程序使用的是Angular自带的包管理系统,那么有一个无法避开的问题就是:所有angular.module的定义要最先执行,也就是说包含module定义的文件的script标签要在别的文件之前。而我们在使用gulp-inject这类插件将JS文件插入的时候通常都是通过匹配符直接选中一堆文件插入的。这时就需要解决插入的顺序问题,而这个插件就是干这个事的。它通过一个数组参数来指定排序,这个数组包含一组匹配模式,匹配到靠前模式的文件在前,匹配靠后的文件在后。如:

gulp
    .src('**/*.js')
    .pipe($.order([
        '**/app.module.js',
        '**/*.module.js',
        '**/*.js'
    ]))

这样,定义appmodule的文件就会在最前面,然后是其它各个module的定义,最后是剩余的JS文件。

压缩工具类

这个部分介绍对CSS、HTML、JS、图片等资源进行压缩的插件。

gulp-csso

压缩CSS的工具,官方说它比其它工具压得更小,因为它可以重建CSS代码结构信息,不知道什么鬼。

gulp-minify-html

压缩HTML的工具,通常在给gulp-angular-templatecache处理前先使用,这样$tempalteCache得到的就是压缩后的HTML字符串了。

gulp-uglify

压缩JS的工具,这个不多介绍了。

gulp-imagemin

压缩图片的工具。在发布到生产环境之前对图片进行压缩是一个非常好的习惯,可以极大的提高页面加载的速度。如果你用Google PageSpeed给网页评过分的话,它可以给出页面上能被继续压缩的图片。使用这个插件可以在保证质量损失很小的情况下压缩图片。

server相关

这部分介绍与本地起server相关的插件。

browser-sync

Browsersync应该算是本地起server的标配了吧,最大的特性是可以在不同浏览器之间同步(这也是名字的由来吧),这在测试时非常有用:起server以后根据配置自动打开多个浏览器,你操作一个,其他的浏览器会同步你的操作。另外,它还可以配合gulp的watch()函数实现类似live-reload的功能。之所以没有gulp对应的插件,是因为这玩意本来就可以直接require进来使用。支持非常多的参数,一整篇文章也介绍不完,具体可以参考它的文档。这里要简单介绍的就是server底下的配置,如:

server: {
    baseDir: './app/',
    index: 'index.html',
    middleware: [
        // middleware
    ]
}

前两个参数就不多说了,一看就明白意思,重点来看第3个参数middleware。middleware就是中间件,类似这样的函数function (req, res, next){}。简单来说,一个request请求到达server,会经过middleware数组里面的中间件函数逐个处理,这里就可以在Browsersync层面上定义很多自定义的操作。适用于Node的connect框架的中间件都可以在这里使用,下面介绍的两个插件都是可用于Browsersync的中间件。

connect-history-api-fallback

这个中间件对于像Angular这样的单页面应用来说非常的实用。我们知道,Browsersync默认起来的server是一个静态server,默认是无法支持$locationProvider.html5Mode(true);的。使用这个插件可以轻松的达到这一点,如:

var historyApiFallback = require('connect-history-api-fallback');
// ...
middleware: [
    historyApiFallback()
]

这样,所有的路由请求都会fallback到index.html处理,这也正是我们想要的。除此之外,这个插件还支持简单的rewrite,如

historyApiFallback({
  rewrites: [
    { from: /\/soccer/, to: '/soccer.html'}
  ]
});

可谓是非常方便,更多的rewrite规则可以参考它的文档。

proxy-middleware

这个中间件其实与上面rewrite的类似,只不过rewrite只是针对get请求,而这个proxy可以代理任何请求。设想这样一个场景,我们起了个本地的server,通常使用ngMock来实现对API的模拟,但有的时候我们希望这个本地的server可以对接真正的API server,而这个中间件可以轻松的完成这一点:将/api开头的请求代理到真正的API server上去。它的使用也是非常简单:

var proxy = require('proxy-middleware');
var proxyOptions = url.parse('https://real-server.com/api');
proxyOptions.route = '/api';
// ...
middleware: [
    proxy(proxyOptions)
]

这样,所有以/api开头的API请求就会被代理到https://real-server.com/api上去,如/api/user/123请求的真实地址是https://real-server.com/api/user/123

特定语言相关

这部分的插件与你选用的具体的语言以及预处理器有关。

gulp-jshintgulp-jscs

大名鼎鼎的和的gulp插件版。这两个插件除了帮你做代码的一些静态检查外,还可以最大程度的帮助你定义所需要的代码风格。尤其是jscs,定义的非常细致。比如我们需要function (a, b) {,即function关键字后面空一格,参数之间空一格,参数列表后面的小括号与大括号之间空一格。这样的需求通过jscs的配置文件可以轻松的实现,具体可以参考其文档。可以将这两个task放在build之前,强制所有人在build代码的时候修改不符合要求的代码风格。我们还尝试过将gulp jshint jscs放入git commit的hook中,每次commit的时候自动检查代码风格,如果不符合要求,不准进行commit的操作。

gulp-jade

编译jade模板的插件,这个也不过多介绍。只介绍一个使用jade变量的场景,通常我们的Angular应用的ng-app名称在测试(e2e)与非测试时是不一样的,所以可以把这个定义成变量,在编译jade模板时传入。如我们的index.jade的头是这样定义的html(lang="en", ng-app= app),在编译时使用.pipe($.jade({locals: {app: 'test'}}))即可指定想要的ng-app的名字。

gulp-stylus

编译stylus的插件,不多说。

gulp-autoprefixer

这个插件最早在从做简历中学到的知识这篇文章中就介绍过,只不过当时介绍的是grunt版本,现在时gulp版本。这个插件的基本作用就是让你在书写CSS3的相关属性时不用关心不同浏览器的前缀问题,它会自动帮助添加各种浏览器前缀。