最近花一点时间学了下 gulp,顺便学了下 sass,因为工作中并不需要用(我比较希望学习是需求驱动),所以一直拖到现在才学。突然觉得学习这类工具性价比很高,半天一天即可上手,技能树丰富了(尽管可能只会 20%,但是可以完成 80% 的工作了啊!),简历丰富了,所以才有这么多 前端er 不屑数据结构和算法这些基础吧,毕竟投入产出比太低,学一个简单的算法的时间都够掌握两遍基本的 gulp 工作流了!
言归正传,今天要讲的是 gulp 的增量编译。在编译的过程中,有没有发现很多不必要的编译呢?
我们以如下的例子为例:
let dest = 'css/';
// sass -> css
gulp.task('css', function() { // 任务名
return gulp.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']);
});
gulp watch
启动监听,此时修改 sass 文件夹下的任意文件,都会编译该文件夹下的所有文件,这不是我们希望的,我们希望只对修改过的文件进行编译,即增量编译(Incremental Builds)。
gulp 官方推荐了 4 个用于增量编译的插件,我们一起来看看使用方法以及它们的差异。
- gulp-changed - only pass through changed files
- gulp-cached - in-memory file cache, not for operation on sets of files
- gulp-remember - pairs nicely with gulp-cached
- gulp-newer - pass through newer source files only, supports many:1 source:dest
gulp-changed
首先推荐的是 gulp-changed,毕竟它的作者在 GitHub 上拥有 15k 的 followers。
该插件默认是通过比较源文件和生成文件的修改时间来判断是否将文件往下传递(pipe 流),当然也可以通过 haschanged 改变默认比较方式,采用 sha1 比较,感觉有点像浏览器缓存中的 Last-Modified/If-Modified-Since 和 ETag/If-None-Match。个人觉得默认比较已经足够了。
let dest = 'css/';
// sass -> css
gulp.task('css', function() { // 任务名
return gulp.src('sass/*.scss') // 目标 sass 文件
.pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
.pipe(changed(dest, { // dest 参数需要和 gulp.dest 中的参数保持一致
extension: '.css' // 如果源文件和生成文件的后缀不同,这一行不能忘
}))
// sass() will only get the files that
// changed since the last time it was run
.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 不同),怎么说?先用 gulp css
的命令编译一次,然后修改一个文件,再用 gulp css
编译一次,这样的操作是生效的。
第二点,因为 gulp-changed 只会将修改过的文件往下 pipe,所以如果后续有需要合并的操作(concat 操作),那么就会导致文件缺失,合并后的文件其实就是修改过的文件了。
所以 gulp-changed 只适合 1:1 的操作。
gulp-cached
和 gulp-changed 基于文件的对比不同,gulp-cached 可以将第一次传递给它的文件内容保留在内存中,如果之后再次执行 task,它会将传递给它的文件和内存中的文件进行比对,如果内容相同,就不再将该文件继续向后传递,从而做到了只对修改过的文件进行增量编译。
let dest = 'css/';
// sass -> css
gulp.task('css', function() { // 任务名
return gulp.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 必须要开启 gulp watch
,保证内存中存在副本,才能进行比较。
gulp-remember
gulp-remember 同样可以在内存中缓存所有曾经传递给它的文件,但是它和 gulp-cached 的区别是,在之后的 task 中,gulp-cached 会过滤掉未经修改的文件不再向下传递,而 gulp-remember 则会将未传递给它的文件进行补足从而能够继续向下传递,因此通过 gulp-cached 和 gulp-remember 的结合使用,既能做到只对修改过的文件进行编译,又能做到当相关联的文件任意一个发生改变时,编译所有相关的文件。所以我觉得实际开发中用 gulp-cached+gulp-remember 的组合非常合适。
let dest = 'css/';
// sass -> css
gulp.task('css', function() { // 任务名
return gulp.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 进行增量编译的例子:
let dest = 'css/';
// sass -> css
gulp.task('css', function() { // 任务名
return gulp.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 对 多进行增量编译的例子:
let dest = 'css/';
// sass -> css
gulp.task('css', function() { // 任务名
return gulp.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 也是可以的。
let dest = 'css/';
// sass -> css
gulp.task('css', function() { // 任务名
return gulp.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 中的增量编译
- gulp-newer vs gulp-changed
- 仅仅传递更改过的文件(gulp-changed)
- 增量编译打包,包括处理整所涉及的所有文件(gulp-cached+gulp-remember)
- 只重新编译被更改过的文件(gulp-watch)