Gulp相关汇总

Gulp简介

Gulp是构建在nodejs上的前端构建工具。就构建这个功能来说,Gulp其实几乎什么也做不了,但是依托它庞大的生态,配合各种各样的插件,gulp在前端构建方面几乎无所不能。
这里简单列举一下gulp可以做到的事情:

  1. css方面
    • css多个合并
    • css压缩
    • css 自动处理厂商私有前缀
    • 预编译css语言:sass,scss,less编译
  2. JS方面
    • js合并
    • js压缩
  3. HTML
    • 压缩
    • 插入特定代码片段,比如公有header和footer
  4. 模块化
    • 基于webpack、jspm、browerify的gulp插件实现
  5. 强缓存
    • 当应用上线需要给资源加上版本号防止缓存更新不及时

当然,这些并不是Gulp可以做到的全部。但是确实是最常见的部分。依托Gulp的watch方法,我们可以实时监控文件改动进行任务处理+实时刷新。这有效提高了前端开发的效率。

Gulp的安装

gulp安装需要依赖nodejs,这里不解释如何安装nodejs了。
需要说明的是,npm3对早期的npm2依赖无限嵌套这种行为做了平坦化处理,这样有效提高了依赖不会无限深,也有效提高了安装依赖的速度,建议安装之。

1
2
npm -v //查看版本号,如果已经大于3.X.X就跳过吧
npm i -g npm

下面安装gulp

1
npm i -g gulp

一切就这样简单.

Gulp的接口

Gulp的接口不多,因为因为其流的思想,用不上太多。
Gulp构建过程,可以分为3步:入口指定->文件留处理->目标位置指定。
具体来说Gulp大致有4个API:

  • gulp.task()
  • gulp.src()
  • gulp.dest()
  • gulp.watch()

Gulp.src(globs[, options])

gulp.src是一切任务起点,用来指定入口文件,也就是要用来处理的目标文件

globs 字符串或者数组

常见的globs用法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 单一文件
gulp.src('src/a.scss')
// 多个scss文件
gulp.src('src/*.scss')
// 目录下所有scss(包括递归子文件夹)
gulp.src('src/**/*.scss')
//数组
gulp.src([
'src/a.js',
'src/b.js',
'!src/c.js',//排除c.js
'src/d.js'
]);

option

option暂时略过,貌似从来没有用过,就不介绍了

gulp.dest(path[, options])

gulp.dist是一切任务的终点,指定处理好的文件存放到哪儿去。

path

path可以是静态的字符串,也可以是一个返回一个字符串的函数。

option

option是一个对象。这个选项也没用到过,但是有可能用到,先列出来。

  • options.cwd(默认:process.cwd()) 当输出路径为相对路径的时候才有效
  • options.mode(默认:0777) 新建目录的权限

gulp.task(name[, deps, fn])

gulp.task是用来定义任务的方法

1
2
3
4
5
6
7
8
9
10
gulp.task('sass', function() {
gulp.src('src/scss/master.scss')
.pipe(sass())
.pipe(gulp.dest('dist/css'));
});
gulp.task('styles',['sass'], function() {
gulp.src('./dist/css/public.css')
.pipe(minify())
.pipe(gulp.dest('dist/css'));
});

第一个任务中没有依赖,直接function里面定义了任务行为,而第二个任务里面依赖了sass,只有sass执行完毕以后才会执行styles任务。

gulp.watch(glob [, opts], tasks)

gulp.watch是个用的相对较少,但是为了开发体验绝不可以或缺的方法。它提供了文件的动态监控。
需要掌握的不多,直接上个例子

1
2
3
gulp.watch('js/**/*.js', function(event) {
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});

这里只是把event进行了监视。实际上使用过程更多是队event.type状态进行对比,为true执行对应的任务。它可能的值是added, changed, deleted,一般情况我们判断是否为changed即可。

Gulp常用插件

Gulp倾向一个平台,在这个平台上有各种各样的组件可以选用,以达成任务构建目标。因此,常用的gulp插件的介绍还是必不可少的。

  1. css处理:
    • 自动添加css前缀(gulp-autoprefixer)
    • 压缩css(gulp-minify-css)
    • sass编译(gulp-sass)
  2. js处理
    • 合并js文件(gulp-concat)
    • 生成js的map文件(gulp-sourcemaps)
    • 压缩js代码(gulp-uglify)
  3. 图片处理
    • 压缩图片(gulp-imagemin)
  4. 其他
    • 服务和自动刷新(browser-sync)
    • 删除文件和目录(del)

css任务

包括简单的编译,压缩,加私有前缀

1
2
3
4
5
6
7
8
9
10
11
12
13
var gulp = require('gulp'),
sass = require('gulp-ruby-sass'),
autoprefixer = require('gulp-autoprefixer'),
minifycss = require('gulp-minify-css');

gulp.task('styles', function() {
return gulp.src('src/styles/main.scss')
.pipe(sass({ style: 'expanded' }))
.pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
.pipe(gulp.dest('dist/assets/css'))
.pipe(minifycss())
.pipe(gulp.dest('dist/assets/css'))
});

js任务

包括简单的语法检查,合并,压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
var gulp = require('gulp'),
jshint = require('gulp-jshint'),
uglify = require('gulp-uglify'),
concat = require('gulp-concat');
gulp.task('scripts', function() {
return gulp.src('src/scripts/**/*.js')
.pipe(jshint('.jshintrc'))
.pipe(jshint.reporter('default'))
.pipe(concat('main.js'))
.pipe(gulp.dest('dist/assets/js'))
.pipe(uglify())
.pipe(gulp.dest('dist/assets/js'))
});

图片压缩

1
2
3
4
5
6
7
var gulp = require('gulp'),
imagemin = require('gulp-imagemin');
gulp.task('images', function() {
return gulp.src('src/images/**/*')
.pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))
.pipe(gulp.dest('dist/assets/img'))
});

监视任务

1
2
3
4
5
gulp.task('watch', function() {
gulp.watch('src/styles/**/*.scss', ['styles']);
gulp.watch('src/scripts/**/*.js', ['scripts']);
gulp.watch('src/images/**/*', ['images']);
});

这里介绍到这样差不多可以了。

Gulp工作流实战。

在之前讲SystemJS时候,说到过整体的工作流。这里说一下自己如何处理这一块的东西。
但是在此之前先把之前的设计说明copy一下:

文件目录基础设计

这里简单说下文件目录基础设计:

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
├── asset
│   ├── css
│   ├── fonts
│   ├── images
│   └── js
├── config.js
├── html
│   ├── dist
│   │   └── login
│   │   ├── login.html
│   │   ├── logout.html
│   │   └── resetPwd.html
│   └── src
│   └── login
│   ├── login.html
│   ├── logout.html
│   └── resetPwd.html
└── modules
├── business
│   └── login
│   ├── login.js
│   ├── logout.js
│   └── resetPwd.js
├── github
├── npm
└── privateregistr

根据约定重于配置原则,这里说下设计和约定:

  1. 开发阶段使用html下src目录放置html文件,modules下business放置业务js和css代码,privateregistr这个随意命名的,用来放置公用模块,使用submodule维护。
  2. 发布阶段使用gulp对business下js和css进行打包,统一发布到asset/js和asset/css
  3. 每个html/dist下的文件夹为一个大栏目,里面每个html对应一个业务页面,每个页面的js名称结构要同modules/business下结构对应,举例来说:
    • html/dist/login/login.html这个文件的对应的js是modules/business/login/login.js
    • 每个页面只接受一个js作为业务入口,意思是如果上文login.js分解为step1.js&step2.js&step3.js是可以的,但是最终将只引入login.js一个,其他将require方式引用进去,不提供单独的script标签给它们。
  4. modules文件夹是jspm packages folder,里面的github和npm分别是github和npm下载的包,privateregistr则是使用jspm-git构建的私有包目标源。config.js是system.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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var filewalker = require('filewalker');
var jsonfile = require('jsonfile');
var path = require('path');
var modulesPath = {
moduleSrc : 'asset/src/js/',
moduleDist: 'asset/dist/js'
};
gulp.task('jspm', function(){
return gulp.src(modulesPath.moduleSrc+'**/*.js')
.pipe($.jspm({ minify: true,sourceMaps: false,arithmetic: '- jquery - react - materialize'}))
.pipe(gulp.dest(modulesPath.moduleDist));
});
//inject 任务配资文件在config/injectJSON中
// var injectJSON = require("./conf/injectJSON.js");
gulp.task("inject",function(){
jsonfile.readFile("conf/injectJSON.json",function(err,json){
inject(json.file);
})
});

gulp.task("json",jsonFac);

// helper
/**
* [inject description]
* @param {[array]} fileList
* 格式:
// file:[
// {
// "html":"html/index.html",
// "basePath":"_asset/scripts/dist/a/",
// "dist":"html",
// "sources":{
// "css":"_asset/scripts/dist/a/*.css",
// "js":"_asset/scripts/dist/a/*.js"
// }
// }
// ]
*/
function inject(fileList){
var opts = {
algorithm: 'sha1',
hashLength: 8,
template: '<%= name %><%= ext %>?v=<%= hash %>'
};
for(var a=0,len=fileList.length;a<len;a++){
//searchPath css文件位置 接受一个数组 或者一个字符串
var assets = $.useref.assets({searchPath: ['asset/**/*','.']});
gulp.src(fileList[a].html)
.pipe($.if(!!fileList[a].sources.js,$.inject(gulp.src(fileList[a].sources.js, {read: true}).pipe($.hash(opts)), {relative: true,removeTags:true})))
.pipe(assets)
.pipe($.rev())
.pipe(assets.restore())
.pipe($.useref())
.pipe($.revReplace())
.pipe($.if('*.js', $.uglify()))
//.pipe($.if('*.html',$.htmlmin({collapseWhitespace: true})))
.pipe(gulp.dest(fileList[a].dist));
}
}

function jsonFac(){
var jsonData = {
file:[]
}
filewalker('./html/src')
.on('file', function(p, s) {
//console.log('file: %s, %d bytes', p, s.size);
if(/\.html$/.test(p)){
var jsPath;
jsPath = 'asset/dist/js/'+p.replace(/\.html$/,'.bundle.js')
jsonData.file.push({
html:'html/src/'+p,
dist: path.dirname('html/dist/'+p),
sources:{
js:jsPath
}
})
}
})
.on('error', function(err) {
console.error(err);
})
.on('done', function() {
var file = 'conf/injectJSON.json'
jsonfile.writeFile(file, jsonData, function (err) {
console.error(err)
});
console.log('%d dirs, %d files, %d bytes', this.dirs, this.files, this.bytes);
})
.walk();
}

使用时候分成3步:

  1. gulp json生成配置文件
  2. gulp jspm生成打包好的bundle文件
  3. gulp inject负责将资源插入大html然后进行压缩

实际上第三步骤里面包含的除了inject任务,还有css打包、压缩,私有前缀处理和强缓存处理。
当然,这并不是非常完善,但是处理SystemJS的应用确实是非常够用。

在这个流程中我们用到了npm的包,并非为gulp设计,但是配合npm的包,却在gulp当前没有合用插件情况下处理好了业务需求。这是一个非常有参考价值的实践。