前端——》gulp打包的执行原理及其部分插件详解

533 阅读1分钟

 gulp是一个非常流行的前端自动化构建工具,主要用来设定程序自动处理静态资源的工作。简单说,gulp就是用来打包项目的。

先看一下 我的前端项目结构,应该跟大多数人的项目结构都相同:

​编辑

如上图所示: 

  • src目录:代码目录
  • bower_components目录:静态js目录
  • build目录:src目录的编译目录,可删除,编译后会重新生成
  • node_modules目录:node环境的固定目录

 本文讨论中的最主要的目录:

  • 打包文件:gulpfile.js 

打包文件的主要内容:

var gulp = require('gulp');
var connect = require('gulp-connect');/*创建本地服务器*/
var modRewrite = require('connect-modrewrite');
var wiredep = require('wiredep').stream;
var usemin = require('gulp-usemin');
var uglify = require('gulp-uglify');/*最小化js文件*/
var minifyHtml = require('gulp-minify-html');
var minifyCss = require('gulp-minify-css');/*最小化css文件*/
var rev = require('gulp-rev');
var clean = require('gulp-clean');
var concat = require('gulp-concat');/*合并文件*/
var copy = require('gulp-copy');/*拷贝文件*/
var argv = require('yargs').argv;
var watch = require('gulp-watch');
var fs = require('fs-sync');
var jshint = require('gulp-jshint');
var map = require('map-stream');
var less = require('gulp-less');/*获取gulp-less插件*/
var replace = require('gulp-replace');
var rename = require('gulp-rename');
var revCollector = require('gulp-rev-collector');

if (argv.env === undefined) {
  console.error("Please specify a profile, like `--env staging`. Refer to README.md for more details.");
  process.exit();
}

/*定义一个静态集合,主要包含了几个js的路径。这个集合在jsLib这个任务中用到*/
var jsLibList = [  'bower_components/jquery/dist/jquery.min.js',  'bower_components/jquery.cookie/jquery.cookie.js',  "bower_components/base-64/base64.js",  "bower_components/clipboard/dist/clipboard.min.js",  "bower_components/js-md5/build/md5.min.js"];


/**
 * 启动一个Web服务器
 * 并且开启 rewrite 功能,配合html5mode
 * gulp.task是用来创建一个任务(相当于创建一个方法,类似function关键字)
 * gulp.task有3个参数,gulp.task('name3',['name1','name2'],fn)
 * 第一个参数name3是当前任务的任务名,类似方法名
 * 第三个参数,fn,是当前名为name3的这个任务具体的代码,可省略。
 * 第二个参数['name1','name2']是依赖的其他方法的方法名的集合,
 *    如果定义了这第二个参数,则表示当前名为name3的这个任务需要先执行名为name1和name2的任务之后,才能其自身的代码fn。
 *    如果未定义这第二个参数,则直接执行自身代码fn
 *    如果定义了这第二个参数,未定义其自身执行代码fn,则当前这个名为name3的任务只相当于同时调用了 name1和name2的方法,相当于将原本的子任务全部集合到一个主任务上面来,方便管理。
 */

/*创建一个名为websever的任务,主要用于开启本地服务*/
gulp.task('webserver', function() {
  /*创建本地连接并,port:端口号,livereload:开启实时刷新,debug:开启调试*/
  connect.server({
    port: 8001,
    livereload: true,
    debug: true
  });
});

//build wap
/*gulp.task用来创建一个名为build的任务,主要用于集合编译并删除多余文件
第一个参数build,是创建的任务名,即方法名,
第二个参数是 其他任务名的集合,相当于把其他任务名对应的方法放到一起执行,即同时调用这个了5个方法
这个方法相当于把build_index_and_js,images,lib,json和cleanSurplus这5个方法集合起来执行,形成了一个新的方法,这个新的方法的方法名叫build*/
gulp.task('build', ['build_index_and_js', 'images', 'lib','json','cleanSurplus']);


/*gulp.task用来创建一个名为watch的任务,主要用于关联和运行这个gulpfile.js文件中定义的其他任务
第一个参数watch是方法名,是创建的任务名,即方法名,
第二个参数是 其他任务名的集合,相当于把build和webserver两个方法放到一起执行
第三个参数是 watch本身的执行代码,表示在执行完build和webserver方法后,
   第三个参数的具体代码即gulp.watch的第一个参数是路径集合,第二个参数是任务集合
   表示当第一个参数中的文件内容发生变化的时候,就会再重新调用执行一次第二个参数中任务名所对应的任务,
   eg:如果修改了src/activities/collection/index.html修改时,会先执行build_index_and_js方法,再执行cleanSurplus方法删除多余文件*/
gulp.task('watch', ['build', 'webserver'], function() {
  gulp.watch(['src/js/**/*.js', 'src/app.js', 'src/activities/**/*.html', 'src/css/**/*.less'], ['build_index_and_js', 'cleanSurplus']);
});

gulp.task('lint', function() {
  return gulp.src(['src/js/**/*.js', '!src/js/config.js'])
      .pipe(jshint())
      .pipe(jshint.reporter('default'));
});

/*创建一个名为clean的任务,主要用于删除文件*/
gulp.task('clean', function() {
  /*gulp.src第一个参数:是扫描build及其子目录和子文件,第二个参数{read:false}:是不读取文件,加快程序
  *扫描完成后执行clean()方法,可以看到clean方法实际调用的是:gulp-clean插件,即删除扫描到的文件和文件夹*/
  return gulp.src('build/', { read: false })
      .pipe(clean());
});

/*创建一个名为images的任务,主要用于拷贝src/images/路径下的静态资源*/
gulp.task('images', function() {
  /*gulp.src:扫描src/images/路径下的文件和文件夹
  *扫描完成后执行copy()方法,可以看到copy方法实际调用的是gulp-copy插件,即将扫描到的文件及其路径拷贝到build目录下*/
  return gulp.src(['src/images/**'])
      .pipe(copy('build/', { prefix: 1 })); // prefix = 1, ignore src dir
});

/*创建一个名为json的任务,主要用于拷贝src/data/路径下的静态资源*/
gulp.task('json', function() {
  /*gulp.src:扫描src/data/路径下的文件和文件夹
   *同上,将扫描到的文件及其路径容拷贝到build目录下*/
  return gulp.src(['src/data/**'])
      .pipe(copy('build/', { prefix: 1 })); // prefix = 1, ignore src dir
});

/*创建一个名为cleanWatchBuild的任务,主要用于删除已经编译过的html、css和js文件*/
gulp.task('cleanWatchBuild', function() {
  /*gulp.src:扫描'build/css', 'build/js', 'build/activities'这三个路径下的文件和文件夹
  * 扫描完成后调用clean()方法,可以看到clean方法实际调用的是:gulp-clean插件,即删除扫描到的文件和文件夹*/
  return gulp.src(['build/css', 'build/js', 'build/activities'])
      .pipe(clean());
});

/*创建一个名为txtCopy的任务,主要用于拷贝txt或html文件*/
gulp.task('txtCopy', function() {
  /*gulp.src:扫描所有后缀名为txt和html的文件
  *同上,将扫描到的文件及其路径拷贝到build目录下 */
  return gulp.src(['*.txt','*.html'])
      .pipe(copy('build/'));
});

/*创建一个名为css的任务,先调用名为cleanWatchBuild和txtCopy的任务,主要用于编译css文件。
将src目录下的所有的less样式文件转成css,随后压缩并合并成一个名为app.css的文件,对这个文件加上md5版本签名,
生成到build/css路径下,并生成映射文件放到src/css*/
gulp.task('css', ['cleanWatchBuild', 'txtCopy'], function() {
  /*gulp.src:扫描src/css目录下所有的less文件,并通过pipe转成文件流
  * less():调用gulp-less插件:将less文件编译成css文件
  * minifyCss():调用gulp-minify-css插件:将css文件压缩,减少文件大小
  * concat('app.css'):调用gulp-concat插件:将多个css文件合并到一个临时文件app.css
  * rev():调用gulp-rev插件:根据文件内容,生成md5签名,加在文件名上,不会因为缓存问题,页面无法立即刷新。当文件发生变化后,md5变化,文件名发生变化,浏览器读取新文件,从而解决缓存问题
  * gulp.dest('build/css'):调用gulp自带的api即dest方法,将经过上面一系列操作的文件流进行输出生成文件放到build/css路径下
  *rev.manifest:调用gulp-rev插件的manifest方法:将app.css的文件名与其 版本号(即md5签名)对应输出到js映射文件,保存到src下的rev-manifest.json文件中,
  *               其中base为存放上一个生成的rev-manifest.json文件,然后merge为是否在已有文件后面追加,由于在当前任务之前没有映射输出,所以直接创建一个名为rev-manifest.json的文件
  *               最终生成的rev-manifest.json文件内容就是{"app.css": "app-44e278f711.css"}*/
  return gulp.src(['src/css/**/*.less'])
      .pipe(less())
      .pipe(minifyCss())
      .pipe(concat('app.css'))
      .pipe(rev())
      .pipe(gulp.dest('build/css'))
      .pipe(rev.manifest({
        base: 'src/**',
        merge: true
      }))
      .pipe(gulp.dest("src/css"));
});

/*创建一个名为jsApp的任务,先调用名为css的任务。主要用于编译src目录下的js文件*/
gulp.task('jsApp', ['css'], function() {
  /*gulp.src:扫描参数集合中包含的指定js和指定路径下的js文件,转成流
  *uglify():调用gulp-uglify插件:压缩扫描到的文件流
  *concat('app.js'):调用gulp-concat插件:将上一步处理后的文件流合并成一个文件app.js
  *rev():调用gulp-rev插件:给上一步处理后的文件名上加上MD5签名,使得浏览器读取这次生成的新文件,以避免出现缓存问题
  *gulp.dest:调用gulp自带的api即dest方法 ,将文件输出到指定目录下
  *rev.manifest:调用gulp-rev插件的manifest方法:将app.js与其 版本号(即md5签名)对应输出到js映射文件,保存到src下的rev-manifest.json文件中,
  *               其中base为存放上一个生成的rev-manifest.json文件,然后merge为是否在已有文件后面追加,
  *               比如当前任务调用了css这个任务,css任务在rev-manifest.json中输出了映射关系"app.css": "app-44e278f711.css",那么追加则为在这个关系后面添加一个如:"app.js": "app-ef93c0cc1c.js"
  *               最终生成的rev-manifest.json文件就是{"app.css": "app-44e278f711.css","app.js": "app-ef93c0cc1c.js"}*/
  return gulp.src(['src/config.js', 'src/js/app.js', 'src/js/service/*.js', 'src/js/util/*.js'])
      .pipe(uglify())
      .pipe(concat('app.js'))
      .pipe(rev())
      .pipe(gulp.dest('build/js/'))
      .pipe(rev.manifest({
        base: 'src/**',
        merge: true
      }))
      .pipe(gulp.dest("src/js"));
});

/*创建一个名为jsLib的任务,先调用名为jsApp的任务。主要用于编译bower_components目录下的js文件,输出为lib.js*/
gulp.task('jsLib', ['jsApp'], function() {
  /*gulp.src:扫描参数集合中包含的指定js,转成流
  *uglify():调用gulp-uglify插件:压缩扫描到的文件流
  *concat('lib.js'):调用gulp-concat插件:将上一步处理后的文件流合并成一个文件lib.js
  *rev():调用gulp-rev插件:给上一步处理后的文件名上加上MD5签名,使得浏览器读取这次生成的新文件,以避免出现缓存问题
  *gulp.dest:调用gulp自带的api即dest方法 ,将文件输出到指定目录下
  *rev.manifest:调用gulp-rev插件的manifest方法:将lib.js与其 版本号(即md5签名)对应输出到js映射文件,保存到src下的rev-manifest.json文件中,
  *               其中base为存放上一个生成的rev-manifest.json文件,然后merge为是否在已有文件后面追加,比如当前任务调用了jsApp这个任务,jsApp任务在rev-manifest.json中输出了映射关系  "app.js": "app-ef93c0cc1c.js",,那么追加则为在这个关系后面添加一个如:"lib.js": "lib-8f9133ddc7.js"
  *               最终生成的rev-manifest.json文件就是{"app.css": "app-44e278f711.css","app.js": "app-ef93c0cc1c.js","lib.js": "lib-8f9133ddc7.js"}*/
  return gulp.src(jsLibList)
      .pipe(uglify())
      .pipe(concat('lib.js'))
      .pipe(rev())
      .pipe(gulp.dest('build/js/'))
      .pipe(rev.manifest({
        base: 'src/**',
        merge: true
      }))
      .pipe(gulp.dest("build/lib"));
});

/*创建一个名为jsController的任务,先调用名为jsLib的任务。主要用于编译controller文件*/
gulp.task('jsController', ['jsLib'], function() {
  /*gulp.src此处扫描的controller文件夹似乎不存在,有可能是路径写错了,暂时不做修改
  * 下面步骤跟上一个jsLib的执行步骤一抹一样,不做赘述*/
  return gulp.src(['src/js/controller/**/*.js'])
      .pipe(uglify({}))
      .pipe(rev())
      .pipe(gulp.dest('build/js/controller'))
      .pipe(rev.manifest({
        base: 'src/**',
        merge: true
      }))
      .pipe(gulp.dest("build/jsController"));
});

/*创建一个名为rev的任务,先调用名为jsController的任务。主要用于根据上一步生成的rev-manifest.json文件中的映射关系,替换调html中的资源文件名称*/
gulp.task('rev', ['jsController'], function() {
  /*gulp.src:扫描rev-manifest.json映射文件和所有src下的html文件
  * revCollector({replaceReved: true,dirReplacements:{"/":"/"}}): 调用gulp-rev-collector插件,将扫描到的所有html文件中携带md5签名的静态资源的资源名称
  *               按照rev-manifest.json中对应的关系,替换为最新生成的md5签名。
  *               replaceReved: 用来说明模板中已经被替换的文件是否还能再被替换
  *               dirReplacements:路径替换*/
  return gulp.src(
      [
        'rev-manifest.json',
        'src/**/*.html',
      ])
      .pipe(revCollector({
        replaceReved: true,
        dirReplacements: {
          '/': '/',
        }
      }))
      .pipe(gulp.dest('build'));
});

/*创建一个名为cleanSurplus的任务,用来将md5签名替换完成后留下来的rev-manifest.json等临时文件删掉,任务的执行顺序是 先替换再删除*/
gulp.task('cleanSurplus', ['rev'], function() {
    /*gulp.src:扫描到这两个临时文件,不读取文件内容
    * clean():调用gulp-clean插件 删除扫描到的文件*/
    return gulp.src(['rev-manifest.json', 'src/config.js'], { read: false })
        .pipe(clean());
});

/*创建一个名为 lib的任务,复制src/lib文件夹到build文件夹下*/
gulp.task('lib', function() {
  return gulp.src(['src/lib/**'])
      .pipe(copy('build/', { prefix: 1 }));
});

/*创建一个名为build_index_and_js的任务,主要用来根据命令行参数判断启动的是正式环境还是测试环境*/
gulp.task('build_index_and_js', function() {
  /*将正式环境、测试环境和开发环境的路径定义到map中
  * argv.env:调用argv的env参数 获取命令行中设置的env的值。在命令行中输入env dev,就相当于给env这个key设置了value=dev
  * fs.copy:调用fs-sync插件的copy方法,同步的将configFile变量指定的路径下的文件复制到src下的config文件,如果config.js已经存在,则覆盖它*/
  var envConfigMap = {
    'production': 'src/config.production.js',
    'staging': 'src/config.staging.js',
    'dev': 'src/config.dev.js'
  };
  var configFile = envConfigMap[argv.env];
  fs.copy(configFile, 'src/config.js');
});

 相信看完上面这个非常非常非常详细的注释,对于gulp的执行原理、插件作用和执行顺序都了然于胸了。

 然后我们启动项目:

  •  到项目目录下,打开命令行输入:gulp watch --env dev

(gulp watch --env dev 的命令的通俗意义是:调用gulp中构建的名为watch的这个任务,并且设置一个参数,参数的名称是env,参数的值是dev。eg:有个方法定义时:watch(env){},调用时:this.watch("dev") ) 

 执行完上面这个命令后程序即开始编译,编译过程中,build目录会自动根据src目录自动生成,编译完成后就会自动打开默认浏览器显示出页面。