mean开发系列工具篇之gulp

前言

本文默认你已经安装好node环境,并且熟悉node命令,和window cd命令。


gulp简介

基于nodejs流的自动化构建工具,可以快速构建项目并减少频繁的I/0操作。你可以利用gulp插件完成各种自动化任务:测试、检查、合并、压缩、格式化、浏览器自动刷新、部署文件生成,并监听文件在改动后重复指定的这些步骤。官方中文网传送门


gulp安装

NPM是基于命令行的node包管理工具,它可以将node的程序模块安装到项目中,
我们利用npm来安装gulp.

  • 在命令行输入:
    npm install gulp -g
  • 运行时注意查看命令行有没有错误信息,安装完成之后,利用命令查看gulp版本号是否被正确安装:
    gulp --version
    或者
    gulp -v
  • 安装到项目本地
    先CD到你的项目根目录
    npm install gulp --save-dev
      --save-dev保存gulp到项目依赖文件package.json的devDependencies里面。
  • 安装错误处理
    • 连不上服务器。由于npm大多包在github或者国外网站上,如果网络不好(铁通/移动长宽等),访问不了,还有可能被墙了(你懂得)。这时候你可以使用淘宝镜像:
      npm install gulp -g --registry=https://registry.npm.taobao.org
      你也可以使用淘宝的命令CNPM
      npm install -g cnpm --registry=https://registry.npm.taobao.org
      cnpm install gulp -g
      有些包可能淘宝上也没,这时候你需要翻墙了,请自备梯子。
      淘宝镜像具体教程:淘宝镜像传送门
    • 权限不够。
      wiondows请使用管理员模式打开CMD,linux下命令前面加 sudo,
      sudo npm install gulp -g
    • windows下莫名错误。
      如果网络良好,还是安装出现莫名其妙错误,那么你就应该换Git安装了。
      window平台下建议使用git安装。

gulp 使用

在你的项目根目录下创建文件gulpfile.js文件:

var gulp = require('gulp');
gulp.task('default', function() { 
     // 将你的默认的任务代码放在这
  });

运行gulp:

 $ gulp

默认的名为 default 的任务(task)将会被运行,在这里由于没有任何代码,所以这个任务并未做任何事情。
  想要单独执行特定的任务(task),请输入 gulp <task> ,上面等价于:

   $ gulp default

gulp API简介

       gulp.src('src/js/*.js') //模糊匹配src/js文件夹下所有js文件
          .pipe( concat('app.js'))//
          .pipe(gulp.dest('pub/dist/js'));//合并后文件路径
  • gulp.dest(path[, options])
      能被 pipe 进来,并且将会写文件。并且重新输出所有数据,因此你可以将它 pipe 到多个文件夹。如果某文件夹不存在,将会自动创建它。
  gulp.src('./client/templates/*.jade') 
     .pipe(jade()) 
     .pipe(gulp.dest('./build/templates'))
     .pipe(minify()) 
     .pipe(gulp.dest('./build/minified_templates'));
  • gulp.task(name[, deps], fn)
    定义一个使用 Orchestrator 实现的任务(task)。
    • name
      任务的名字,如果你需要在命令行中运行你的某些任务,那么,请不要在名字中使用空格。
    • deps
      类型: Array
      一个包含任务列表的数组,这些任务会在你当前任务运行之前完成。
      注意: 你的任务是否在这些前置依赖的任务完成之前运行了?请一定要确保你所依赖的任务列表中的任务都使用了正确的异步执行方式:使用一个 callback,或者返回一个 promise 或 stream。
    • fn
      该函数定义任务所要执行的一些操作。通常来说,它会是这种形式:
      gulp.src().pipe(someplugin())
      返回一个 stream或者promise
gulp.task('mytask', ['array', 'of', 'task', 'names'], function() { 
  return gulp.src('client/**/*.js') 
             .pipe(minify())
             .pipe(gulp.dest('build'));
});

接受一个callback

// 在 shell 中执行一个命令
var exec = require('child_process').exec;
gulp.task('jekyll', function(cb) { 
     // 编译 Jekyll 
    exec('jekyll build', function(err) { 
            if (err) return cb(err); // 返回 error 
           cb(); // 完成 task 
     }); 
});

注意: 默认的,task 将以最大的并发数执行,也就是说,gulp 会一次性运行所有的 task 并且不做任何等待。如果你想要创建一个序列化的 task 队列,并以特定的顺序执行,你需要做两件事:
给出一个提示,来告知 task 什么时候执行完毕,
并且再给出一个提示,来告知一个 task 依赖另一个 task 的完成。

var gulp = require('gulp');
// 返回一个 callback,因此系统可以知道它什么时候完成
gulp.task('one', function(cb) { 
// 做一些事 -- 异步的或者其他的 
     cb(err); // 如果 err 不是 null /undefined,则会停止执行,注意,这样代表执行失败了
});
// 定义一个所依赖的 task 必须在这个 task 执行之前完成
gulp.task('two', ['one'], function() {
 // 'one' 完成后
});
gulp.task('default', ['one', 'two']);
  • gulp.watch(glob [, opts], tasks) 或 gulp.watch(glob [, opts, cb])
    监视文件,并且可以在文件发生改动时候做一些事情。它总会返回一个 EventEmitter 来emit change事件。
    示例:
gulp.watch('js/**/*.js', function(event) { 
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});

文件变动后执行一个或者多个task的。有js文件变动,执行jshint验证任务再刷新浏览器:

gulp.watch('js/**/*.js', ['jshint']).on('change', plugins.livereload.changed);
  • gulp.src通配符匹配
    foo.js指明特定某个文件
    *.js匹配当前目录下的所有js文件,不指名扩展名则匹配所有类型
    */*.js匹配所有**第一层子文件夹**的js文件,第二层请用*/*/.js
    **/*.js匹配**所有文件夹层次**下的js文件, 包括当前目录
    ?匹配文件路径中的一个字符(不会匹配路径分隔符)
    [...]匹配方括号中出现的字符中的任意一个,当方括号中第一个字符为^!时,则表示不匹配方括号中出现的其他字符中的任意一个.
    !匹配任何与括号中给定的任一模式都不匹配的
    下面以一系列例子来加深理解
    能匹配 a.js,x.y,abc,abc/,但不能匹配a/b.js
    .能匹配 a.js,style.css,a.b,x.y
    //
    .js能匹配 a/b/c.js,x/y/z.js,不能匹配a/b.js,a/b/c/d.js
    能匹配 abc,a/b.js,a/b/c.js,x/y/z,x/y/z/a.b,能用来匹配所有的目录和文件
    /.js 能匹配 foo.js,a/foo.js,a/b/foo.js,a/b/c/foo.js
    a/
    /z能匹配 a/z,a/b/z,a/b/c/z,a/d/g/h/j/k/z
    a/
    b/z 能匹配 a/b/z,a/sb/z,但不能匹配a/x/sb/z,因为只有单*单独出现才能匹配多级目录
    ?.js 能匹配 a.js,b.js,c.js
    a??能匹配 a.b,abc,但不能匹配ab/,因为它不会匹配路径分隔符
    [xyz].js只能匹配 x.js,y.js,z.js,不会匹配xy.js,xyz.js等,整个中括号只代表一个字符
    [^xyz].js能匹配 a.js,b.js,c.js等,不能匹配x.js,y.js,z.js
  • 项目实例,项目结构如下
    Paste_Image.png

    'modules/*/client/css/**/*.scss' 能匹配blog,core,manage三个目录下client/css目录下任意.scss文件,不管client/css/下有几层;
    ['server.js', 'config/**/*.js', 'modules/*/server/**/*.js'] 匹配根目录下server.js,config目录下所有js文件,modules所有子目录下的server文件夹所有js文件。
    ['modules/!(core)/server/routes/**/*.js', 'modules/core/server/routes/**/*.js'],匹配module下除core之外的子目录下server/route下所有js文件。
    ['server/routes/**/*.js', '!./server/routes/app.js'] 这种是无法排除routes/app.js,因为前后匹配表达式不一直,正确应为['server/routes/**/*.js', '!server/routes/app.js']
    如果你想了解更多请参考gulp进阶传送门

gulp使用

3.1 gulp之文件合并--gulp-concat

为了减少网络请求,通常会对文件夹进行合并。

  • 安装
      gulp-concat插件安装和gulp安装雷同,不再赘述。
  • 基本使用
var gulp = require('gulp'),
    concat = require('gulp-concat');//引入gulp及插件gulp-concat
    gulp.task('concat', function () {//定义任务concat
       gulp.src('src/js/*.js') //模糊匹配src/js文件夹下所有js文件
          .pipe(concat('app.js'))//合并后的文件名
         .pipe(gulp.dest('pub/dist/js'));//合并后文件路径
   });
  • 执行任务
    gulp concat
  • 结果
Paste_Image.png

上图为合并压缩重命名后的文件名。下面会一一讲到。

3.2 gulp之js文件压缩gulp-uglify

为了减少文件大小,通常会对文件进行压缩处理。

  • 安装
      gulp-uglify插件安装和gulp安装雷同,不再赘述。
  • 基本使用
var gulp = require('gulp'),
    uglify = require('gulp-uglify');
 
   gulp.task('min', function () {
       gulp.src(['src/js/index.js','src/js/app.js']) //多个文件以数组形式或模糊匹配传入.
          .pipe(uglify({
            mangle: true,//类型:Boolean 默认:true 是否修改变量名
            compress: true//类型:Boolean 默认:true 是否完全压缩
        }))
        .pipe(gulp.dest('pub/dist/js'));//压缩后存放路劲
});

uglify具体参数查看

  • 执行任务
    gulp min
    执行成功后可以看到pub/dist/js目录下生成了新的文件

3.3 gulp之css文件压缩gulp-minify-css

  • 基本使用
var gulp = require('gulp'),
    cssmin = require('gulp-minify-css');
gulp.task('Cssmin', function () {
    gulp.src('src/css/*.css')
        .pipe(cssmin())
        .pipe(gulp.dest('dist/css'));
});
  • 配合其他插件使用
 var gulpLoadPlugins = require('gulp-load-plugins'),
       cssver = require('gulp-make-css-url-version'),
      plugins = gulpLoadPlugins();//load-plugins一次加载所有依赖的gulp插件
   gulp.task('Cssmin', function () {
      return gulp.src('modules/*/client/css/*.css')
             .pipe(cssver()) //给css文件里引用文件加版本号(文件MD5)
             .pipe(cssmin({
               advanced: false,//类型:Boolean 默认:true [是否开启高级优化(合并选择器等)]
               compatibility: 'ie7',//类型:String 默认:''or'*' [启用兼容模式; 'ie7':IE7兼容模式,'ie8':IE8兼容模式,'*':IE9+兼容模式]
               keepBreaks: true//类型:Boolean 默认:false [是否保留换行]
        }))
       .pipe(gulp.dest('public/dist'));
    });

插件gulp-make-css-url-version 给css文件里引用url加版本号(根据引用文件的md5生产版本号)
.class(background:url(../img/1.jpg?v=je82o9djZTYljusqe%2B3D%4B5A) no-repeat)

  • 执行任务
    gulp Cssmin

3.4 gulp之图片压缩gulp-imagemin

压缩图片文件(包括PNG、JPEG、GIF和SVG图片)

  • 基本使用
var gulp = require('gulp'),
    imagemin = require('gulp-imagemin'); 
   gulp.task('Imagemin', function () {
        gulp.src('src/img/*.{png,jpg,gif,ico}')
           .pipe(imagemin())
           .pipe(gulp.dest('dist/img'));
    });
var gulp = require('gulp'),
    imagemin = require('gulp-imagemin');
  gulp.task('Imagemin', function () {
       gulp.src('src/img/*.{png,jpg,gif,ico}')
          .pipe(imagemin({
            optimizationLevel: 5, //类型:Number  默认:3  取值范围:0-7(优化等级)
            progressive: true, //类型:Boolean 默认:false 无损压缩jpg图片
            interlaced: true, //类型:Boolean 默认:false 隔行扫描gif进行渲染
            multipass: true //类型:Boolean 默认:false 多次优化svg直到完全优化
        }))
        .pipe(gulp.dest('public/dist'));
});
  • 由于有些图片比较大,所以可以进行深度压缩,和只压缩修改的图片,没修改就从缓存读取
var gulp = require('gulp'),
    gulpLoadPlugins = require('gulp-load-plugins'), //自动加载插件
    pngquant = require('imagemin-pngquant'),//深度压缩插件
    plugins = gulpLoadPlugins(); //实例化
  gulp.task('testImagemin', function () {
    gulp.src('src/img/*.{png,jpg,gif,ico}')
        .pipe(plugins.cache(plugins.imagemin({
            progressive: true,
            svgoPlugins: [{removeViewBox: false}],//不要移除svg的viewbox属性
            use: [pngquant()] //使用pngquant 插件深度压缩
        })))
        .pipe(gulp.dest('dist/img'));
});

3.5 自动加载插件gulp-load-plugins

上面很多例子都用到这个插件。由于我们项目中有时候会用到很多插件,如果都用require进来,我们得写很多行require代码,虽然这样没问题,但是会显得很冗长,所以gulp-load-plugins应运而生,帮我们加载这些插件。

  • 基本使用
    假定我们项目有如下插件:


    Paste_Image.png

    我们可以通过一行代码就加载进来

var gulp = require('gulp'),
plugins = require('gulp-load-plugins')();
  • gulp-load-plugins命令规则为驼峰命名,比如我们引用gulp-rename就可以plugins.rename替代,去掉gulp-前缀,再使用驼峰命名。

  • 注意: gulp-load-plugins在我们需要用到某个插件的时候,才去加载那个插件,并不是一开始就全部加载进来。因为gulp-load-plugins是依赖package.json文件来加载插件的,所以请确保你需要的插件已经加入package.json文件并已经安装完毕。

3.6 gulp之HTML文件压缩gulp-htmlmin

可以压缩页面javascript、css,去除页面空格、注释,删除多余属性等操作

  • 基本用法
var gulp = require('gulp'),
     plugins = require('gulp-load-plugins')(); //自动插件引用
     //htmlmin = require('gulp-htmlmin'); 单独引用
gulp.task('testHtmlmin', function () {
    var options = {
        removeComments: true,//清除HTML注释
        collapseWhitespace: true,//压缩HTML
        collapseBooleanAttributes: true,//省略布尔属性的值 <input checked="true"/> ==> <input />
        removeEmptyAttributes: true,//删除所有空格属性值 <input id="" /> ==> <input />
        removeScriptTypeAttributes: true,//删除<script>的type="text/javascript"
        removeStyleLinkTypeAttributes: true,//删除<style>和<link>的type="text/css"
        minifyJS: true,//压缩页面JS
        minifyCSS: true//压缩页面CSS
    };
    gulp.src('src/html/*.html')
        .pipe(plugins.htmlmin(options))
        .pipe(gulp.dest('dist/html'));
});

3.7 gulp之less文件编译(gulp-less)

将less文件编译成css,当有less文件发生改变自动编译less,并保证less语法错误或出现异常时能正常工作并提示错误信息。

  • 基本使用
var gulp = require('gulp'),
    less = require('gulp-less'); 
gulp.task('less', function () {
    gulp.src('src/less/*.less')
        .pipe(less())
        .pipe(gulp.dest('src/css'));
});

一般会使用通配符自动编译less

  • 多模块编译less
var gulp = require('gulp'),
     less = require('gulp-less'),
     //本地安装gulp-minify-css [npm install gulp-minify-css --save-dev]
    cssmin = require('gulp-minify-css');
gulp.task('testLess', function () {
    gulp.src('src/less/**/*.less')
        .pipe(less())
        .pipe(cssmin()) 
         //兼容IE7及以下需设置compatibility属性 
        //.pipe(cssmin({compatibility: 'ie7'}))
        .pipe(gulp.dest('pub/css'));
});
  • 自动编译
    如果每修改一次less,就要编译一次,显然是很繁琐的,所以应当监听less改动,当有less文件发生改变时使其自动编译。配合实时重载live-reload插件轻松监听。
  • 新建less任务编译less文件
var gulp = require('gulp'),
     plugins = require('gulp-load-plugins')(); //自动插件引用
    gulp.task('less', function () {
        return gulp.src('src/less/**/*.less')
            .pipe(plugins.less()) //编译
           .pipe(plugins.rename(function (file) {
           //把编译后less文件重命名为css文件
           file.dirname = file.dirname.replace(path.sep + 'less', path.sep + 'css');
    }))
    .pipe(gulp.dest('./modules/'));//编译后存放目录
});
  • 建立监听事件
 plugins.livereload.listen(); //启动live-reload监听
gulp
.watch('src/less/**/*.less', ['sass', 'csslint'])
.on('change', plugins.livereload.changed);

官方实时重载和CSS注入实例传送门

3.8 gulp之sass文件编译(gulp-sass)

sass和less同为css预处理器,gulp处理方法也差不多

  • 用法
var gulp = require('gulp'), 
plugins = require('gulp-load-plugins')(); //自动插件引用
 gulp.task('less', function () { 
    return gulp.src('src/less/**/*.sass') 
      .pipe(plugins.sass()) //编译sass
      .pipe(plugins.rename(function (file) {
    //重命名为css
       file.dirname = file.dirname.replace(path.sep + 'scss', path.sep + 'css'); 
    }))
    .pipe(gulp.dest('./modules/'));//指定存放路径
});

3.9 gulp之js语法检查(gulp-jshint)

根据jshint规则检查语法错误,规范项目编码。详细规则请查看JHS详细规则

  • 基本使用
var gulp = require('gulp'), 
jshint = require("gulp-jshint"); 
gulp.task('jsLint', function () { 
   gulp.src('client/**/*.js')
     .pipe(jshint()) 
     .pipe(jshint.reporter()); // 输出检查结果
});
  • 项目实例
    具体规则在项目根目录的.jshintrc文件里面
var gulp = require('gulp'), 
plugins = require('gulp-load-plugins')(); //自动插件引用
gulp.task('jshint', function () {
    return gulp.src('client/**/*.js')
       .pipe(plugins.jshint())
       .pipe(plugins.jshint.reporter('default'))// 调用default输出结果
       .pipe(plugins.jshint.reporter('fail')); //调用fail输出检查错误
});

3.10 gulp之css语法检查(gulp-csslint)

根据规则检查CSS语法

  • 基本用法
var gulp = require('gulp'), 
plugins = require('gulp-load-plugins')(); 
gulp.task('csslint', function (done) {
   return gulp.src(d'client/**/*.css')
    .pipe(plugins.csslint('.csslintrc'))//加载配置文件
    .pipe(plugins.csslint.reporter())
    .pipe(plugins.csslint.reporter(function (file) {
      if (!file.csslint.errorCount) { //有错误,输出具体错误
        done();
      }
    }));
});

3.11 gulp之重命名(gulp-rename)

这个插件上面已经过了好多次了。

  • 基本用法
    同时输出一个压缩过和一个未压缩的文件
var gulp = require('gulp'),
 rename = require('gulp-rename'),
uglify = require('gulp-uglify'),
DEST = 'build/';
gulp.task('default', function() { 
   return gulp.src('foo.js') // 这会输出一个未压缩过的版本 
      .pipe(gulp.dest(DEST)) // 这会输出一个压缩过的并且重命名未 foo.min.js 的文件
     .pipe(uglify())
     .pipe(rename({ extname: '.min.js' })) 
     .pipe(gulp.dest(DEST));
});

3.12 gulp之测试gulp-mocha

mocha 是一个简单、灵活有趣的 JavaScript 测试框架,用于 Node.js 和浏览器上的 JavaScript 应用测试

  • 基本使用
var gulp = require('gulp');
var mocha = require('gulp-mocha');
gulp.task('default', function() { 
  return gulp.src(['test/test-*.js'], { read: false }) 
          .pipe(mocha({ reporter: 'spec', 
                globals: { should: require('should') }
               }));
});
  • 文件改动监听
var gulp = require('gulp');
var mocha = require('gulp-mocha');
var gutil = require('gulp-util'); //gulp工具库
gulp.task('mocha', function() {
    return gulp.src(['test/*.js'], { read: false }) 
      .pipe(mocha({ reporter: 'list' })) 
      .on('error', gutil.log);}); //记录错误
gulp.task('watch-mocha', function() { 
     gulp.watch(['lib/**', 'test/**'], ['mocha']);});

3.13 gulp之自动刷新gulp-livereload

监听到文件改动自动刷新前端页面,不需要F5。
上面好几个例子已经使用过了,下面简单介绍下。

  • 基本用法
var gulp = require('gulp'), 
     plugins = require('gulp-load-plugins')(); //自动插件引用
gulp.task('sass', function() {
     gulp.src('sass/**/*.scss') 
        .pipe(plugins.sass()) 
        .pipe(gulp.dest('css'))
        .pipe(plugins.livereload());});
gulp.task('watch', function() { 
    plugins.livereload.listen(); //要在这里调用listen()方法
    gulp.watch('sass/**/*.scss', ['sass']);
});

3.14 gulp之自动重启node程序gulp-nodemon

监听到node服务文件改动自动重启node,官方文档传送门

  • 基本用法
gulp.task('nodemon', function () {
  return plugins.nodemon({
    script: 'server.js',
    nodeArgs: ['--debug'],
    ext: 'js,html', //文件扩展名
    env: { 'NODE_ENV': 'development',//环境-开发环境(生产环境)
    watch: ['server.js', 'config/**/*.js', 'modules/*/server/**/*.js'],
    tasks: ['jshint'] //任务
  });
});

3.15 gulp之其他插件

  • run-sequence
    让gulp任务之间可以单独执行,解除任务之间相互依赖,增强tasks复用性
  • 基本用法
  var runSequence = require('run-sequence');
  gulp.task('default', function(done) {
     runSequence('env:dev', 'lint', ['nodemon', 'watch'], done);
   // env:dev', 'lint', 'nodemon', 'watch'均是任务
  });
  • browser-sync
    静态文件服务器,同时也支持浏览器自动刷新,和gulp-livereload功能差不多。
  • 基本使用
var gulp = require('gulp');
var browserSync = require('browser-sync');
gulp.task('browser-sync', function() { 
    browserSync({ 
        files: "**", 
          server: { 
               baseDir: "./" 
                 } 
     });
});
gulp.task('default', ["browser-sync"]);
  • 删除文件del
    你也许会想要在编译文件之前删除一些文件,del 是一个node模块,github传送门
  • 基本用法
var gulp = require('gulp');
var del = require('del');
gulp.task('clean:tmp', function (cb) {
     del([ 'dist/report.csv', 
      // 使用通配模式来匹配 `tmp` 文件夹中的所有东西
        'dist/tmp/**/*', 
// 我们不希望删掉这个文件,使用!排除
       '!dist/tmp/temp.json' 
       ], cb);
});
gulp.task('default', ['clean:tmp']);

结语

关于gulp用法以及常用插件就讲解到这里,如果错误请指正。

推荐阅读更多精彩内容