配色: 字号:
gulp 中的增量编译
2017-01-09 | 阅:  转:  |  分享 
  
gulp中的增量编译



最近花一点时间学了下gulp,顺便学了下sass,因为工作中并不需要用(我比较希望学习是需求驱动),所以一直拖到现在才学。突然觉得学习这类工具性价比很高,半天一天即可上手,技能树丰富了(尽管可能只会20%,但是可以完成80%的工作了啊!),简历丰富了,所以才有这么多前端er不屑数据结构和算法这些基础吧,毕竟投入产出比太低,学一个简单的算法的时间都够掌握两遍基本的gulp工作流了!



言归正传,今天要讲的是gulp的增量编译。在编译的过程中,有没有发现很多不必要的编译呢?



我们以如下的例子为例:



复制代码

letdest=''css/'';



//sass->css

gulp.task(''css'',function(){//任务名

returngulp.src(''sass/.scss'')//目标sass文件

.pipe(plumber({errorHandler:notify.onError(''Error:<%=error.message%>'')}))

.pipe(sass())//sass->css

.pipe(debug({title:''编译''}))

.pipe(gulp.dest(dest));

});



gulp.task(''watch'',function(){

gulp.watch(''sass/.scss'',[''css'']);

});

gulpwatch启动监听,此时修改sass文件夹下的任意文件,都会编译该文件夹下的所有文件,这不是我们希望的,我们希望只对修改过的文件进行编译,即增量编译(IncrementalBuilds)。



gulp官方推荐了4个用于增量编译的插件,我们一起来看看使用方法以及它们的差异。



gulp-changed-onlypassthroughchangedfiles

gulp-cached-in-memoryfilecache,notforoperationonsetsoffiles

gulp-remember-pairsnicelywithgulp-cached

gulp-newer-passthroughnewersourcefilesonly,supportsmany:1source:dest

gulp-changed



首先推荐的是gulp-changed,毕竟它的作者在GitHub上拥有15k的followers。



该插件默认是通过比较源文件和生成文件的修改时间来判断是否将文件往下传递(pipe流),当然也可以通过haschanged改变默认比较方式,采用sha1比较,感觉有点像浏览器缓存中的Last-Modified/If-Modified-Since和ETag/If-None-Match。个人觉得默认比较已经足够了。



复制代码

letdest=''css/'';



//sass->css

gulp.task(''css'',function(){//任务名

returngulp.src(''sass/.scss'')//目标sass文件

.pipe(plumber({errorHandler:notify.onError(''Error:<%=error.message%>'')}))

.pipe(changed(dest,{//dest参数需要和gulp.dest中的参数保持一致

extension:''.css''//如果源文件和生成文件的后缀不同,这一行不能忘

}))

//sass()willonlygetthefilesthat

//changedsincethelasttimeitwasrun

.pipe(sass())//sass->css

.pipe(debug({title:''编译''}))

.pipe(gulp.dest(dest))

});



gulp.task(''watch'',function(){

gulp.watch(''sass/.scss'',[''css'']);

});

值得注意的是,如果源文件和生成文件的后缀不一样,需要加上extension参数。



个人认为还有两点需要注意。



第一点是因为gulp-changed是基于文件的判断,所以并不一定需要开启watch(这和接下去要说的gulp-cached不同),怎么说?先用gulpcss的命令编译一次,然后修改一个文件,再用gulpcss编译一次,这样的操作是生效的。



第二点,因为gulp-changed只会将修改过的文件往下pipe,所以如果后续有需要合并的操作(concat操作),那么就会导致文件缺失,合并后的文件其实就是修改过的文件了。



所以gulp-changed只适合1:1的操作。



gulp-cached



和gulp-changed基于文件的对比不同,gulp-cached可以将第一次传递给它的文件内容保留在内存中,如果之后再次执行task,它会将传递给它的文件和内存中的文件进行比对,如果内容相同,就不再将该文件继续向后传递,从而做到了只对修改过的文件进行增量编译。



复制代码

letdest=''css/'';



//sass->css

gulp.task(''css'',function(){//任务名

returngulp.src(''sass/.scss'')//目标sass文件

.pipe(plumber({errorHandler:notify.onError(''Error:<%=error.message%>'')}))

.pipe(cached(''sass-task''))//取个名字

.pipe(sass())//sass->css

.pipe(debug({title:''编译''}))

.pipe(gulp.dest(dest))

});



gulp.task(''watch'',function(){

gulp.watch(''sass/.scss'',[''css'']);

});

和gulp-changed不同的是,gulp-cached必须要开启gulpwatch,保证内存中存在副本,才能进行比较。



gulp-remember



gulp-remember同样可以在内存中缓存所有曾经传递给它的文件,但是它和gulp-cached的区别是,在之后的task中,gulp-cached会过滤掉未经修改的文件不再向下传递,而gulp-remember则会将未传递给它的文件进行补足从而能够继续向下传递,因此通过gulp-cached和gulp-remember的结合使用,既能做到只对修改过的文件进行编译,又能做到当相关联的文件任意一个发生改变时,编译所有相关的文件。所以我觉得实际开发中用gulp-cached+gulp-remember的组合非常合适。



复制代码

letdest=''css/'';



//sass->css

gulp.task(''css'',function(){//任务名

returngulp.src(''sass/.scss'')//目标sass文件

.pipe(plumber({errorHandler:notify.onError(''Error:<%=error.message%>'')}))

.pipe(cached(''sass-task''))//取个名字

.pipe(sass())//sass->css

.pipe(debug({title:''编译''}))

.pipe(gulp.dest(dest))

.pipe(remember(''sass-task''))//和cached()参数一致

.pipe(concat(''all.css''))

.pipe(debug({title:''合并''}))

.pipe(gulp.dest(dest))

});



gulp.task(''watch'',function(){

gulp.watch(''sass/.scss'',[''css'']);

});

由于在第一次合并文件时,gulp-remember已经将传递过来的文件缓存在内存中了,那么即使在后续的task执行中,gulp-cached插件过滤掉了未经修改过的css文件,但是gulp-remember还是能够通过自己的缓存来补全这些缺失的文件,从而做到正确地合并文件。



我们还可以合理地管理两个插件的缓存,具体见文档。



gulp-newer



gulp-newer和gulp-changed类似,也是基于文件对比,不过它只支持最后修改时间的对比。



1:1进行增量编译的例子:



复制代码

letdest=''css/'';



//sass->css

gulp.task(''css'',function(){//任务名

returngulp.src(''sass/.scss'')//目标sass文件

.pipe(plumber({errorHandler:notify.onError(''Error:<%=error.message%>'')}))

.pipe(newer({

dest:dest,

ext:''.css''

}))

.pipe(sass())//sass->css

.pipe(debug({title:''编译''}))

.pipe(gulp.dest(dest))

});



gulp.task(''watch'',function(){

gulp.watch(''sass/.scss'',[''css'']);

});

1对多进行增量编译的例子:



复制代码

letdest=''css/'';



//sass->css

gulp.task(''css'',function(){//任务名

returngulp.src(''sass/.scss'')//目标sass文件

.pipe(plumber({errorHandler:notify.onError(''Error:<%=error.message%>'')}))

.pipe(newer(''dest''+''all.css''))

.pipe(sass())

.pipe(concat(''all.css''))

.pipe(gulp.dest(dest));

});



gulp.task(''watch'',function(){

gulp.watch(''sass/.scss'',[''css'']);

});

遗憾的是,貌似不能同时1:1&1:多进行编译(此处问号脸):)



gulp-watch



除了以上4个插件外,用gulp-watch也是可以的。



复制代码

letdest=''css/'';



//sass->css

gulp.task(''css'',function(){//任务名

returngulp.src(''sass/.scss'')//目标sass文件

.pipe(plumber({errorHandler:notify.onError(''Error:<%=error.message%>'')}))

.pipe(watch(''sass/.scss''))

.pipe(sass())

.pipe(debug({title:''编译''}))

.pipe(gulp.dest(dest));

});



gulp.task(''watch'',function(){

gulp.watch(''sass/.scss'',[''css'']);

});

gulp-watch也不能使用类似gulp-concat的工具进行一对多的编译。



之前关于gulp的基础笔记都不敢往首页发,本文发到首页,除了觉得很多人使用gulp可能不关注增量编译外,还想知道大家在实际的开发中使用的增量编译插件方式是?个人觉得gulp-cached+gulp-remember的搭配不错,不过我还没在实际开发中用过gulp,所以想听听老司机的意见。



参考资料:



Gulp.js是目前前端非常流行的自动化构建工具,它基于流,代码优于配置,API简单,又有着大量优秀的第三方插件。它的gulp.watch()接口,可以让我们监听文件改动而进行自动构建,但是如果存在耗时的任务或者随着项目的逐渐增大,可能每次构建都要花费很多的时间,那么在Gulp中有什么解决的办法呢?



其实在Gulp的文档中对于增量编译有推荐下面4个插件:



gulp-changed-onlypassthroughchangedfiles

gulp-cached-in-memoryfilecache,notforoperationonsetsoffiles

gulp-remember-pairsnicelywithgulp-cached

gulp-newer-passthroughnewersourcefilesonly,supportsmany:1source:dest

那么他们的具体用法和区别是什么呢?



gulp-cached



gulp-cached可以将第一次传递给它的文件内容保留在内存中,如果之后再次执行task,它会将传递给它的文件和内存中的文件进行比对,如果内容相同,就不再将该文件继续向后传递,从而做到了只对修改过的文件进行增量编译。



vargulp=require(''gulp'');

varless=require(''gulp-less'');

varcached=require(''gulp-cached'');



gulp.task(''less'',function(){

gulp.src(''./src/.less'')

.pipe(cached(''less''))

.pipe(less())

.pipe(gulp.dest(''./dist''));

});



gulp.task(''watch'',function(){

gulp.watch(''src/.less'',[''less'']);

});

gulp-cached插件还可以接收一个可选的optimizeMemory参数,插件默认会将文件内容保存在内存中,如果将optimizeMemory设置为true,那么会转而将文件的md5值保留在内存中,从而减少对内存的占用,但是另一方面计算md5值也会消耗更多的时间,插件的作者建议在一般情况下,并不需要开启这个功能。



但是,gulp-cached在使用时也有一些限制,比如下面的例子:



vargulp=require(''gulp'');

varcached=require(''gulp-cached'');

varremember=require(''gulp-remember'');

varless=require(''gulp-less'');

varconcat=require(''gulp-concat'');



gulp.task(''concat'',function(){

gulp.src(''./src/.less'')

.pipe(cached(''concat''))

.pipe(less())

.pipe(remember(''concat''))

.pipe(concat(''all.css''))

.pipe(gulp.dest(''./dist''));

});



gulp.task(''watch'',function(){

gulp.watch(''src/.less'',[''concat'']);

});

这是一个监听less文件改动并自动编译成css文件后合并的task,考虑一下这种情况,在修改文件a.less后,触发了concat的task,但是由于此时并没有修改文件b.less,所以传递给gulp-concat插件的b.less被gulp-cached过滤掉了,导致最后生成的all.css文件中只有修改后的a.less编译成的内容。那如何解决这个问题呢,此时就需要借助gulp-remember了。



gulp-remember



gulp-remember同样可以在内存中缓存所有曾经传递给它的文件,但是它和gulp-cached的区别是,在之后的task中,gulp-cached会过滤掉未经修改的文件不再向下传递,而gulp-remember则会将未传递给它的文件进行补足从而能够继续向下传递,因此通过gulp-cached和gulp-remember的结合使用,既能做到只对修改过的文件进行编译,又能做到当相关联的文件任意一个发生改变时,编译所有相关的文件。



vargulp=require(''gulp'');

varcached=require(''gulp-cached'');

varremember=require(''gulp-remember'');

varless=require(''gulp-less'');

varconcat=require(''gulp-concat'');



gulp.task(''concat'',function(){

gulp.src(''./src/.less'')

.pipe(cached(''concat''))

.pipe(less())

.pipe(remember(''concat''))

.pipe(concat(''all.css''))

.pipe(gulp.dest(''./dist''));

});



gulp.task(''watch'',function(){

gulp.watch(''src/.less'',[''concat'']);

});

由于在第一次合并文件时,gulp-remember已经将传递过来的文件缓存在内存中了,那么即使在后续的task执行中,gulp-cached插件过滤掉了未经修改过的less文件,但是gulp-remember还是能够通过自己的缓存来补全这些缺失的文件,从而做到正确地合并文件。你可能要问了对于这种情况,为什么还要额外引入两个插件,直接监听文件改动重新编译不就好了么?



gulp.task(''concat'',function(){

gulp.src(''./src/.less'')

.pipe(less())

.pipe(concat(''all.css''))

.pipe(gulp.dest(''./dist''));

});

相比上面这种不使用插件方式,gulp-cached和gulp-remember的结合使用还是有一定优势的:

gulp-remember缓存的是less文件编译后生成的css文件,这样只有修改了的less文件才需要重新编译成css文件,其他less文件对应的css文件可以直接从gulp-remember的缓存中读取,而不使用插件的方式每次都要重现编译所有的less文件。



另外,不要忘了我们还可以合理的管理两个插件的缓存:



gulp.task(''watch'',function(){

varwatcher=gulp.watch(''./src/.less'',[''concat'']);

watcher.on(''change'',function(event){

console.log(event.type);

if(event.type===''deleted''){

deletecached.caches[''concat''][event.path];

remember.forget(''concat'',require(''gulp-util'').replaceExtension(event.path,''.css''));

}

});

});

gulp-changed



gulp-changed插件也能够像gulp-cached插件一样过滤未修改过的文件做到增量编译,不同之处主要在于如何判断文件被修改过,gulp-cached是通过对文件设置缓存来进行比较,而gulp-changed则是通过比较源文件和生成文件。



vargulp=require(''gulp'');

varchanged=require(''gulp-changed'');

varimagemin=require(''gulp-imagemin'');

varpngquant=require(''imagemin-pngquant'');



gulp.task(''img'',function(){

gulp.src(''./src/img/'')

.pipe(changed(''./dist/img''))

.pipe(imagemin({

progressive:true,

use:[pngquant()]

}))

.pipe(gulp.dest(''./dist/img''));

});



gulp.task(''watch'',function(){

gulp.watch(''src/img/.png'',[''img'']);

});

由于gulp-changed是比较源文件和生成文件,所以调用插件的时候,要传入生成的位置,一般就是最后gulp.dest()方法要传入的参数。插件默认比较的是文件修改的时间,如果不同,就说明源文件有被修改过。另外可以通过hasChanged参数来使用插件内置的另一种

比较方式:changed(''dist'',{hasChanged:changed.compareSha1Digest}),也就是通过计算文件内容的sha1值来比较,一般情况下,生成文件的内容都是不同于源文件的,除非只是简单的拷贝,所以说基本上没什么用,hasChwww.baiyuewang.netanged参数也支持传入一个函数来进行自定义比较。



在使用gulp-changed插件时有一个需要注意的地方,如果在task中改变了文件的后缀名,那么就需要通过extension参数来指定新的后缀名,否则插件无法找到生成的文件,比较令人遗憾的是,对于这种情况gulp-changed插件不会有任何提示,只是默默的执行了task,但是完全没有起到增量编译的目的。



vargulp=require(''gulp'');

varchanged=require(''gulp-changed'');

varless=require(''gulp-less'');



gulp.task(''less'',function(){

gulp.src(''./src/.less'')

.pipe(changed(''./dist'',{

extension:''.css''

}))

.pipe(less())

.pipe(gulp.dest(''./dist''));

});



gulp.task(''watch'',function(){

gulp.watch(''src/.less'',[''less'']);

});

gulp-newer



gulp-newer既可以像gulp-cached/gulp-changed那样1对1地进行增量编译,也可以像gulp-cached配合gulp-remember那样多对1地进行增量编译。它实现增量编译的原理和gulp-changed相同,都是通过比较源文件和生成文件,只不过它只支持比较修改时间。



先来看一个1对1进行增量编译的例子:



vargulp=require(''gulp'');

varnewer=require(''gulp-newer'');

varless=require(''gulp-less'');



gulp.task(''less'',function(){

gulp.src(''./src/.less'')

.pipe(newer({

dest:''./dist'',

ext:''.css''

}))

.pipe(less())

.pipe(gulp.dest(''./dist''));

});



gulp.task(''watch'',function(){

gulp.watch(''src/.less'',[''less'']);

});

多对1增量编译的例子如下:



vargulp=require(''gulp'');

varnewer=require(''gulp-newer'');

varless=require(''gulp-less'');



gulp.task(''less'',function(){

gulp.src(''./src/.less'')

.pipe(newer(''./dist/all.css''))

.pipe(less())

.pipe(concat(''all.css''))

.pipe(gulp.dest(''./dist''));

});



gulp.task(''watch'',function(){

gulp.watch(''src/.less'',[''less'']);

});

我目前的看法是借助gulp-newer进行多对1的增量编译和直接监听文件自动编译的区别并不是很大(它和gulp-cached配合gulp-remember使用还是有区别的,没有办法带来之前说的缓存编译结果的好处),可能唯一的好处就是下面说的这种情况:通过gulp.watch()监听了所有的文件,但是触发task的源文件只有a文件和b文件,那么此时修改不相关的c文件,gulp-newer可以防止重复编译a文件和b文件情况的出现。关于gulp-newer和gulp-changed,你也可以参考下Stackoverwww.tt951.comflow上对他们的比较。



gulp-watch



除了上面介绍的和gulp.watch()配合使用的4个插件外,我们还可以直接借助gulp-watch插件来完成增量编译。



vargulp=require(''gulp'');

varwatch=require(''watch'');

varless=require(''less'');



gulp.task(''watch-less'',function(){

gulp.src(''./src/.less'')

.pipe(watch(''./src/.less''))

.pipe(less())

.pipe(gulp.dest(''./dist''));

});

gulp-watch可以做到和gulp原生APIgulp.watch()一样监听文件改动,并且由于它是整个task的一环,因此每次文件改动时,只有这个被改动的文件会被gulp-watch继续向下传递,而且这种写法也更接近于我们使用gulp插件的方式。另外还有的一个好处是执行watch-lesstask,会自动运行一次编译less的task,而gulp.watch()在启动时,则什么都不会做。不过gulp-watch也有它的限制,无法处理concat这样的task,具体原因可以参考这里。

献花(0)
+1
(本文系thedust79首藏)