代码改变世界

gulp进阶构建项目由浅入深

2016-06-06 01:06  龙恩0707  阅读(5398)  评论(4编辑  收藏  举报

gulp进阶构建项目由浅入深

阅读目录

gulp基本安装和使用

    Gulp的构建过程:gulp是使用nodejs中的stream(流),首先通过gulp.src()方法获取到我们需要的文件流(stream),然后把文件流通过pipe()方法把流导入到gulp的插件中,最后通过插件处理后的流再通过pipe()方法导入到gulp.dest()中,gulp.dest()方法把流中的内容写入到文件中。

1. Gulp安装:

首先我们需要安装nodejs,然后进行全局安装;安装如下:

   sudo npm install gulp –g

全局安装后,还需要切换到项目的根目录下,单独为单个项目进行安装下;安装如下:

   sudo npm install gulp 

如果想在安装的时候把gulp写进package.json文件的依赖中,则可以加上 –save-dev 

   sudo npm install –save-dev  gulp

2. 如何使用gulp?

在项目的根目录下新建一个 gulpfile.js文件,之后就可以定义一个任务了;

比如如下简单的任务:代码如下:

var gulp = require('gulp');

gulp.task('default',function(){

    console.log('hello world');

});

现在我项目的目录结构假如是如下样子:

最后我们进行命令行切换到项目的根目录下,运行gulp命令后,就可以在控制台看到consoe.log的打印的消息了;  

gulp API介绍

Gulp.src(globs[,options])

最常见的我们使用四个API,gulp.task() gulp.src() gulp.dest() gulp.watch();

该方法是获取我们需要的文件流,这个流里面的内容不是原始的文件流,而是一个虚拟文件对象流(Vinyl files);该方法有2个参数

globs类型是 String 或 Array , 该文件流可以是一个单独的字符串形式,也可以是一个数组形式;

options是一个对象类型;该对象类型有一个我们常用的base字段配置 options.base是经常会使用的到;

比如如下代码:

var gulp = require('gulp');
var uglify = require('gulp-uglify');
gulp.task("uglify-js",function(){
return gulp.src("src/js/*.js")
     .pipe(uglify())
     .pipe(gulp.dest('build'));
});
gulp.task('default',['uglify-js']);
// 写入到 build/a.js 和 build/index.js

如下目录结构:

我们使用base字段来继续编写如下代码:

var gulp = require('gulp');
var uglify = require('gulp-uglify');

gulp.task("uglify-js",function(){
    return gulp.src("src/js/*.js",{ base: 'src' })
        .pipe(uglify())
        .pipe(gulp.dest('build'));
});
gulp.task('default',['uglify-js']);

我们再在项目的根目录下面运行gulp命令可以看到项目的目录结构变为如下:

因此我们可以理解base字段为相对于路径来进行打包,最后生成 build/js/*.js文件;

看看Gulp用到的glob的匹配规则:

Gulp内部使用了node-glob模块来实现文件匹配功能。该文件匹配类似于JS中的正则表达式;如下匹配:

         匹配文件路径中的0个或者多个字符,但是不会匹配路径分隔符。比如: 可以匹配 abc.js,x.js,aaa,abc/(路径分隔符出现在末尾也可以匹配);但是不能匹配类似于这样的路径分隔符 abc/aa.js

        *.* 可以匹配a.xxx; xxxx.yyyy;等

        */*/*.js  可以匹配a/b/c.js,但不是不能匹配 a/b.js 或者 a/b/c/d.js

        **  可以匹配路径中的0个或者多个目录及其子目录。比如:能匹配abc,a/b.js,

a/b/c.js,x/y/z等等;

        **/*.js  能够匹配a.js , a/a.js,a/aaa/aaaa/a.js等等;也就是说只要以.js结尾的,不管前面有多少个文件或者分隔符都可以匹配;

        a/**/z  能匹配a/z,a/b/z, a/b/c/z等等。

 a/**b/z  能匹配a/b/z,a/sb/z, 不是不能匹配a/x/y/xxb/z;          

?.js  能匹配a.js,b.js,c.js,相当于js正则里面的一样匹配0个或者1个,优先匹配;

[xyz].js 能匹配x.js,y.js,z.js,类似于js正则一样,中括号中的任意一个字符;

    [^xyz].js 除了中括号中的x,y,z中的其他的任意一个字符;

当有多种匹配模式的时候可以使用数组,如下:

gulp.src([‘js/*.js’,’css/*.css’]);

我们也可以排除一些文件,可以使用!, 比如如下代码:

gulp.src([‘js/*.js’,’css/*.css’,’!reset.css’]) ; 匹配所有的js/目录下的js文件及匹配css/目录下的css文件,但是不包括reset.css文件;但是不能把排除写在第一个元素位置;

比如如下代码: gulp.src([‘!reset.css’,’css/*.css’]); 这样的是排除不掉的,这种方式我们在css中可以理解为后面的代码覆盖前面的,因此需要写在后面才能排除当前的;

gulp.dest(path[,options])

   该方法可以理解为把目标的源文件通过pipe方法导入到gulp插件中,最后把文件流写到目标文件中;如果该文件不存在的话,则会自动创建它;比如上面的gulp.src(),目录结构一刚开始build目录是没有的,通过打包后自动创建build文件夹;

字段path: 文件被写入的路径(输出的目录),也可以传入一个函数,在函数中返回相应的路径。

字段options也是一个对象类型;

    Gulp.dest(path) 生成的文件路径是相对于gulp.src()中有通配符开始出现的那部分路径。

        比如如下代码:

var gulp = require('gulp');
var uglify = require('gulp-uglify');
gulp.task("uglify-js",function(){
    return gulp.src("src/js/*.js")
        .pipe(uglify())
        .pipe(gulp.dest('build'));
});
gulp.task('default',['uglify-js']);

gulp.src()上面有通配符的是 *.js, 因此最后生成的文件路径是 build/*.js;
但是如果没有出现通配符的情况下,比如如下代码:
gulp.src("src/js/a.js")
.pipe(gulp.dest('build'));

那么最后生成的路径就是 build/a.js 了;

当然我们可以在gulp.src()方法中配置base属性,如果没有配置base属性的话,那么默认生成的路径就是相对于通配符出现的那部分路径;如果设置了base属性的话,那么就相对于base的那个设置的路径;假如现在的源目录结构为src/js/下游很多js文件

比如如下代码:

var gulp = require('gulp');
var uglify = require('gulp-uglify');
gulp.task("uglify-js",function(){
    return gulp.src("src/js/*.js",{ base: 'src' })
        .pipe(uglify())
        .pipe(gulp.dest('build'));
});
gulp.task('default',['uglify-js']);

是相对于src文件下的,因此最后生成的路径为 build/js/*.js 

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

该方法是定义一个任务;

name: 是任务的名字;

deps: {Array} 类型是数组类型;一个包含任务列表的数组,这些任务会在你当前任务运行之前完成;比如如下代码:

gulp.task('mytask', ['one', 'two', 'task', 'names'], function() {

    // 做一些事

});

比如上面的代码,我们想要完成'mytask'这个任务的话,首先会执行依赖数组中的那些任务,但是如果依赖任务里面又使用了异步的方法,比如使用setTimeout这样的时候,那么这个时候,我再执行mytask这个任务的时候,就不会等待该依赖任务完成后再执行了;比如如下代码:

var gulp = require('gulp');
gulp.task('one',function(){
  //one是一个异步执行的任务
  setTimeout(function(){
    console.log('one is done')
  },5000);
});
//two任务虽然依赖于one任务,但并不会等到one任务中的异步操作完成后再执行
gulp.task('two',['one'], function(){
  console.log('two is done');
});
gulp.task('default',['two']);

上面的例子中我们执行two任务时,会先执行one任务,但不会去等待one任务中的异步操作完成后再执行two任务,而是紧接着执行two任务。所以two任务会在one任务中的异步操作完成之前就执行了。

但是如果我们想等待one任务中的setTimeout执行完成后,再执行two这个任务该怎么办呢?

我们可以使用如下方法,代码如下:

var gulp = require('gulp');
gulp.task('one',function(fn){
  // fn 为任务函数提供的回调 用来通知该任务已经完成
  //one是一个异步执行的任务
  setTimeout(function(){
    console.log('one is done');
    fn();
  },1000);
});
//two任务虽然依赖于one任务,但并不会等到one任务中的异步操作完成后再执行
gulp.task('two',['one'], function(){
  console.log('two is done');
});
gulp.task('default',['two']);

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

用来监听文件的变化,当文件发生改变的时候,我们可以用它来执行相应的任务;

参数如下:

glob: 为要监听的文件匹配模式,规则和gulp.src中的glob相同;

opts: 为一个可选的配置对象,一般不怎么用;

tasks: 为文件变化后要执行的任务,为一个数组;

比如代码如下:

gulp.task('two', function(){

  console.log('two is done');

});

gulp.watch('js/**/*.js',['two'])

gulp一些常用插件

1.gulp-rename(重命名)

用来重命名文件流中的文件。

安装:npm install --save-dev gulp-rename

比如如下代码:

var gulp = require('gulp'),
    rename = require('gulp-rename'),
    uglify = require("gulp-uglify");
 
gulp.task('rename', function () {
    gulp.src('src/**/*.js')
    .pipe(uglify())  //压缩
    .pipe(rename('index.min.js')) 
    .pipe(gulp.dest('build/js'));
 });
gulp.task('default',['rename']);
//关于gulp-rename的更多强大的用法请参考https://www.npmjs.com/package/gulp-rename

2.gulp-uglify(JS压缩)

安装:npm install --save-dev gulp-uglify

还是上面的gulpfile.js代码如下:

var gulp = require('gulp'),
    rename = require('gulp-rename'),
    uglify = require("gulp-uglify");
 
gulp.task('rename', function () {
    gulp.src('src/**/*.js')
    .pipe(uglify())  //压缩
    .pipe(rename('index.min.js')) 
    .pipe(gulp.dest('build/js'));
});
gulp.task('default',['rename']);

3.gulp-minify-css(css文件压缩)

安装:npm install --save-dev gulp-minify-css

代码如下:

var gulp = require('gulp'),
   minifyCss = require("gulp-minify-css");
gulp.task('minify-css', function () {
    gulp.src('src/**/*.css') // 要压缩的css文件
    .pipe(minifyCss()) //压缩css
    .pipe(gulp.dest('build'));
});
gulp.task('default',['minify-css']);

4.gulp-minify-html(html压缩)

安装:npm install --save-dev gulp-minify-html

代码如下:

var gulp = require('gulp'),
    minifyHtml = require("gulp-minify-html");
 
gulp.task('minify-html', function () {
    gulp.src('src/**/*.html') // 要压缩的html文件
    .pipe(minifyHtml()) //压缩
    .pipe(gulp.dest('build'));
});

gulp.task('default',['minify-html']);

5.gulp-concat(JS文件合并)

安装:npm install --save-dev gulp-concat

代码如下:

var gulp = require('gulp'),
    concat = require("gulp-concat");
 
gulp.task('concat', function () {
    gulp.src('src/**/*.js')  //要合并的文件
    .pipe(concat('index.js'))  // 合并匹配到的js文件并命名为 "index.js"
    .pipe(gulp.dest('build/js'));
});

gulp.task('default',['concat']);

6.gulp-less (less编译)

安装:npm install –save-dev  gulp-less

Gulpfile.js代码如下:

var gulp = require('gulp'),
    less = require("gulp-less");
 
gulp.task('compile-less', function () {
    gulp.src('src/less/*.less')
    .pipe(less())
    .pipe(gulp.dest('build/css'));
});

gulp.task('default',['compile-less']);

7.gulp-sass(sass编译)

安装:npm install –save-dev  gulp-sass

代码如下:

var gulp = require('gulp'),
    sass = require("gulp-sass");
 
gulp.task('compile-sass', function () {
    gulp.src('src/sass/*.sass')
    .pipe(sass())
    .pipe(gulp.dest('build/css'));
});

gulp.task('default',['compile-sass']);

8.gulp-imagemin(图片压缩)

安装:npm install –save-dev  gulp-imagemin

代码如下:

var gulp = require('gulp');
var imagemin = require('gulp-imagemin');

gulp.task('uglify-imagemin', function () {
    return gulp.src('src/images/*')
        .pipe(imagemin())
        .pipe(gulp.dest('build/images'));
});
gulp.task('default',['uglify-imagemin']);

9.理解 Browserify

   browserify是一个使用node支持的CommonJS模块标准 来为浏览器编译模块的,可以解决模块及依赖管理;

先来看看使用gulp常见的问题:

1. 使用 gulp 过程中,偶尔会遇到 Streaming not supported 这样的错误。这通常是因为常规流与 vinyl 文件对象流有差异、
gulp 插件默认使用了只支持 buffer (不支持 stream)的库。比如,不能把 Node 常规流直接传递给 gulp 及其插件。
比如如下代码:会抛出异常的;

var gulp = require('gulp');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');
var rename = require('gulp-rename');
var fs = require('fs'); 

gulp.task('bundle', function() {  
  return fs.createReadStream('./test.txt')
    .pipe(uglify())
    .pipe(rename('bundle.min.js'))
    .pipe(gulp.dest('dist/'));
});
gulp.task('default',['bundle']);

gulp 选择默认使用内容转换成 buffer 的 vinyl 对象流,以方便处理。当然,设置 buffer: false 选项,可以让 gulp 禁用 buffer:
比如如下gulpfile.js代码如下:

var gulp = require('gulp');
var fs = require('fs'); 
gulp.task('bundle', function() { 
    return gulp.src('./src/js/app.js', {buffer: false}).on('data', function(file) {  
      var stream = file.contents;
      stream.on('data', function(chunk) {
        console.log('Read %d bytes of data', chunk.length);
      });
    });
})
gulp.task('default',['bundle']);

运行如下:

2. Stream 和 Buffer 之间转换

基于依赖的模块返回的是 stream, 以及 gulp 插件对 stream 的支持情况,有时需要把 stream 转换为 buffer。比如很多插件只支持 buffer,如 gulp-uglify、使用时可以通过 gulp-buffer 转换:Stream转换Buffer 
如下gulpfile.js代码:

var gulp = require('gulp');
    var source = require('vinyl-source-stream');  
    var buffer = require('gulp-buffer');  
    var uglify = require('gulp-uglify');
    var fs = require('fs'); 
    gulp.task('bundle', function() {  
        return fs.createReadStream('./src/js/app.js')  
          .pipe(source('app.min.js')) // 常规流转换成 vinyl 对象
          .pipe(buffer())
          .pipe(uglify())
          .pipe(gulp.dest('dist/'));
    })
    gulp.task('default',['bundle']);

如下所示:

3. 从 Buffer 到 Stream之间转换

也可以通过使用 gulp-streamify(https://www.npmjs.com/package/gulp-streamify) 或者 gulp-stream (https://www.npmjs.com/package/gulp-stream)插件,让只支持 buffer 的插件直接处理 stream。
如下gulpfile.js代码:

var gulp = require('gulp');
    var wrap = require('gulp-wrap');  
    var streamify = require('gulp-streamify');  
    var uglify = require('gulp-uglify');  
    var gzip = require('gulp-gzip');

    gulp.task('bundle', function() { 
        return gulp.src('./src/js/app.js', {buffer: false})  
              .pipe(wrap('(function(){<%= contents %>}());'))
              .pipe(streamify(uglify()))
              .pipe(gulp.dest('dist'))
              .pipe(gzip())
              .pipe(gulp.dest('dist'));
    });
    gulp.task('default',['bundle']);

如下所示:

4. 使用browserify进行Stream 向 Buffer 转换

vinyl-source-stream + vinyl-buffer
vinyl-source-stream(https://www.npmjs.com/package/vinyl-source-stream) : 将常规流转换为包含 Stream 的 vinyl 对象;
vinyl-buffer(https://www.npmjs.com/package/vinyl-buffer) 将 vinyl 对象内容中的 Stream 转换为 Buffer。

gulpfile.js代码如下:

var browserify = require('browserify');  
    var gulp = require('gulp');  
    var uglify = require('gulp-uglify');  
    var source = require('vinyl-source-stream');  
    var buffer = require('vinyl-buffer');

    gulp.task('browserify', function() {  
      return browserify('./src/js/app.js')
        .bundle()
        .pipe(source('bundle.js'))   // gives streaming vinyl file object
        .pipe(buffer())              // convert from streaming to buffered vinyl file object      
        .pipe(uglify())   
        .pipe(gulp.dest('./dist/js'));
    });
    gulp.task('default',['browserify']);

vinyl-source-stream 使用指定的文件名bundle.js创建了一个 vinyl 文件对象实例,因此可以不再使用 gulp-rename(gulp.dest 将用此文件名写入结果)。

如下所示:

5. 使用browserify多文件操作

    5-1. 使用Gulp和Browserify单个文件操作也可以如下:

var gulp = require('gulp');  
var browserify = require('browserify');  
var source = require('vinyl-source-stream');  

gulp.task('browserify', function(){
     return browserify(
        {entrieis:['./src/js/app.js']})
        .bundle()
        .pipe(source("bundle.js"))
        .pipe(gulp.dest('dist'));
});
gulp.task('default',['browserify']);

  5-2 多文件操作如下:

var gulp       = require('gulp');
var source     = require('vinyl-source-stream');
var browserify = require('browserify');
var glob       = require('glob');
var es         = require('event-stream');

gulp.task('browserify', function(done) {
     glob('./src/**/*.js', function(err, files) {
      if(err) {
        done(err)
      };
       var tasks = files.map(function(entry) {
            return browserify({ entries: [entry] })
              .bundle()
              .pipe(source(entry))
               .pipe(gulp.dest('./dest'));
         });
        es.merge(tasks).on('end', done);
       })
});
gulp.task('default',['browserify']);

5-3 也可以使用gulp.src和browserify一起使用;代码如下:

var gulp       = require('gulp');
var source     = require('vinyl-source-stream');
var browserify = require('browserify');
var glob       = require('glob');
var es         = require('event-stream');
var buffer = require('vinyl-buffer');

gulp.task('browserify', function(done) {
    gulp.src('./src/**/*.js',function(err,files) {
        if(err) {
            done(err)
        }
        files.forEach(function(file){
            return browserify({ entries: [file] })
                .bundle()
                .pipe(source(file))
                .pipe(buffer()) 
                .pipe(gulp.dest('./dest'));
        });
       });
});
gulp.task('default',['browserify']);

browserify深入学习;

1.前言:
之前我们做项目的时候,比如需要jquery框架的话,我们可能需要下载jquery源码,然后引入到我们的项目中,之后在html文件中像如下引入即可:
<script src="path/to/jquery.js"></script>

2.bower学习
之后我们学习了 Bower,因此我们安装了Bower,然后进入我们的项目文件根目录中 在命令行中使用bower安装jquery;如下命令:
bower install jquery
之后会在我们的根目录中生成一个 bower_components文件,里面包含了jquery文件,因此我们需要在我们的html文件中这样引入jquery了;
<script src="bower_components/jquery/dist/jquery.js"></script>

如下所示:

3. npm&Browserify学习
我们现在又可以使用 命令行用npm安装jQuery。进入项目的根目录后,运行如下命令:
npm install --save-dev jquery

接着我们在命令行中全局安装 browserify;命令如下:
sudo npm install -g browserify
现在我们就可以在命令行中使用 browserify命令了;
比如现在我在我的项目目录下的源文件 src/js/a.js 下想要使用jquery的话,我们可以在a.js代码如下引用:

var $ = require('jquery');
$(function(){
// 获取页面中的DOM元素
console.log($("#jquery2"));
});
function a() {
console.log("a.js");
}
a();

再进入命令行相对应的js文件中,进行如下命令:
browserify a.js -o dest.js
执行命令后会在同目录下生成dest.js,该文件包含jquery.js和a.js;然后我们把dest文件引入到我们需要的html文件中即可访问;

4. gulp和Browserify 一起使用
结合gulp一起使用时,我们只需要把Browserify安装到我们的项目内即可;因此进入项目的根目录中,进行如下命令安装:
npm install --save-dev browserify

然后在项目的根目录中在gulpfile.js文件中加入如下代码:

var gulp = require("gulp");
var browserify = require("browserify");
var sourcemaps = require("gulp-sourcemaps");
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');

gulp.task("browserify", function () {
    var b = browserify({
        entries: "./src/js/a.js",
        debug: true
    });
    return b.bundle()
        .pipe(source("bundle.js"))
        .pipe(buffer())
        .pipe(sourcemaps.init({loadMaps: true}))
        .pipe(sourcemaps.write("."))
        .pipe(gulp.dest("./dist"));
});

gulp.task('default',['browserify']);

a.js代码还是如下:

var $ = require('jquery');
$(function(){
    // 获取页面中的DOM元素
    console.log($("#jquery2"));
});
function a() {
    console.log("a.js");
}
a();

进入项目的根目录中运行命令 gulp即可,在目录中会生成dist目录(包含bundle.js和bundle.js.map)两个文件;之后在html文件页面上引入
dist目录文件下的bundle.js即可;

在上面的代码中,debug: true是告知Browserify在运行同时生成内联sourcemap用于调试。
如果我们把debug设置成false的话;在浏览器中访问页面,可以看到如下:

如果我们把debug设置成true的话,在浏览器中访问页面,可以看到如下:

引入gulp-sourcemaps并设置loadMaps: true是为了读取上一步得到的内联sourcemap,并将其转写为一个单独的sourcemap文件。
如果我们把loadMaps设置成false的话,我们在浏览器访问页面如下图所示:

如果我们把loadMaps设置成true的话,我们在浏览器访问页面如下图所示:

vinyl-source-stream用于将Browserify的bundle()的输出转换为Gulp可用的[vinyl][](一种虚拟文件格式)流。
vinyl-buffer用于将vinyl流转化为buffered vinyl文件(gulp-sourcemaps及大部分Gulp插件都需要这种格式)。

如果代码比较多,可能一次编译需要很长时间。这个时候,我们可以使用[watchify][]。它可以在你修改文件后,
只重新编译需要的部分(而不是Browserify原本的全部编译),这样,只有第一次编译会花些时间,此后的即时变更刷新则十分迅速。
如下代码:

var watchify = require('watchify');
var browserify = require('browserify');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var gutil = require('gulp-util');
var sourcemaps = require('gulp-sourcemaps');
var assign = require('lodash.assign');

// 在这里添加自定义 browserify 选项
var customOpts = {
  entries: ['./src/js/a.js'],
  debug: true
};
var opts = assign({}, watchify.args, customOpts);
var b = watchify(browserify(opts));

// 在这里加入变换操作
// 比如: b.transform(coffeeify);

gulp.task('js', bundle); // 这样你就可以运行 `gulp js` 来编译文件了
b.on('update', bundle); // 当任何依赖发生改变的时候,运行打包工具
b.on('log', gutil.log); // 输出编译日志到终端

function bundle() {
  return b.bundle()
    // 如果有错误发生,记录这些错误
    .on('error', gutil.log.bind(gutil, 'Browserify Error'))
    .pipe(source('bundle.js'))
    // 可选项,如果你不需要缓存文件内容,就删除
    .pipe(buffer())
    // 可选项,如果你不需要 sourcemaps,就删除
    .pipe(sourcemaps.init({loadMaps: true})) // 从 browserify 文件载入 map
       // 在这里将变换操作加入管道
    .pipe(sourcemaps.write('./')) // 写入 .map 文件
    .pipe(gulp.dest('./dist'));
}
gulp.task('default',['js']);

5. 使用Browserify来组织JavaScript文件

还是上面那个项目,假如src/js文件内有2个js文件,分别为a.js和b.js;假如现在a.js想引用b.js的模块,就像seajs那样通过require来引用如何做?
现在我们可以在b.js这样编写代码;把我们的代码模块通过module.exports 或 exports模块对外提供接口,和其他的比如seajs一样编写代码
即可:比如现在b.js代码如下:
function b() {
    console.log("b.js");
}
module.exports = b;
那么a.js代码如下:
var b = require('./b');
function a() {
   b();
   console.log("a.js");
}
a();
然后再在gulpfile.js文件代码还是如下:

var gulp = require("gulp");
var browserify = require("browserify");
var sourcemaps = require("gulp-sourcemaps");
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');

gulp.task("browserify", function () {
    var b = browserify({
        entries: "./src/js/a.js",
        debug: true
    });
    return b.bundle()
        .pipe(source("bundle.js"))
        .pipe(buffer())
        .pipe(sourcemaps.init({loadMaps: true}))
        .pipe(sourcemaps.write("."))
        .pipe(gulp.dest("./dist"));
});

gulp.task('default',['browserify']);

在命令行中运行gulp,即可生成bundle.js文件;引用该文件即可解决模块依赖的问题;我们打开bundle.js文件查看代码如下:

(function e(t,n,r){
    /*
    function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;
    if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");
    throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];
        return s(n?n:e)},l,l.exports,e,t,n,r)}
        return n[o].exports}var i=typeof require=="function"&&require;
        for(var o=0;o<r.length;o++)s(r[o]);return s */

})({1:[function(require,module,exports){

var b = require('./b');

function a() {
    b();
    console.log("a.js");
}
a();
},{"./b":2}],2:[function(require,module,exports){
function b() {
    console.log("b.js");
}
module.exports = b;
},{}]},{},[1])

该函数有3个参数,
第一个参数是一个对象;第二个参数是一个空对象{};第三个参数是一个[1];
第一个参数是一个object;它的每一个key都是一个数字,作为模块的id,每一个数字key对应的值是长度为2的数组。可以看下,第一个key数字1
模块中的数组中的第一个元素是a.js代码;数组中第二个元素是a模块的依赖项,第二个key数字2模块中数组第一个元素是b.js代码;数组中的第二个
元素是空对象{};因为b模块没有依赖项;因此为{};
我们的文件代码通过一个匿名函数被包装起来,这样做的好处是:我们的浏览器中并没有require这样的解决依赖的东西,但是我们又想像seajs,
requireJS等一样使用require来引入文件解决模块依赖的文件的时候,因此 Browserify实现了require、module、exports这3个关键字。
第2个参数几乎总是空的{}。它如果有的话,也是一个模块map;
第3个参数是一个数组,指定的是作为入口的模块id。a.js是入口模块,它的id是1,所以这里的数组就是[1]。
那么 Browserify是如何实现了require、module、exports这3个关键字的呢?
我们前面被注释的代码将解析require、module、exports这三个3个参数,然后让一切运行起来。
这段代码是一个函数,来自于browser-pack项目的[prelude.js][]。

上面我们看到在Browserify打包文件的时候,它会自动使用匿名函数进行包装;因此我们在编写代码的时候一般可以不需要考虑全局变量的问题了;
不需要在函数中添加像类型匿名函数的结构 (function(){})();

10.理解gulp.watch()的使用

gulp.watch()方法可以监听文件的动态修改,它接受一个glob或者glob数组(和gulp.src()一样)以及一个任务数组来执行回调。下面我们来看下
gulp.watch()方法的使用;比如现在gulpfile.js任务代码如下:

var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');

var paths = {
  scripts: ['src/js/**/*.js'],
  css: ['src/css/**/*.css'],
  // 把源文件html放在src下,会自动打包到指定目录下
  html: ['src/html/**/*.html']
};

gulp.task('scripts', function() {

  return gulp.src(paths.scripts)
    .pipe(concat('all.js'))
    .pipe(uglify())
    .pipe(gulp.dest('build/js'));
});

gulp.task('css', function() {

  return gulp.src(paths.css)
    .pipe(concat('all.css'))
    .pipe(gulp.dest('build/css'));
});

// 监听html文件的改变
gulp.task('html',function(){
    return gulp.src(paths.html)
        .pipe(gulp.dest('html/'));
});

// Rerun the task when a file changes
gulp.task('watch', function() {
  gulp.watch(paths.scripts, ['scripts']);
  gulp.watch(paths.css, ['css']);
  gulp.watch(paths.html, ['html']);
});
 
// The default task (called when you run `gulp` from cli)
gulp.task('default', ['scripts', 'css', 'html','watch']);

监听src文件下的js和css及html的文件的变化,我们在相关的项目根目录命令行中运行gulp后,当我们改变css或者js文件或html的时候,
可以监听文件的动态修改,因此保存刷新浏览器即可生效,这是gulp-watch的基本功能;如下所示:

会把src下的文件css和js自动打包到build下,src下的html文件会打包到项目根目录下的html文件下;
上面的gulp.watch 回调函数有一个包含触发回调函数信息的event对象:比如我们把gulp watch任务改成如下:
当每次更改文件的时候 都会触发change事件;代码改为如下:

gulp.task('watch', function() {
  var watch1 = gulp.watch(paths.scripts, ['scripts']);
  var watch2 = gulp.watch(paths.css, ['css']);
  var watch3 = gulp.watch(paths.html, ['html']);

  watch1.on('change', function (event) {
       console.log('Event type: ' + event.type); // Event type: changed
       console.log('Event path: ' + event.path); // Event path: /Users/tugenhua/gulp/src/css/a.css
  });
  watch2.on('change', function (event) {
       console.log('Event type: ' + event.type); // Event type: changed
       console.log('Event path: ' + event.path); // Event path: /Users/tugenhua/gulp/src/css/a.css
  });
  watch3.on('change', function (event) {
       console.log('Event type: ' + event.type); // Event type: changed
       console.log('Event path: ' + event.path); // Event path: /Users/tugenhua/gulp/src/css/a.css
  });
});

除了change事件,还可以监听很多其他的事件:
end 在watcher结束时触发
error 在出现error时触发
ready 在文件被找到并正被监听时触发
nomatch 在glob没有匹配到任何文件时触发

Watcher对象也包含了一些可以调用的方法:
watcher.end() 停止watcher(以便停止执行后面的任务或者回调函数)
watcher.files() 返回watcher监听的文件列表
watcher.add(glob) 将与指定glob相匹配的文件添加到watcher
watcher.remove(filepath) 从watcher中移除个别文件

上面是通过gulp-watch来动态监听html,css和js文件的改变,但是需要重新刷新页面才能生效;

11.理解LiveReload插件的使用

该插件的作用是当文件被修改的时候,它能实时刷新网页,这样的话就不需要我们实时刷新了;但是该插件需要在我们服务器下生效;因此
我们需要使用 gulp-connect 创建一个服务器;下面是gulpfile.js代码如下;使用liveReload实现实时刷新;

var gulp = require('gulp');
var connect = require('gulp-connect');
var uglify = require("gulp-uglify");
var concat = require("gulp-concat");
var mincss = require("gulp-minify-css");  
//自动刷新     
var livereload = require("gulp-livereload");

/* 设置路径 */
var paths = {
  src   : "src/",
  css   : "src/css/",
  scripts    : "src/js/",
  scss  : "src/scss/",
  img   : "src/images/",
  html  : "src/html/", 
  build : "build"
}
// 创建一个webserver 服务器
gulp.task('webserver', function() {
    connect.server({
        port: 8000,
        livereload: true
    });
});

gulp.task('scripts', function() {

  return gulp.src(paths.scripts+ "**/*.js")
    .pipe(concat('all.js'))
    .pipe(uglify())
    .pipe(gulp.dest(paths.build + '/js'));
});

gulp.task('css', function() {

  return gulp.src(paths.css+ "**/*.css")
    .pipe(concat('all.css'))
    .pipe(mincss())
    .pipe(gulp.dest(paths.build + '/css'));
});

// 监听html文件的改变
gulp.task('html',function(){
    return gulp.src(paths.html + "**/*.html")
        .pipe(gulp.dest('html/'));
});

//reload server
gulp.task('reload-dev',['scripts','css','html'],function() {
  return gulp.src(paths.src + '**/*.*')
    .pipe(connect.reload());
});

// Watch
gulp.task('watch', function() {
    //监听生产环境目录变化
    gulp.watch(paths.src + '**/*.*',['reload-dev']);
})

gulp.task('default', ['webserver','reload-dev','watch']);

12.理解browser-sync的使用

BroserSync在浏览器中展示变化的功能与LiveReload非常相似,但是它有更多的功能。实现静态服务器,也是能实时刷新页面的;BrowserSync也可以在不同浏览器之间同步点击翻页、表单操作、滚动位置等功能。
安装如下命令:
npm install --save-dev browser-sync
如下gulpfile文件是动态监听js,css和html文件的变化实时更新;如下代码:

var gulp = require('gulp');
var connect = require('gulp-connect');
var uglify = require("gulp-uglify");
var concat = require("gulp-concat");
var mincss = require("gulp-minify-css");  
//自动刷新     
var browserSync = require('browser-sync').create();
var reload      = browserSync.reload;

/* 设置路径 */
var paths = {
  src   : "src/",
  css   : "src/css/",
  scripts    : "src/js/",
  scss  : "src/scss/",
  img   : "src/images/",
  html  : "src/html/", 
  build : "build"
}

gulp.task('scripts', function() {

  return gulp.src(paths.scripts+ "**/*.js")
    .pipe(concat('all.js'))
    .pipe(uglify())
    .pipe(gulp.dest(paths.build + '/js'))
    .pipe(reload({stream:true})); // inject into browsers
});

gulp.task('css', function() {

  return gulp.src(paths.css+ "**/*.css")
    .pipe(concat('all.css'))
    .pipe(mincss())
    .pipe(gulp.dest(paths.build + '/css'))
    .pipe(reload({stream:true})); // inject into browsers
});

// 监听html文件的改变
gulp.task('html',function(){
    return gulp.src(paths.html + "**/*.html")
        .pipe(gulp.dest('html/'))
        .pipe(reload({stream:true})); // inject into browsers
});

// 创建本地服务器,并且实时更新页面文件
gulp.task('browser-sync', ['scripts','css','html'],function() {
    var files = [
      '**/*.html',
      '**/*.css',
      '**/*.js'
    ];
    
    browserSync.init(files,{
        server: {
            //baseDir: "./html"
        }
    });
    
});

//gulp.task('default', ['webserver','reload-dev','watch']);
gulp.task('default', ['browser-sync'], function () {
    gulp.watch("**/*.css", ['css']);
    gulp.watch("**/*.html", ['html']);
    gulp.watch("**/*.js", ['scripts']);
});

对 browser-sync 更多的学习 请看文档(http://www.browsersync.cn/docs/api/)

gulp构建小型项目的基本过程

比如我现在一个小项目的基本架构如下所示:

src文件夹:是源文件的目录结构;build文件夹是通过构建后生成的文件;

src存放文件如下:

   common(该目录是存放公用的插件css文件和js文件)

   html目录是存放目前的html文件

   images目录存放所有在项目中用到的图片

   js目录是在项目中用到的所有的js文件;

   less文件是存放需要预编译成css文件;

这上面几个目录都会通过gulpfile.js打包到build目录下;通过上面的学习browserify(可以解决js的模块化依赖问题)及学习 browserSync(实现自动刷新效果),因此目前该项目打包有2个优点:

 1. 可以使用require,exports,和moudle这三个参数实现js模块化组织及加载的问题,它不需要依赖于seajs或者requireJS;

 2. 可以实时监听html,css,js文件的修改,从而不需要刷新页面,可以提高工作效率;

现在我把package.json用到的依赖包放到下面来:

{
  "name": "testProject",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "browser-sync": "^2.12.10",
    "browserify": "^13.0.1",
    "event-stream": "^3.3.2",
    "glob": "^7.0.3",
    "gulp": "^3.9.1",
    "gulp-buffer": "0.0.2",
    "gulp-clean": "^0.3.2",
    "gulp-concat": "^2.6.0",
    "gulp-connect": "^4.0.0",
    "gulp-gzip": "^1.3.0",
    "gulp-imagemin": "^3.0.1",
    "gulp-less": "^3.1.0",
    "gulp-livereload": "^3.8.1",
    "gulp-marked": "^1.0.0",
    "gulp-minify-css": "^1.2.4",
    "gulp-rename": "^1.2.2",
    "gulp-sourcemaps": "^1.6.0",
    "gulp-str-replace": "0.0.4",
    "gulp-streamify": "^1.0.2",
    "gulp-uglify": "^1.5.3",
    "gulp-util": "^3.0.7",
    "gulp-watch": "^4.3.6",
    "gulp-wrap": "^0.13.0",
    "lodash.assign": "^4.0.9",
    "node-glob": "^1.2.0",
    "vinyl-buffer": "^1.0.0",
    "vinyl-source-stream": "^1.1.0",
    "watchify": "^3.7.0"
  }
}

项目用到的话,直接npm install 就可以把所有的包加载进来;

gulpfile.js代码如下:

var gulp = require('gulp');
var less = require('gulp-less');
var mincss = require('gulp-minify-css');
var concat = require("gulp-concat");
var uglify = require("gulp-uglify");
var clean = require('gulp-clean');

var browserify = require("browserify");
var sourcemaps = require("gulp-sourcemaps");
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');

var replace = require('gulp-str-replace');
var imagemin = require('gulp-imagemin');

//自动刷新     
var browserSync = require('browser-sync').create();
var reload      = browserSync.reload;

var fs = require('fs');
var fileContent = fs.readFileSync('./package.json');
var jsonObj = JSON.parse(fileContent);

var argv = process.argv.pop();
var DEBUGGER = (argv === "-D" || argv === "-d") ? true : false;

/* 基础路径 */
var paths = {
  css       :  'src/common/css/',
  less      :  'src/less/',
  scripts   :  "src/js/",
  img       :  "src/images/",
  html      :  "src/html/", 
  build     :  "build",
  src       :  'src' 
}
var resProxy = "项目的真实路径";
var prefix = "项目的真实路径"+jsonObj.name;

if(DEBUGGER) {
    resProxy = "http://localhost:3000/build";
    prefix = "http://localhost:3000/build";
}

// 先清理文件
gulp.task('clean-css',function(){
    return gulp.src(paths.build + "**/*.css")
             .pipe(clean());
});
gulp.task('testLess', ['clean-css'],function () {
  return gulp.src([paths.less + '**/*.less',paths.css+'**/*.css']) 
           .pipe(less())
           .pipe(concat('index.css'))
           .pipe(mincss())
           .pipe(replace({
                      original : {
                        resProxy : /\@{3}RESPREFIX\@{3}/g,
                        prefix : /\@{3}PREFIX\@{3}/g
                      },
                      target : {
                        resProxy : resProxy,
                        prefix : prefix
                      }
                  }))
           .pipe(gulp.dest(paths.build + "/css"))
           .pipe(reload({stream:true}));
});

// 监听html文件的改变
gulp.task('html',function(){
    return gulp.src(paths.html + "**/*.html")
        .pipe(replace({
            original : {
              resProxy : /\@{3}RESPREFIX\@{3}/g,
              prefix : /\@{3}PREFIX\@{3}/g
            },
            target : {
              resProxy : resProxy,
              prefix : prefix
            }
        }))
      .pipe(gulp.dest(paths.build+'/html'))
      .pipe(reload({stream:true})); 
});
// 对图片进行压缩
gulp.task('images',function(){
   return gulp.src(paths.img + "**/*")
          .pipe(imagemin())
          .pipe(gulp.dest(paths.build + "/images"));
});
// 创建本地服务器,并且实时更新页面文件
gulp.task('browser-sync', ['testLess','html','browserify'],function() {
    var files = [
      '**/*.html',
      '**/*.css',
      '**/*.less',
      '**/*.js'
    ]; 
    browserSync.init(files,{
   
        server: {
            //baseDir: "./html"
        }
        
    });
});

// 解决js模块化及依赖的问题
gulp.task("browserify",function () {
    var b = browserify({
        entries: ["./src/js/index.js"],
        debug: true
    });
    return b.bundle()
        .pipe(source("index.js"))
        .pipe(buffer())
        .pipe(sourcemaps.init({loadMaps: true}))
        .pipe(gulp.dest("./build/js"))
        .pipe(uglify())
        .pipe(sourcemaps.write("."))
        .pipe(replace({
                original : {
                  resProxy : /\@{3}RESPREFIX\@{3}/g,
                  prefix : /\@{3}PREFIX\@{3}/g
                },
                target : {
                  resProxy : resProxy,
                  prefix : prefix
                }
            }))
        .pipe(gulp.dest("./build/js"))
        .pipe(reload({stream:true}));
});

gulp.task('default',['testLess','html','images','browserify'],function () {
    gulp.watch(["**/*.less","**/*.css"], ['testLess']);
    gulp.watch("**/*.html", ['html']);
    gulp.watch("**/*.js", ['browserify']);
});

gulp.task('server', ['browser-sync','images'],function () {
    gulp.watch(["**/*.less","**/*.css"], ['testLess']);
    gulp.watch("**/*.html", ['html']);
    gulp.watch("**/*.js", ['browserify']);
});

如果我们在命令行中 运行gulp server -d  那就是开发环境,会自动打开一个服务器;如果运行gulp的话,就是线上的正式环境了;代码会通过replace插件替换成线上的环境;

使用replace插件的好处可以这样引入文件

<script src="@@@PREFIX@@@/js/index.js"></script>
<link rel="stylesheet" href="@@@PREFIX@@@/css/index.css"/>

所有的图片都可以使用这样的@@@PREFIX@@@来引入的,这样的话就可以指向本地的环境和线上的环境了;方便开发;

git项目demo