你觉得一切尽在掌握中
你觉得你是操纵者
可是你特么一转身
啥都没了,都没了,没了
欧擦,滚吧 - 可特么的它又来了。
不断进化的前端开发环境,现在又有了一个新的构建系统,Gulp,它已经拿下了不少 web 开发人员了。
在花了一些时间来阅读文档和把玩 Gulp 之后,我最后决定试着把它用到一个既存的工程上面,目前这个工程是用 Grunt 构建的。就目前来看,Gulp 在处理和 Grunt 类似的任务的时候,还是很快的。
我们来稍微深挖一点,稍微理解一下 Grunt 和 Gulp 的异同,而不是肤浅的速度比较。
在这篇文章中,我们的讨论范围将覆盖:
第一印象
以我的使用经验来说,用 Grunt 的一个痛点在于配置过于复杂,即使对简单的任务来说也一样。就拿样式文件的编译来说。我的源文件是用 SCSS 来写的,它需要最终编译成 CSS 文件,我想通过 autoprefixer 提供商前缀来运行这些文件。并且我想每次源文件有变更的时候都实时编译。
那么下面是我用 Grunt 和 Gulp 完成这个任务的配置文件。
Gruntfile.js
<!-- lang: js -->
grunt.initConfig({
sass: {
dist: {
files: [{
cwd: 'app/styles',
src: '**/*.scss',
dest: '../.tmp/styles',
expand: true,
ext: '.css'
}]
}
},
autoprefixer: {
options: ['last 1 version'],
dist: {
files: [{
expand: true,
cwd: '.tmp/styles',
src: '{,*/}*.css',
dest: 'dist/styles'
}]
}
},
watch: {
styles: {
files: ['app/styles/{,*/}*.scss'],
tasks: ['sass:dist', 'autoprefixer:dist']
}
}
});
grunt.registerTask('default', ['styles', 'watch']);
Gulpfile.js
<!-- lang: js -->
gulp.task('sass', function () {
gulp.src('app/styles/**/*.scss')
.pipe(sass())
.pipe(autoprefixer('last 1 version'))
.pipe(gulp.dest('dist/styles'));
});
gulp.task('default', function() {
gulp.run('sass');
gulp.watch('app/styles/**/*.scss', function() {
gulp.run('sass');
});
});
如你所见,在 Gulp 中我们不需要中介 .tmp 文件夹来存储编译后,无前缀的 CSS 文件。这意味着更少的配置,以及节省 I/O 上的开支。
几次比较之后,Gulp 说,它在我机器上处理这些文件变更需要 2.13ms,而 Grunt 则花了 1.298s 。
Timing numbers reported by Grunt
Grunt timing
比较两个工具的耗时报告其实没什么意义,因为它们的机制不同。当然如果我用 time 把所有的任务(SASS 编译 + autoprefixer)都加进来的话,那么比较结果会更加接近: Gulp 用 0.641ms 而 Grunt 用 1.235s。当然,这还包括了工具本身的启动时间,所以它并不是一个严格的比较,看看就好!
Streams all the way down
要搞明白 Gulp 你需要理解节点流。所有的 Gulp 插件都是通过流来输入输出数据的。所有的处理都在内存中进行,输出流通过管道输入下一个。看起来就和 Unix 的管道一样。
这让 Gulp 相对于 Grunt 有了巨大的速度优势,因为相对于内存操作 I/O 操作就显得开销非常大了。最重要的还有,即使只是其中一个文件发生了变化,Grunt 也会把所有的文件编译一次,这无疑增加了额外的开销。
In Grunt, we must write intermediary files to disk
In Gulp, we pipe the intermediary files in-memory to other streams
这也意味着,Gulp 插件其实只是映射流。而 Grunt 则是完整的,比如说在服务器上运行 livereload 。在 Gulp 中,你需要节点中做一些编码来实现同样的功能。
gulp-reload 例子:
<!-- lang: js -->
var lr = require('tiny-lr'),
gulp = require('gulp'),
less = require('gulp-less'),
livereload = require('gulp-livereload'),
server = lr();
gulp.task('less', function () {
gulp.src('less/*.less')
.pipe(less())
.pipe(gulp.dest('css'))
.pipe(livereload(server));
});
gulp.task('watch', function () {
server.listen(35729, function (err) {
if (err) return console.log(err);
gulp.watch('less/*.less', function () {
gulp.run('less');
});
});
});
那么问题来了,编译系统哪家强?
我不可能告诉你哪个比较好,因为那是你个人爱好。
我希望在 Grunt 0.5 发布之后,两者之间的速度差能够缩小一点。在 Grunt 0.5 的路线图上,包括了多任务之间的管道数据传输的支持,以及将输出作为数据事件广播的支持。这样的话任务执行肯定会有显著的速度提升,并且可能因为而不需要临时文件的配置,而变得更简洁。
目前 Grunt 相对 Gulp 的一个优势是,有更丰富的社区支持,有更多的可用插件。这种情况,当然,随着更多的开发者采用 Gulp,有可能会发生改变,但是记住一点,Gulp 插件和 Grunt 插件是截然不同的两种,所以不要指望 Grunt 插件能 100% 的迁移到 Gulp 上。
还有一个问题是,你更赞同哪一方的理念?对于一个编译系统你会更喜欢用代码来改变行为还是更喜欢用配置文件?如果是代码,那么你一定会很喜欢 Gulp,否则,继续坚持 Grunt。
扩展阅读 & 信息
GulpJS
GruntJS
Gulp, Grunt, Whatever
And just like that Grunt and RequireJS are out, it’s all about Gulp and Browserify now
Speedtesting gulp.js and Grunt
Edit #1 on 2014/01/28: 我加了个时间比较,虽然这不是我们讨论的重点。
Edit #2 on 2014/01/30: 某读者指出用 grunt-sass 比较会比用 grunt-contrib-compass 比较更合适,因为基于 node-sass ,这也是 gulp-sass 所采用的。我更新了这部分的对比。