gulp是什么?

一个基于node的前端自动化工作构建工具,应用经典回调+链式调用的形式实现工作的自动化 (src.pipe(...).pipe),gulp其实和webpack很类似,然而gulp侧重点不同,gulp更偏重前端流程自动化、工作执行(通过工作使开发提效),就像一条流水线。而webpack则是更偏重用于打包前端资源,所有皆可打包成模块。
官网文档:https://www.gulpjs.com.cn/

gulp的利用场景?为什么用gulp?

1.构建前端自动化流程比拟好的计划,一些反复的人工操作能够让gulp去做,并且代码量不大,晋升开发效率(自动化实现前端工作流工作:单元测试、优化、打包)。

2.与其余构建工具相比开发简略,易上手。基于nodejs 文件系统流,运行速度快。

3.更适宜原生前端我的项目的打包。有助于了解前端工程化。

4.公布通用型组件或者npm库的时候能够用gulp来进行打包。

gulp的装置

在你的我的项目目录中执行

npm install -g gulp 

在根目录下创立gulp的配置文件gulpfile.jsinstall一下文件中的依赖,之后就能够间接在这个文件中定义咱们的gulp工作了

var gulp=require("gulp");//引入gulp模块gulp.task('default',function(){  console.log('hello world');});

而后间接终端中进入当前目录运行 gulp 命令就开始执行工作了。
gulp前面也能够加上要执行的工作名,例如gulp task1,如果没有指定工作名,则会执行工作名为default的默认工作。
gulp罕用的几个api : task, series, parallel, src, pipe, dest

  • task: 创立一个工作
  • series:程序执行多个工作
  • prallel:并行执行多个工作
  • src:读取数据源转换成stream
  • pipe:管道-能够在两头对数据流进行解决
  • dest:输入数据流到指标门路

gulp理论利用之原生前端我的项目打包:

以一个jQuery原生我的项目为例子,目录构造:

门路表:
因为我的项目构造有点非凡,资源比拟扩散,html的js、css保留在对应模块文件夹中。而公共的js、css却在html的里面的文件中,所以就设置了2个入口(以js资源为例):一个根目录入口、一个html入口

  scriptsjs: {//根目录入口    src: [      "./temp/js/**/*.js",//temp/js/之下的所有文件夹下的js      "!./temp/**/*.min.js", //不匹配压缩过的js,避免二次压缩      "!./temp/js/common.js",       "!./temp/mpcc/**/*.js",//这个文件不须要压缩解决所以不匹配    ],    dest: destDir + "/js",//进口  },  scriptshtml: {//html入口    src: [      "./temp/html/**/*.js",      "!./temp/html/BasicSet/RoleManage/js/*.js",//不匹配      "!./temp/html/BasicSet/UserAuthorization/js/*.js",    ],    dest: destDir + "/html",  },

你没看错,是不是有点像webpack中的entryoutput* 通配符是代表所有文件夹、是代表不匹配。这里能够本人选哪些文件不须要进行匹配

gulpfile.js 残缺代码:

var gulp = require("gulp"),  sourcemaps = require("gulp-sourcemaps");var babel = require("gulp-babel");var uglify = require("gulp-uglify");var del = require("del");var minifycss = require("gulp-minify-css");var through = require("through2");var path = require("path");var fs = require("fs");var crypto = require("crypto");var ramdomHash = function (len) {  //获取随机hash值  var random = Math.random().toString();  return crypto    .createHash("md5")    .update(new Date().valueOf().toString() + random) //退出工夫戳和随机数保障hash的惟一    .digest("hex")    .substr(0, len);};var destDir = "workOrder"; //生产包文件目录//包门路表var paths = {  stylescss: {    src: ["./temp/css/**/*.css", "!./temp/**/*.min.css"],    dest: destDir + "/css",  },  styleshtml: {    src: ["./temp/html/**/*.css", "!./temp/**/*.min.css"],    dest: destDir + "/html",  },  scripts: {    src: "./temp/**/*.js",    dest: destDir + "/",  },  scriptsjs: {    src: [      "./temp/js/**/*.js",      "!./temp/**/*.min.js",      "!./temp/js/common.js",      "!./temp/mpcc/**/*.js",    ],    dest: destDir + "/js",  },  scriptshtml: {    src: [      "./temp/html/**/*.js",      "!./temp/html/BasicSet/RoleManage/js/*.js",      "!./temp/html/BasicSet/UserAuthorization/js/*.js",    ],    dest: destDir + "/html",  },  html: {    src: "./temp/**/*.html",    dest: destDir + "/",  }};//删除生产包function clean() {  return del([destDir]);}//革除temp文件夹function revClean() {  return del(["temp"]);}//复制到temp,防止净化srcfunction revCopy() {  return gulp    .src("./workorder_dev/**/*", { base: "./workorder_dev" })    .pipe(gulp.dest("./temp/"));}//html中资源门路加版本号,更改所有的文件里的资源门路,以便接下来的减少版本号工作.function revHtmlPathReplace() {  var ASSET_REG = {    SCRIPT:      /("|')(.[^('|")]*((\.js)|(\.css)|(\.json)|(\.png)|(\.jpg)|(\.ttf)|(\.eot)|(\.gif)|(\.woff2)|(\.woff)))(\1)/gi,  };  return gulp    .src("./temp/html/**/*.html")    .pipe(      (function () { //利用through读取html文件夹下的所有html文件        return through.obj(function (file, enc, cb) {          if (file.isNull()) {            this.push(file);            return cb();          }          if (file.isStream()) {            this.emit(              "error",              new gutil.PluginError(PLUGIN_NAME, "Streaming not supported")            );            return cb();          }          var content = file.contents.toString();          var filePath = path.dirname(file.path);             for (var type in ASSET_REG) { //获取html文件内容间接应用replace+正则进行替换            content = content.replace(              ASSET_REG[type],              function (str, tag, src) {                var _f = str[0];                src = src.replace(/(^['"]|['"]$)/g, "");                if (/\.min\./gi.test(src)) {                  //压缩文件不加版本号                  return src;                }                var assetPath = path.join(filePath, src);                if (fs.existsSync(assetPath)) {                  var buf = fs.readFileSync(assetPath);                  var md5 = ramdomHash(7); //获取版本号hash,只须要7位hash不须要太长                  var verStr = "" + md5;                  src = src + "?v=" + verStr;                }                src = _f + src + _f;                return src;              }            );          }          file.contents = new Buffer(content);          this.push(file);          cb();        });      })()    )    .pipe(gulp.dest("./temp/html/"));}//css中资源加版本号function assetRev(options) {  var ASSET_REG = {    SCRIPT: /(<script[^>]+src=)['"]([^'"]+)["']/gi,    STYLESHEET: /(<link[^>]+href=)['"]([^'"]+)["']/gi,    IMAGE: /(<img[^>]+src=)['"]([^'"]+)["']/gi,    BACKGROUND: /(url\()(?!data:|about:)([^)]*)/gi,  };  return through.obj(function (file, enc, cb) {    options = options || {};    if (file.isNull()) {      this.push(file);      return cb();    }    if (file.isStream()) {      this.emit(        "error",        new gutil.PluginError(PLUGIN_NAME, "Streaming not supported")      );      return cb();    }    var content = file.contents.toString();    var filePath = path.dirname(file.path);    for (var type in ASSET_REG) {      if (type === "BACKGROUND" && !/\.(css|scss|less)$/.test(file.path)) {      } else {        content = content.replace(ASSET_REG[type], function (str, tag, src) {          src = src.replace(/(^['"]|['"]$)/g, "");          if (!/\.[^\.]+$/.test(src)) {            return str;          }          if (options.verStr) {            src += options.verStr;            return tag + '"' + src + '"';          }          // remote resource          if (/^https?:\/\//.test(src)) {            return str;          }          var assetPath = path.join(filePath, src);          if (src.indexOf("/") == 0) {            if (              options.resolvePath &&              typeof options.resolvePath === "function"            ) {              assetPath = options.resolvePath(src);            } else {              assetPath = (options.rootPath || "") + src;            }          }          if (fs.existsSync(assetPath)) {            var buf = fs.readFileSync(assetPath);            var md5 = ramdomHash(7);            var verStr = (options.verConnecter || "") + md5;            src = src + "?v=" + verStr; //减少版本号          } else {            return str;          }          return tag + '"' + src + '"';        });      }    }    file.contents = new Buffer(content);    this.push(file);    cb();  });}//为css中引入的图片/字体等增加hash编码function revAssetCsscss() {  return gulp    .src(paths.stylescss.src) //该工作针对的文件    .pipe(assetRev()) //该工作调用的模块    .pipe(gulp.dest("./temp/css")); //编译后的门路}function revAssetCsshtml() {  return gulp    .src(paths.styleshtml.src) //该工作针对的文件    .pipe(assetRev()) //该工作调用的模块    .pipe(gulp.dest("./temp/html")); //编译后的门路}//压缩css,并增加sourcemapfunction stylesMinifyCss() {  return (    gulp      .src(paths.stylescss.src)      .pipe(sourcemaps.init())      // .pipe(less())      // .pipe(cleanCSS())      // // pass in options to the stream      // .pipe(rename({      //   basename: 'main',      //   suffix: '.min'      // }))      .pipe(minifycss())      .pipe(sourcemaps.write("./maps"))      .pipe(gulp.dest(paths.stylescss.dest))  );}//把html文件夹下的css进行压缩function stylesMinifyHtml() {  return (    gulp      .src(paths.styleshtml.src)      .pipe(sourcemaps.init())      .pipe(minifycss())      .pipe(sourcemaps.write("./maps"))      .pipe(gulp.dest(paths.styleshtml.dest))  );}//压缩js,并增加sourcemapfunction scriptsjs() {  return gulp    .src(paths.scriptsjs.src, { sourcemaps: true })    .pipe(sourcemaps.init()) //源码映射便于调试    .pipe(sourcemaps.identityMap())    .pipe(babel()) //es6转换    .pipe(uglify()) //压缩    .pipe(sourcemaps.write("./maps"))    .pipe(gulp.dest(paths.scriptsjs.dest));}//压缩html文件夹下的jsfunction scriptshtml() {  return gulp    .src(paths.scriptshtml.src, { sourcemaps: true })    .pipe(sourcemaps.init())    .pipe(sourcemaps.identityMap())    .pipe(babel())    .pipe(uglify())    .pipe(sourcemaps.write("./maps"))    .pipe(gulp.dest(paths.scriptshtml.dest));}//把临时文件拷贝到生产目录function copy() {  return gulp    .src("./temp/**/*", { base: "./temp" })    .pipe(gulp.dest(destDir + "/"));}//创立一个json文件保留标识用于辨认以后是否是线上环境function updateEnv(done) {  fs.writeFile(    "./temp/env.json",    JSON.stringify({ env: "prod" }),    function (err) {      if (err) {        console.error(err);      }      done();      console.log("--------------------updateEnv");    }  );}var build = gulp.series(//串行工作  clean,//革除上一次的生产包  revClean,//删除temp文件夹  revCopy,//拷贝开发目录到temp  revHtmlPathReplace,//html加版本号  revAssetCsscss,//给css资源加版本号  revAssetCsshtml,   updateEnv,//生成运行环境json  copy, //copy之后再压缩  gulp.parallel( //对两个入口的资源压缩、优化的并行任务    stylesMinifyCss,    stylesMinifyHtml,    scriptsjs,    scriptshtml  ),  revClean //删除temp);exports.clean = clean;exports.stylesMinifyCss = stylesMinifyCss;exports.stylesMinifyHtml = stylesMinifyHtml;exports.updateEnv = updateEnv;exports.scriptsjs = scriptsjs;exports.scriptshtml = scriptshtml;exports.default = build;

能够看出每个工作就是一个函数,在最初对定义的工作(函数)按程序进行执行一遍。
打包流程:
其实看最初的gulp.series 就能看进去,串行工作外面含有一个并行任务,间接运行gulp命令就能间接看到打包的过程。

所有工作执行的先后顺序:

删除上次生产包 > 删除temp文件夹 > 把开发目录拷贝到temp文件夹 > html内容加版本号 > 资源内容加版本号 > 创立json > 把temp文件拷到生产目录 > 开始并行压缩 > 最初删除temp文件夹

gulp实现防缓存

为什么要防缓存?
如果不防缓存,在原生我的项目上线后,浏览器会把前端的css,js资源缓存在本地,下次关上的时候如果资源不变就会间接应用本地的缓存来加载页面,这样会造成用户必须手动革除浏览器缓存能力应用新的性能,影响体验,所以就须要在页面引入资源的时候给文件加上版本号?v=xxxx,这样浏览器就能辨认到资源有变动就会从服务器上从新获取资源
咱们回看一下gulpfile文件中加版本号的工作revHtmlPathReplace

//html中资源门路加版本号,更改所有的文件里的资源门路,以便接下来的减少版本号工作.function revHtmlPathReplace() {  var ASSET_REG = {    SCRIPT:      /("|')(.[^('|")]*((\.js)|(\.css)|(\.json)|(\.png)|(\.jpg)|(\.ttf)|(\.eot)|(\.gif)|(\.woff2)|(\.woff)))(\1)/gi,  };  return gulp    .src("./temp/html/**/*.html")    .pipe(      (function () { //利用through读取html文件夹下的所有html文件        return through.obj(function (file, enc, cb) {          if (file.isNull()) {            this.push(file);            return cb();          }          if (file.isStream()) {            this.emit(              "error",              new gutil.PluginError(PLUGIN_NAME, "Streaming not supported")            );            return cb();          }          var content = file.contents.toString();          var filePath = path.dirname(file.path);             for (var type in ASSET_REG) { //获取html文件内容间接应用replace+正则进行替换            content = content.replace(              ASSET_REG[type],              function (str, tag, src) {                var _f = str[0];                src = src.replace(/(^['"]|['"]$)/g, "");                if (/\.min\./gi.test(src)) {                  //压缩文件不加版本号                  return src;                }                var assetPath = path.join(filePath, src);                if (fs.existsSync(assetPath)) {                  var buf = fs.readFileSync(assetPath);                  var md5 = ramdomHash(7); //获取版本号hash,只须要7位hash不须要太长                  var verStr = "" + md5;                  src = src + "?v=" + verStr;                }                src = _f + src + _f;                return src;              }            );          }          file.contents = new Buffer(content);          this.push(file);          cb();        });      })()    )    .pipe(gulp.dest("./temp/html/"));}

通过遍历所有html文件时应用through获取到文件的文本内容,而后利用正则对文本内容中须要加版本的门路加上hash版本而后替换下来,最初再输入一个新的文件文件。同时assetRev工作也相似
这个工作的代码其实是借鉴了gulp-asset-rev/index.js 中的源码,把原来源码中的css文件内容的资源门路加版本拿进去改成了给html文件内容加版本。
加版本后成果:

顺带说一下gulp另一种比拟麻烦的加版本防缓存计划就是应用rev模块,须要批改多处源码。这种计划有不好的中央,因为是生成rev-manifest.json对应关系来加版本 ,如果因为某些起因在这个json中没有对应文件对照,会导致某些非凡门路如(../../xxx.js)的文件加版本号没加上 就会漏掉某些文件没加版本。还有就是node_modules源码改变后重新安装就会被笼罩。

最初:

对打包流程进行一些优化
1.因为每次打包后是都要手动进行压缩、命名一下再发给后端部署。解放双手,写一个打包后对生产包主动进行压缩的工作distZip:
这个distZip工作要放到最初执行,默认压缩当前目录下的dist文件夹

//对生产包主动压缩成zipfunction distZip(done) {  var archiver = require("archiver");  var now = new Date();  var filename = [    __dirname + "/dist",    now.getMonth(),    now.getDate(),    now.getHours(),    now.getMinutes(),    now.getSeconds(),    ".zip",  ].join(""); //以后工夫拼接  var output = fs.createWriteStream(filename);  //设置压缩格局为zip  var archive = archiver("zip", {    zlib: { level: 9 }, // Sets the compression level.  });  archive.on("error", function (err) {    throw err;  });    archive.pipe(output);  archive.directory("./dist/");  archive.finalize();  done();}

2.在原生我的项目打包实现时没有像webpack有process.env来辨别是否是生产环境,能够在打包阶段写一个生成以后运行环境配置文件的工作updateEnv,主动生成一个json文件,我的项目进入时提前加载这个json文件,在代码中就能够用这个json里的env标识来判断以后是否在生产环境。

function updateEnv(done) {  fs.writeFile(    "./temp/env.json",    JSON.stringify({ env: "prod" }),    function (err) {      if (err) {        console.error(err);      }      done();      console.log("--------------------updateEnv");    }  );}

3.以上打包的js、css资源文件没有做文件合并的工作,如果提前进行js,css文件合并就能够解决了文件扩散的问题,就不须要多设置入口了