前端打包工具有很多款,从早期雅虎的Ant到现在的Webpack,期间的迭代变化的差别之大,如同Java到JavaScript,除了名字有点一样,几乎就是两门完全不同的语言。公司项目主要用Gulp和Webpack,本篇先整理一下Gulp。
如果你用过Grunt,可能觉得Gulp有点莫名其妙,Grunt前辈社区完善,插件成千上万,弄出个Gulp干什么?我个人感觉在未来的前端开发中一切都将是JS,因此即便是配置工具,Gulp用JS代码实现的策略优于Grunt的半JS半配置的策略。而且Gulp用流的方式进行文件处理,通过管道将多个任务和操作连接在一起,全程只有一个IO过程,最后的结果直接写入硬盘。因此打包流程更加清晰,没有中间临时文件,速度更快。注意,不要轻视“速度更快”这个评价,打包的时间越短越好,快1秒也能提高前端工程师的效率,做前端的都懂的…
- 安装和执行
- 插件引入
- task
- 数据流
- watch
安装和执行
安装很简单:
npm install --global gulp
安装之后,在工程目录下新建一个名为gulpfile.js的文件:
var gulp = require('gulp');
gulp.task('default', function() {
……
});
这样在目录下执行gulp就能自动执行上面定义的default方法。
插件引入
作为一款流程的打包工具,社区丰富的插件库是必不可少的。除了var gulp=require(‘gulp’);
是一定要引入的外,其他的插件根据需要自行引入。例如gulpfile.js的头部可能是这样的:
var gulp = require('gulp');
var del = require('del');
var less = require('gulp-less');
var base64 = require('gulp-base64');
var tpl2mod = require('gulp-tpl2mod');
var extReplace = require('gulp-ext-replace');
从上面代码就能看出,Gulp完全遵守CommonJS的规范,无论是前端页面工程师,还是后端Node工程师都可以零基础立马上手。这可能也是Gulp能取代Grunt的一个原因。
task
引入插件后,就需要建立一个个子任务来执行插件。task函数的原型:
gulp.task(name[, deps], fn)
第一个参数是task名。第二个参数可选,是个任务列表array,这些任务会在运行当前任务之前运行,例如:
gulp.task('default', ['css', 'image', 'ejs', 'babel']);
需要注意的是,你需要确保array里的任务都在当前任务开始前被运行完毕。如果array里的任务是同步方法,那就没没什么好担心的,Gulp会确保顺序依次执行。但如果array里的任务是异步方法,需要确保是否使用了一个callback,或返回一个promise或stream。
第三个参数fn里定义了任务需要执行的操作。
数据流
如果你熟悉Java,那么IO数据流这个策略应该非常熟了。Gulp通过串联起来的的小函数来传递数据,这些函数会对数据进行修改,然后把修改后的数据传递给下一个函数。src函数的原型:
gulp.src(globs[, options])
该函数将输出匹配globs的文件。输出的文件可以用pipe输入到别的插件中,形成一串数据流。
第一个参数globs,可以是string也可以是array,是一些常用的正则表达式,例如:
js/sample.js //精确匹配文件
js/*.js //匹配js目录下的所有js文件
js/**/*.js //匹配js目录及其子目录下所有后缀为.js的文件
!js/sample.js //从匹配结果中去除js/sample.js文件
*.+(js|css) //匹配根目录下所有后缀为.js或者.css的文件
//例如:匹配js目录及其子目录下所有js文件,但需要去除后缀为.min.js的文件
gulp.src(['js/**/*.js', '!js/**/*.min.js'])
更多高级语法,请仔细阅读node-glob语法。
第二个参数options可选,除了支持node-glob和glob-stream的参数外,还支持一些Gulp的额外的参数。例如:
options.buffer:如果设为false,将会以stream方式返回file.contents而不是文件buffer的形式。这在处理一些大文件的时候将会很有用。
options.read:如果设为false, 那么file.contents会返回null,这样就不会去读取文件
options.base:string型的值,将会被加在glob之前。例如下面两段代码唯一的区别是,上面这段没有options.base,而下面这段加上了options.base,导致结果路径有区别:
gulp.src('client/js/**/*.js')
.pipe(minify())
.pipe(gulp.dest('build'));
//匹配client/js/somedir/somefile.js并且将base解析为client/js/,因此结果 写入build/somedir/somefile.js
gulp.src('client/js/**/*.js', { base: 'client' })
.pipe(minify())
.pipe(gulp.dest('build'));
//结果写入build/js/somedir/somefile.js
数据流的结果通过dest函数输出,原型:
gulp.dest(path[, options])
第一个参数是文件被输出的路径,路径是相对路径,当然相对路径也可根据上面的options.base来计算。第二个参数options可选,具体可以参照Linux的文件系统操作命令:
options.cwd:默认值process.cwd()。输出目录的 cwd 参数,只在所给的输出目录是相对路径时候有效。
options.mode:默认值0777,用以定义所有在输出目录中所创建的目录的权限。
看一个Babel解码ES6的例子:
gulp.task('babel', function () {
return gulp.src('./javascript/**/*.js')
.pipe(babel({
presets: [
'es2015',
'stage-0'
],
plugins: [
'transform-runtime',
'syntax-async-functions',
'transform-class-properties',
'transform-decorators-legacy'
]
}))
.pipe(gulp.dest('./js'));
});
最后,Gulp及其插件使用的流是vinyl文件对象流(stream或buffer),和普通的文件流(chunk)还是有区别的。因此在使用Gulp时,如果pipe出现Streaming not supported。可能是因为传给Gulp及其插件的是普通文件流(例如Node里fs.createReadStream(‘app.js’).pipe(uglify()))。可以用vinyl-source-stream转换工具转一下就行了。
watch
Gulp可以监视文件的修改,功能类似于Jade里的-w参数,当文件有变动时,执行回调函数。Watch有两种原型:
gulp.watch(glob[, opts], tasks)
gulp.watch(glob[, opts, cb])
第一种原型:第一个参数glob用于指定哪些文件需要被监视,语法参数数据流不赘述。第二个参数opts可选,参照gaze。第三个参数tasks用于指定当被监视的文件有改动时,执行哪些task。watch函数始终会返回一个EventEmitter 来发射change 事件。例如:
var watcher = gulp.watch('js/**/*.js', ['uglify','reload']);
watcher.on('change', function(event) {
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});
第二种原型:和第一种的区别是,第三个参数不是task数组,而是自定义回调函数。回调函数的参数是event对象,你可以从该参数中获取到event.type(被监视的文件发生的变化类型added,changed, deleted)和event.path(触发该事件的文件的路径)例如:
gulp.watch('js/**/*.js', function(event) {
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});
总结
技术总是在发展,从实际使用下来的经验看,Gulp确实比Grunt更快,代码更少更易读。Gulp能火多久不清楚,目前来看,还是项目的一个比较理想的打包工具。