这里介绍一些gulp比较常用的插件,包括util工具类、stream相关、inject相关、Angular相关、压缩工具类、server相关、特定语言相关等。
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
1 | ; |
copy.js
1 | ; |
gulpfile.js
1 | ; |
可以看到copy.js中最后有这样一行代码module.exports.dependencies = ['clean'];
,这个是用做任务依赖的,只有前一个任务执行完毕后才能执行当前任务,使用起来还是很简单的
gulp-task-listing
这个插件的作用也很容易猜,它可以打印出gulpfile.js
中定义的所有task,这个插件我们在重构你的gulpfile这篇文章的最后介绍过,值得一提的是它还可以根据task的名字确定它是不是一个子task,比如带有:
、-
、_
的task就被认为是子task。我一般把这个插件作为默认的task来调用,如
1 | gulp.task('default', ['help']); |
这样,如果只执行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
这是一个统计文件大小变化的工具,通常与压缩类工具放在一起实用,比如
1 | gulp.src('**/*.html') |
在压缩的pipe前后加上$.bytediff.start()
和$.bytediff.stop(callback)
,即可统计压缩前后文件的变化。在callback中传入的参数data上,可以访问到很多变量,如文件名,变化前后的大小,变化百分比等等。
gulp-print
这个插件的作用很简单,打印出stream里面的所有文件名,通常调试的时候比较需要。
gulp-bump
这个插件也可以顾名思义:用来升级版本用的,废话不说,直接看例子吧:
1 | return gulp |
重点来看这里的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
- prerelease:
最终这个升级后的版本号会反映在package.json
中,当然,你也可以在gulp.src中传入更多的文件(如bower.json
)来替换更多的文件。
gulp-header
这个工具用来在压缩后的JS、CSS文件中添加头部注释,你可以包含任意想要的信息,通常就是作者、描述、版本号、license等,比如:
1 | function getHeader () { |
这个函数将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,最后要把所有的文件都拷贝到最终的位置。代码如下:
1 | var filter = $.filter('**/a/*.js'); |
可以看到,如果没有restore这个操作,那么拷贝到最终位置的文件将只包含被过滤出来的文件,这样一restore,所有的文件都被拷贝了。
gulp-flatten
gulp-flatten非常实用,可能知道别的库中flatten函数的同学已经猜到它是干嘛的了。比如gulp.src('**/*.js')
匹配了很多文件,包括a/b/c.js
,d/e.js
,f/g/h/i/j/k.js
,l.js
,这些文件的层级都不一样,一旦我们将这个文件pipe给$.flatten()
,则所有的文件夹层级都会去掉,最终的文件将是c.js
,e.js
,k.js
,l.js
,在一些场景下还是非常有用的。
gulp-plumber
这个插件的作用简单来说就是一旦pipe中的某一steam报错了,保证下面的steam还继续执行。因为pipe默认的onerror函数会把剩下的stream给unpipe掉,这个插件阻止了这种行为。那它一般用于哪种场景呢?比如,代码每次build之前要跑一遍jshint和jscs来确保所有代码都符合规范,但一旦某些代码不符合规范,整个build流程就会停止,这个时候就需要gulp-plumber出场了。如:
1 | gulp.task('build', ['jslint', 'xxxx']); |
这样,一旦jshint或jscs报错,整个build流程还是可以继续走下去的,而且gulp-plumber会给出一个报错提醒我们:
1 | [16:52:36] Plumber found unhandled error: |
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.src
和gulp.dest
的参数不同,我们就需要写两个task来分别完成这个任务,一方面略显重复,另一方面逻辑上来讲这两个task本来就是处理同样的事情的。这种情况就需要merge-stream登场了,它的作用就是将多个stream合成一个返回。比如下面这个例子:
1 | var merge = require('merge-stream'); |
可以看到,处理的流程被提取出来放入一个函数,它接受两个参数,分别是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中的注释来识别插入位置,如
1 | <!-- bower:css --> |
不同类型的文件被插入到不同的区块。
- 插入什么文件:要插入的文件列表自然来自
bower.json
,每个bower安装的依赖库,根目录下边都有一个自己的bower.json
文件,其中的main
字段指明了使用这个库需要包含的文件,wiredep最终包含的文件列表就来自这个字段。有些情况下,库自身的bower.json
的main字段可能会多包含文件或少包含文件,如果想要定制这个列表,则可以在自己的bower.json
中使用overrides
字段,如下面的代码覆盖了mdi
这个库的main
字段。
1 | "overrides": { |
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,不需要真正的去读取文件。
- name:默认识别的注释标签格式为
这个插件的使用场景通常是,我们需要index里有多个区块,比如上面name的例子,只有当为production环境编译的时候才去包含相关的文件。
gulp-useref 与 gulp-rev、gulp-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中的注释:
1 | <!-- build:css static/styles/lib.css --> |
gulp-useref识别的就是build开头的注释,build后面首先跟的是类型扩展名,然后后面的路径就是build区块中的所有文件进行合并后的文件路径,这个相对路径是相对于这个HTML的路径。上面的例子中我们用build区块把bower和inject进来的文件包起来,这些文件就可以被gulp-useref合并了。再来看gulp中useref相关task的定义:
1 | var assets = $.useref.assets({searchPath: 'app/src/'}); |
首先一上来,先调用$.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:
1 | { |
当然这个文件默认是不会生成在文件系统里的,可以通过.pipe($.rev.manifest())
将这个文件保存到本地。有了这个文件,gulp-rev-replace甚至可以脱离gulp-rev独立工作哦!
gulp-html-replace
gulp-html-replace同样是识别以build开头的注释,与gulp-useref不同的是不会对build区块中的所有文件进行合并,而是根据配置直接替换掉build区块中的内容下面以一个例子做说明:
html片段
1 | <html> |
相关任务:
1 | gulp.src('index.html') |
执行任务后的html片段:
1 | <html> |
很简单,不用再做过多的说明
gulp-rev-hash2
这个插件是在引用的静态文件后面添加版本号的,这个版本号是被引用的文件的md5值,所以说这个插件和实用,不多说,直接上代码
gulpfile.js
1 | var gulp = require('gulp'); |
输入:
1 | <link rel="stylesheet" href="main.min.css"/> |
输出:
1 | <link rel="stylesheet" href="main.min.css?rev=9d58b7441d92130f545778e418d1317d"> |
用法也是很简单的,具体的可以看api
gulp-file-concat
顾名思义,文件合并,这个插件可以合并通过document.write引入的js和通过@import引入的css,下面看一个例子:
index.js:
1 | (function() { |
index.css:
1 | @import url("a.css"); |
gulpfile.js:
1 | var gulp = require('gulp'); |
这个一个文件合并的插件,为什么要把这个插件放在这里来介绍,其实我觉得这个可以和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
:指定注册后的模板路径前缀。
生成后的文件如下:
1 | angular.module("module name").run([$templateCache, |
gulp-ng-annotate
这个插件是ng-annotate的gulp插件版,它解决的是Angular中依赖注入的小问题。Angular中通过参数名来进行依赖注入,一旦压缩,参数名就会变化导致注入失败,所以官方推荐通过添加字符串进行注入。比如:
1 | angular |
上面的例子中我们定义了一个叫DashboardController
的controller,它依赖一个userAPI
的service。这个插件的作用就是根据第6行的注释/* @ngInject */
来帮你生成第5行的内容。当然是在你忘记写的情况下,如果你自己写了它不会重复生成。除了这种使用$inject
赋值的方式,它同样支持inline定义的方式,如
1 | /* @ngInject */ |
会生成
1 | .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文件插入的时候通常都是通过匹配符直接选中一堆文件插入的。这时就需要解决插入的顺序问题,而这个插件就是干这个事的。它通过一个数组参数来指定排序,这个数组包含一组匹配模式,匹配到靠前模式的文件在前,匹配靠后的文件在后。如:
1 | gulp |
这样,定义app
module的文件就会在最前面,然后是其它各个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底下的配置,如:
1 | server: { |
前两个参数就不多说了,一看就明白意思,重点来看第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);
的。使用这个插件可以轻松的达到这一点,如:
1 | var historyApiFallback = require('connect-history-api-fallback'); |
这样,所有的路由请求都会fallback到index.html处理,这也正是我们想要的。除此之外,这个插件还支持简单的rewrite,如
1 | historyApiFallback({ |
可谓是非常方便,更多的rewrite规则可以参考它的文档。
proxy-middleware
这个中间件其实与上面rewrite的类似,只不过rewrite只是针对get请求,而这个proxy可以代理任何请求。设想这样一个场景,我们起了个本地的server,通常使用ngMock来实现对API的模拟,但有的时候我们希望这个本地的server可以对接真正的API server,而这个中间件可以轻松的完成这一点:将/api
开头的请求代理到真正的API server上去。它的使用也是非常简单:
1 | var proxy = require('proxy-middleware'); |
这样,所有以/api
开头的API请求就会被代理到https://real-server.com/api
上去,如/api/user/123
请求的真实地址是https://real-server.com/api/user/123
。
特定语言相关
这部分的插件与你选用的具体的语言以及预处理器有关。
gulp-jshint 与 gulp-jscs
大名鼎鼎的jshint和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的相关属性时不用关心不同浏览器的前缀问题,它会自动帮助添加各种浏览器前缀。