大约是上一年的这个时候,因为项目合并来到了新的项目组中。虽然协作的同岗位同事也是同一个组的,但使用的Gulp 工作流却有些不一样。第一天做项目需求的时候,就遇到了一件让我瞠目结舌的事情:这里的Sass 编译一次居然要10s 以上。
有用过Sass 这类CSS 预处理器都知道10s+ 意味着什么,你每保存一次.scss
文件,都必须等上10s 以上才能看到你所改动的效果。如此一来十分尴尬,因为你直接写原生的CSS 语法比这还快。让我更加惊讶的是当时的项目组相关同事已经用了这套工作流快一年了,却居然能一直默默忍受这种编译速度。
后面的故事倒有些题外话了,简单概括是我在接下来的半年发挥了一些“主观能动性”将整个工作流做了一番优化。其中涉及到的就是本文所言的在Gulp 工作流中Sass 增量编译功能的探索。
初级玩家的玩法
Gulp 工作流中集成Sass 编译一般都是用gulp-sass
这个模块,本质上gulp-sass
调用的是node-sass(C++ 版的Sass)。熟悉Gulp 的都知道,默认的话Sass 中都是全量编译的,小项目玩家或初级玩家一般直接这么写:
var gulp = require('gulp'); var sass = require('gulp-sass'); gulp.task('sass', function () { return gulp.src('./sass/**/*.scss') .pipe(sass().on('error', sass.logError)) .pipe(gulp.dest('./css')); }); |
当你的CSS 项目足够大的时候,这种全量编译的方式固然会导致瓶颈的出现。是时候要考虑增量编译了(Incremental Builds)。
增量更新的社区方案
打开Gulp 的Github 主页,README 处拉到快底部,开源社区早早就有产出一系列解决方案。gulp-changed、gulp-cached 、gulp-remember、gulp-newer 等等。这几个Gulp 插件的区别有兴趣的可以自行去研究在这里不累赘述了。结合本文所言的需求,上面几个插件中,gulp-cached “好像”就能满足我们的需求。
var gulp = require('gulp'); var sass = require('gulp-sass'); var cached = require('gulp-cached'); gulp.task('sass', function() { return gulp.src('src/styles/**/*.scss') .pipe( cached('sass')) .pipe(sass()) .pipe(gulp.dest('dist')); }); |
如上面的写法,就能做到你修改了a.scss
,在接下来的pipe 中 a.scss
才会去编译;修改了b.scss
,接下来的pipe 中只有 b.scss
去编译而a.scss
不会。看起来好像已经做到了增量编译了,不是么?
兼顾Sass 依赖关系的增量编译
熟悉的CSS 预处理器的都知道,一个scss 文件中可能会(被)@import,@include ,@extend
了外部scss 或相关代码段。如此如果按照上面的方案,当a.scss
@import 了_c.scss
,而当你改动了_c.scss
,在上面的机制下,a.scss
的出口文件a.css
是没有被相应更新到的。上面这种粗暴的增量更新机制并没有考虑到Sass 中存在的依赖关系。
解决方法也呼之欲出了,在cached()
与 sass()
的pipe 的中间我们还需要一个步骤,即将传入的改动文件找出其上下关系的依赖文件,整体文件集传入到 sass()
的pipe 去执行编译。社区中也早早有这个解决方案,sass-graph 便是这么个专门分析依赖文件的第三方模块。而对应的Gulp 插件也有不少,不过本人使用的是在前人的基础上二次开发的 gulp-better-sass-inheritance。
兼顾Sass 依赖关系的增量编译,应该是这样的:
// https://devework.com/sass-incremental-builds-in-gulp.html var gulp = require('gulp'); var sassInheritance = require('gulp-better-sass-inheritance'); var sass = require('gulp-sass'); var cached = require('gulp-cached'); var gulpif = require('gulp-if'); gulp.task('sass', function() { return gulp.src('src/styles/**/*.scss') //filter out unchanged scss files, only works when watching .pipe(gulpif(global.isWatching, cached('sass'))) //find files that depend on the files that have changed .pipe(sassInheritance({base: 'src/styles/'})) //process scss files .pipe(sass()) //save all the files .pipe(gulp.dest('dist')); }); gulp.task('watch', ['sass', 'other-task'], function() { global.isWatching = true; //your watch functions... }); |
Gulp 4 中的增量编译
上面的这套方案执行后,我们在执行Gulp 进程中,除第一次第二次,从第三次编译开始就是增量编译了,时间也将为1s 以内。这套方案我们一直用了很长一段时间,直到我们的Gulp 工作流中更新到了Gulp 4。
Gulp 4 到现在两年多了一直都没有正式版(2018.1.1更新:已经发布,详情),但用在生产环境中其实是一点问题都没有(就是安装的时候麻烦些)。Gulp 4 中自带了增量更新的方案gulp.lastRun()
,gulp.lastRun()
可以取代Gulp 3 中如gulp-cached 这类插件。
于是,我们的gulpfile.js 中,核心代码部分是类似下面这样的:
// https://devework.com/sass-incremental-builds-in-gulp.html function sass () { return gulp.src(sassFilesRrc, {since: gulp.lastRun(sass)}) .pipe($.sassInheritance({base: 'src/styles/'})) .pipe($.sass({errLogToConsole: true, outputStyle: 'expanded'}) .on('error', $.sass.logError)) .pipe(gulp.dest(config.paths.dist.cssDir)) } |
小结
以上就是本人在Gulp 工作流中Sass 增量编译功能的探索。其实说是探索,不过是了解有哪些成熟的社区方案,一次次试用来找到最合适自己的方案。整体而言并不是什么牛逼的事情,但就是看你选择去忍受每一次的10s+ 还是选择去改进了。团队工作中,当越来越多人从新人变成老人,他们会不经意掉入固化的思维圈中,大多数时候选择去跟随,遇到不满选择忍受,看到缺陷选择妥协。只有少数人在创造,遇到不满选择抗争,看到缺陷选择动手,因此机会往往在这些少数人手上产生。