未分类

编写一个支持jade+sass+livescript开发的gulpfile

gulp是一个优秀的前端开发自动化管理工具,今天,我准备分享一个可以支持用jade+sass+livescript开发web前端的gulpfile。OK,废话不多说,直接进入正题。

基本文件目录介绍:

1
2
3
4
5
/app : 该目录下存放我们要开发的应用程序文件(jade, sass, ls等文件)
/dest : 该目录下存放最终的成品文件(html, css, js等文件)
/node_modules : nodejs的各种package
/bower_components : 通过bower安装的各种前端框架或组件
/gulpfile.js : gulp的执行文件(gulp在nodes环境下运行,在终端输入gulp之后,它会找到目录下的gulp file.js来执行)

前端开发的时候,通常需要搭配后台的服务器,为了做到更好的前后端分离,我们在前端开发的过程中,一般没有同时进行后台的开发。为了模拟后台服务器,我们使用browser-sync包来实现。调用browser-sync的init() API可以实例化一个服务器出来。下面以一个简单hello world的例子来进行说明。

支持静态文件服务

上面例子中,定义了2个gulp任务,分别为serve和index。第一个任务依赖到第二个任务,即serve会等待index执行完之后才开始执行。每个gulp任务中的function就是那个任务执行的内容。上面index任务将/app文件夹下的index.html文件pipe到目标文件夹/dest中。serve任务创建了一个服务器,其中的参数这里不多做解释,有兴趣可自行查看browser-sync的文档。在终端输入gulp serve,就会执行serve任务和index任务了。浏览器将会呈现index.html的内容(browser-sync默认发送的其实页面为index.html)。

有了一个可以跑起来的服务器,下面,我们就要开始搭建jade的开发环境了。gulp有许多插件,功能多样特别方便,要将jade编译成html文件,也只需要一个插件gulp-jade就够了。请看下面的例子:

支持jade

定义一个名为jade的gulp任务,把它加到serve的依赖中去。jade任务主要做的工作是讲/app下的所有jade文件编译成html文件然后pipe到目标文件夹。这里的$是一个gulp-load-plugins对象,gulp-load-plugins可以帮你引入你需要的插件,也是一个很方便的工具,具体使用方法请自行查看文档。在终端输入gulp serve之后,app文件夹下的jade文件都被编译成/dest文件夹下对应的html文件。这里还有一个要注意的,'./app/**/*.jade'是一种通配模式,这种写法可以匹配出/app文件夹下的所有jade文件,不管它在/app下是否嵌进其他文件夹。你可以将其想象为正则表达式,不过它跟正则表达是还是有所区别的。使用on('error', ...)的目的是监测错误,然后利用errorHandler将错误信息输出以便debug(eg. 当jade语法不规范时就会输出相关提示)。

继续,接下来搭建sass和livescript的开发环境。使用的插件是gulp-sass和gulp-livescript,安装gulp-livescript时还需要安装livescript。在/app目录下随便写几个sass文件和ls文件,执行gulp serve

支持sass和livescript

定义了sass和livescript任务,形式与jade类似,就只有编译的函数不同。执行之后/app文件夹下的文件也都转化为dest文件加下对应的文件。

至此,我们的开发环境已经搭建完毕。但事实上,这还未结束。我们只是有了一个能够自动编译代码并正确输出到目标文件夹的环境。大家有没有发现一个问题,我们编译完之后的css和js文件是不是都要没有引入到html文件中去,即在html文件加上<link><script>标签把css和js引入。这个过程,如果人工去做的话也是一件很繁琐的事情,尤其是当你的文件数量很多的时候。举个例子,在现代web SPA开发中,我们可能用到很多angularJS以及相关的组件,还有bootstrap等开源框架,当一个应用比较庞大的时候,这些框架的文件加上自己写的文件合起来数量是相当多的,人工去加入标签效率非常低。下面,我们用gulp-inject和wiredep插件来实现自动引入这些文件。在这个例子中,我将所有css和js文件引入到index.html中。

支持依赖注入

定义了一个inject任务,由于是要把编译好的css和js文件引入到index.html中,因此,需要inject需要等待sass和livescript编译完成,所以,我重新调整了一下依赖关系,把sass, livescript任务从serve的依赖列表中搬了下来(jade顺便也拉下来了,好看点~)。使用inject把要引入的文件引入到index.html中去,这里引入的主要是自己写的文件。然后使用wiredep引入那些通过bower安装的前端框架文件。gulp-inject和wiredep的用法也比较简单,官方文档有示例用法,这里我也不作详细介绍了。

依赖注入结果

以上是成功引入之后的截图,css和js文件都自动加入到index.html文件里面了。这样,我们就可以愉快地只顾在/app打代码而不管其他事情了。

最后,我们还有两个需求要实现。开发的过程中,经常会修改代码,每一次修改代码都要刷一下浏览器真的很繁琐,有没有办法让我在修改代码之后按下ctrl+s保存时浏览器也跟着自动刷新呢?答案是yes。回到我们刚一开始提及的browser-sync,这个组件提供了reload API,调用之后它机会自动帮你通知浏览器进行刷新。知道这个API之后,就要开始写如何以及何时调用reload了。请看下面例子:

支持自动刷新

定义一个watch任务,这个任务监控/app下的jade,sass,ls和html文件是否发生修改,以及检测是否有通过bower安装或删除一些文件(检测bower.json的目的)。如果有发生改动的话,马上重新执行inject,我们之前的inject需要等待sass,livescript和jade任务,所以这个操作会执行到的任务有4个,也就是重新编译了一遍所有的文件,并重新引入相关的css和js到index.html文件。执行完之后,再执行function里面的browserSync.reload(),刷新浏览器的页面。狂拽酷炫吊炸天,这回真的可以抛开很多杂七杂八的事了。

最后一个需求,一次性删除/dest目录下所有内容。在开发时,会不断的迭代过去的版本,文件数量也会随之改变。如果我在/app下面删除了1.ls,执行gulp serve之后虽然那些没有被删除的文件都会重新编译然后写到/dest目录下对应的文件,但是/dest下面的1.js却会一直留在那里。这些琐碎文件有时也是对项目的一种污染,而且文件太过琐碎的话清理起来也是麻烦,最好的方式就是一次性删除/dest下的内容然后重新编译/app里面的东西。所以,我最后实现了一个清除功能,例子如下:

支持删除文件

看到代码最后的clean任务,这个任务使用了del插件,可以删除对应路径的文件或文件夹。在终端执行gulp clean之后,dest文件夹整个被删除,一点不剩。

到这里,整个gulpfile就算是写完了,支持jade+sass+livescript开发环境,可以自动监控文件改动刷新浏览器,一次性清除文件。基本功能算是有了,不过也有不少地方需要完善。本博客只给一个简单例子作为交流,展示一般的gulpfile要怎么写,如果巧妙地利用插件。有兴趣者可以继续完善该gulpfile。比如:这个gulpfile还不能同时支持ls和js混合开发,sass和css混合开发,其次,在watch的时候重新执行inject会做多余的工作,应该检查是文件改动还是文件增删,如果是文件改动,比如我改了某些ls文件,那只需执行livescript任务重新编译livescript即可。如果是文件增删,则需要重新inject引入正确的css和js到index.html~

最后附上本文用到的完整的gulpfile.js。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
var gulp = require('gulp');
var browserSync = require('browser-sync').create();
var gutil = require('gulp-util');
var wiredep = require('wiredep').stream;
var $ = require('gulp-load-plugins')({
pattern: ['gulp-*', 'del']
});
var _ = require('lodash');

var dest = './dest';

function errorHandler(title) {
return function (err) {
gutil.log(gutil.colors.red('[' + title + ']'), err.toString());
this.emit('end');
};
}

gulp.task('serve', ['inject', 'watch'], function() {
browserSync.init({
startPath : '/',
server : {
baseDir : './dest',
routes : {
'/bower_components' : 'bower_components'
}
}
});
});

gulp.task('jade', function() {
return gulp.src(['./app/**/*.jade'])
.pipe($.jade({pretty : true}))
.on('error', errorHandler('jade'))
.pipe(gulp.dest(dest));
});

gulp.task('sass', function() {
return gulp.src(['./app/**/*.sass'])
.pipe($.sass())
.on('error', errorHandler('sass'))
.pipe(gulp.dest(dest));
});

gulp.task('livescript', function() {
return gulp.src(['./app/**/*.ls'])
.pipe($.livescript())
.on('error', errorHandler('livescript'))
.pipe(gulp.dest(dest));
});

gulp.task('inject', ['sass', 'livescript', 'jade'], function() {
var injectFiles = gulp.src([dest+'/**/*.js', dest+'/**/*.css'], {read: false});
return gulp.src(['./app/index.html'])
.pipe($.inject(injectFiles, {ignorePath : '/dest', addRootSlash: false}))
.on('error', errorHandler('inject'))
.pipe(wiredep(_.extend({}, 'bower_components')))
.pipe(gulp.dest(dest));
});

gulp.task('watch', function() {
gulp.watch(['./app/**/*.html', './app/**/*.jade', './app/**/*.ls',
'./app/**/*.sass', 'bower.json'], ['inject', function() {
browserSync.reload();
}]);
});

gulp.task('clean', function() {
return $.del(dest);
});

分享到