前端工程的构建工具对比
前端
早些年提到构建工具,不免会让人联想到历史比较悠久的Make
,Ant
,以及后来为了更方便的构建结构相似的Java项目而出现的Maven
。Node催生了一批自动化工具,像Bower,Yeoman,Grunt等。而现在前端提到构建工具会天然想起Grunt
。Java世界里的Maven提供了强大的包依赖管理和构建生命周期管理。node
在JavaScript的世界里,Grunt.js是基于Node.js的自动化任务运行器。2013年02月18日,Grunt v0.4.0 发布。Fractal公司积极参与了数个流行Node.js模块的开发,它去年发布了一个新的构建系统Gulp,但愿可以取其精华,并取代Grunt,成为最流行的JavaScript任务运行器。gulp
完善
– Grunt的插件数据: 根据社区的结果显示,共计3,439个插件,其中49个官方插件。promise
易用
– Grunt的插件丰富: 许多常见的任务都有现成的Grunt插件,并且有众多第三方插件,如:CoffeeScript
,Handlebars
,Jade
,JsHint
,Less
,RequireJS
,Sass
,Styles
。并且经过参考文档进行配置即可以使用。浏览器
易用
Gulp相比Grunt更简洁,并且遵循代码优于配置策略,维护Gulp更像是写代码。缓存
高效
Gulp相比Grunt更有设计感,核心设计基于Unix流的概念,经过管道链接,不须要写中间文件。网络
高质量
Gulp的每一个插件只完成一个功能,这也是Unix的设计原则之一,各个功能经过流进行整合并完成复杂的任务。例如:Grunt的imagemin
插件不只压缩图片,同时还包括缓存功能。他表示,在Gulp中,缓存是另外一个插件,能够被别的插件使用,这样就促进了插件的可重用性。目前官方列出的有673个插件。异步
易学
Gulp的核心API只有5个,掌握了5个API就学会了Gulp,以后即可以经过管道流组合本身想要的任务。函数
Yeoman团队去年12月份时在Github上也专门提过一个issue,讨论是否使用Gulp取代Grunt:他们提到Gulp是一个新的基于流的管道式构建系统,须要不多的配置而且更快。grunt
module.exports = function(grunt) { grunt.initConfig({ concat: { 'dist/all.js': ['src/*.js'] }, uglify: { 'dist/all.min.js': ['dist/all.js'] }, jshint: { files: ['gruntfile.js', 'src/*.js'] }, watch: { files: ['gruntfile.js', 'src/*.js'], tasks: ['jshint', 'concat', 'uglify'] } }); // Load Our Plugins grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-watch'); // Register Default Task grunt.registerTask('default', ['jshint', 'concat', 'uglify']); };
var gulp = require('gulp'); var jshint = require('gulp-jshint'); var concat = require('gulp-concat'); var rename = require('gulp-rename'); var uglify = require('gulp-uglify'); // Lint JS gulp.task('lint', function() { return gulp.src('src/*.js') .pipe(jshint()) .pipe(jshint.reporter('default')); }); // Concat & Minify JS gulp.task('minify', function(){ return gulp.src('src/*.js') .pipe(concat('all.js')) .pipe(gulp.dest('dist')) .pipe(rename('all.min.js')) .pipe(uglify()) .pipe(gulp.dest('dist')); }); // Watch Our Files gulp.task('watch', function() { gulp.watch('src/*.js', ['lint', 'minify']); }); // Default gulp.task('default', ['lint', 'minify', 'watch']);
Gulp经过流和代码优于配置策略来尽可能简化任务编写的工做。这看起来有点“像jQuery”的方法,把动做串起来建立构建任务。早在Unix的初期,流就已经存在了。流在Node.js生态系统中也扮演了重要的角色,相似于*nix将几乎全部设备抽象为文件同样,Node将几乎全部IO操做都抽象成了Stream的操做。所以用Gulp编写任务也可看做是用Node.js编写任务。当使用流时,Gulp去除了中间文件,只将最后的输出写入磁盘,整个过程所以变得更快。
Doug McIlroy, then head of the Bell Labs CSRC (Computing Sciences Research Center), and inventor of the Unix pipe, summarized the Unix philosophy as follows:
This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.
基于流的模块特色:
Unix管道示例:
tput setaf 88 ; whoami | figlet | tr _ … | tr \ \` | tr \| ¡ | tr / √
Node中的I/O操做是异步的,所以磁盘的读写和网络操做都须要传递回调函数。
var http = require('http'); var fs = require('fs'); var server = http.createServer(function (req, res) { fs.readFile(__dirname + '/data.txt', function (err, data) { res.end(data); }); }); server.listen(8000);
这个Node.js应用很简单,估计全部学习过Node的人都作过这样的练习,能够说是Node的Hello World了。这段代码没有任何问题,你使用node能够正常的运行起来,使用浏览器或者其余的http客户端均可以正常的访问运行程序主机的8000端口读取主机上的data.txt文件。可是这种方式隐含了一个潜在的问题,node会把整个data.txt文件都缓存到内存中以便响应客户端的请求(request),随着客户端请求的增长内存的消耗将是很是惊人的,并且客户端须要等待很长传输时间才能获得结果。让咱们再看一看另一种方式,使用流:
var http = require('http'); var fs = require('fs'); var server = http.createServer(function (req, res) { var stream = fs.createReadStream(__dirname + '/data.txt'); stream.pipe(res); }); server.listen(8000);
这里面有一个很是大的变化就是使用createReadStream这个fs的方法建立了stream这个变量,并由这个变量的pip方法来响应客户端的请求。使用stream这个变量就可让node读取data.txt必定量的时候就开始向客户端发送响应的内容,而无需服务缓存以及客户端的等待。
Node中Stream的种类
流能够是可读(Readable)或可写(Writable),或者兼具二者(Duplex,双工)的。全部流都是 EventEmitter,但它们也具备其它自定义方法和属性,取决于它们是 Readable、Writable 或 Duplex。
Liftoff
Through2
Vinyl
, Vinyl-fs
Orchestrator
Liftoff
模块解决的问题是全局安装一个CLI工具,但支持多个项目多个配置文件,而且当前目录没有配置文件时,能够就近向上级目录找到已有的配置文件,或者在项目目录外执行命令行时能够指定配置文件的目录。因此Gulp基于liftoff
能够实现,多个项目多个Gulpfile,而且能够执行gulp
时指定配置文件路径。
Through2
是为Node的streams2.Transform
的小型封装,来避免subclassing
的烦恼。能够更简单的经过一个函数来建立一个流,而不用再繁琐的设置原型链的_transform
,_flush
,以及再扩充的Transform类中调用构造函数,以便缓冲设定可以正确初始化。
Vinyl
是用来描述文件的一个很是简单的元信息对象,Vinyl对象有两个主要的属性:path
和content
。由于一个文件不只多是你硬盘上的一些内容,还多是你托管在S3,FTP,甚至DropBox上的一些内容,因此Vinyl能够描述全部这些来源的文件。它提供了一种简洁的描述文件的方式,但若是你须要访问本地文件系统上的一个文件,还须要经过一个所谓的Vinyl Adapter
,它会暴漏一些方法:如.src(globs)
,.dest(folder)
,和watch(globs, fn)
。globs是路径模式匹配。
Orchestrator
实际上是一个基于Node的模块,负责任务依赖关系定义,处理和执行,很像咱们目前所用的AMD模块加载器,并且默认是最大限度的并行加载的方式。
事实上,gulp中的任务运行系统并非本身实现的,而是直接使用了orchestrator。在gulp的源代码中能够发现,gulp继承了orchestrator,而gulp.task仅仅只是orchestrator.add的别名而已:
//gulp source code var util = require('util'); var Orchestrator = require('orchestrator'); function Gulp() { Orchestrator.call(this); } util.inherits(Gulp, Orchestrator); Gulp.prototype.task = Gulp.prototype.add;
gulp.task
gulp.run
gulp.watch
gulp.src
gulp.dest
gulp.task
在orchestrator中,解决上述任务依赖的方式有三种:
Gulp脚本中可使用这三种方法来实现任务依赖,不过因为Gulp中的任务大可能是数据流操做,所以以第一种方法为主。
全部的Gulp.js插件基本都是through
(后面再也不使用transform这个词)streams,便是消费者(接收gulp.src()
传递出来的数据,而后进行处理加工处理),又是生产者(将加工后的数据传递出去)。Gulp.js的使用和插件的开发都很简单,固然里面还有不少细节,抛砖引玉,具体请看Gulp.js的官方文档。
转自:http://www.benben.cc/blog/?p=407