[take you to gulp from actual combat] package front-end projects and implement anti caching

Posted by aidude111 on Sun, 06 Mar 2022 04:31:32 +0100

What is gulp?

A node based front-end automated task construction tool, which uses the classic callback + Chain call to realize task automation (SRC. Pipe (...) Pipe), gulp is actually very similar to webpack, but gulp has different emphasis. Gulp focuses more on front-end process automation and task execution (improving development efficiency through tasks), just like a pipeline. Webpack focuses more on packaging front-end resources, and everything can be packaged into modules.
Official documents: https://www.gulpjs.com.cn/

Gulp application scenarios? Why gulp?

1. Build a better scheme for the front-end automation process. Some repetitive manual operations can be done by gulp, and the amount of code is small, so as to improve the development efficiency (automate the front-end workflow tasks: unit testing, optimization and packaging).

2. Compared with other construction tools, the development is simple and easy to use. Based on nodejs file system flow, the running speed is fast.

3. It is more suitable for the packaging of native front-end projects. It is helpful to understand front-end engineering.

4. gulp can be used for packaging when publishing general-purpose components or npm libraries.

gulp installation

Execute in your project directory

npm install -g gulp 

Create the gulp configuration file gulpfile. In the root directory JS, install the dependencies in the file, and then we can directly define our gulp task in this file

var gulp=require("gulp");//Introducing gulp module
gulp.task('default',function(){ 
 console.log('hello world');
});

Then directly enter the current directory in the terminal and run the gulp command to start executing the task.
Gulp can also be followed by the task name to be executed, such as gulp task1. If no task name is specified, the default task with the task name of default will be executed.
Several APIs commonly used in gulp: task, series, parallel, src, pipe, dest

  • Task: create a task
  • series: execute multiple tasks in sequence
  • prallel: execute multiple tasks in parallel
  • src: read data source and convert to stream
  • pipe: pipe - data flow can be processed in the middle
  • dest: output data flow to target path

Native front-end project packaging of gulp practical application:

Taking the work order system jq native project as an example, the directory structure is as follows:

Path table:
Because the project structure is a little special and the resources are scattered, the js and css of html are saved in the corresponding module folder. However, the public js and css are in the files outside the html, so two entries are set (taking js resources as an example): one root directory entry and one html entry

  scriptsjs: {//Root directory entry
    src: [
      "./temp/js/**/*.js",//temp/js/js under all folders under
      "!./temp/**/*.min.js", //Do not match the compressed js to prevent secondary compression
      "!./temp/js/common.js", 
      "!./temp/mpcc/**/*.js",//This file does not need to be compressed, so it does not match
    ],
    dest: destDir + "/js",//Export
  },
  scriptshtml: {//html entrance
    src: [
      "./temp/html/**/*.js",
      "!./temp/html/BasicSet/RoleManage/js/*.js",//Mismatch
      "!./temp/html/BasicSet/UserAuthorization/js/*.js",
    ],
    dest: destDir + "/html",
  },

You're right. Is it a bit like entry and output in webpack* Wildcards represent all folders! Yes represents a mismatch. Here you can choose which files do not need to be matched

gulpfile.js complete code:

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) {
  //Get random hash value
  var random = Math.random().toString();
  return crypto
    .createHash("md5")
    .update(new Date().valueOf().toString() + random) //Adding timestamp and random number to ensure the uniqueness of hash
    .digest("hex")
    .substr(0, len);
};
var destDir = "workOrder"; //Production package file directory
//Package path table
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 + "/",
  }
};
//Delete production package
function clean() {
  return del([destDir]);
}
//Clear temp folder
function revClean() {
  return del(["temp"]);
}
//Copy to temp to avoid polluting src
function revCopy() {
  return gulp
    .src("./workorder_dev/**/*", { base: "./workorder_dev" })
    .pipe(gulp.dest("./temp/"));
}
//html Add the version number to the resource path in the file to change the resource path in all files, so as to increase the version number next
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 () { //Use through to read all html files in the html folder
        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) { //Get the content of html file and replace it directly with replace + regular
            content = content.replace(
              ASSET_REG[type],
              function (str, tag, src) {
                var _f = str[0];
                src = src.replace(/(^['"]|['"]$)/g, "");

                if (/\.min\./gi.test(src)) {
                  //Compressed file without version number
                  return src;
                }
                var assetPath = path.join(filePath, src);
                if (fs.existsSync(assetPath)) {
                  var buf = fs.readFileSync(assetPath);

                  var md5 = ramdomHash(7); //To obtain the version number hash, only 7 bits are required, and the hash does not need to be too long
                  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/"));
}
//Add version number to resources in 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; //Add version number
          } else {
            return str;
          }
          return tag + '"' + src + '"';
        });
      }
    }

    file.contents = new Buffer(content);
    this.push(file);
    cb();
  });
}

//Add hash encoding for pictures / fonts introduced into css
function revAssetCsscss() {
  return gulp
    .src(paths.stylescss.src) //Files for this task
    .pipe(assetRev()) //Module called by this task
    .pipe(gulp.dest("./temp/css")); //Compiled path
}
function revAssetCsshtml() {
  return gulp
    .src(paths.styleshtml.src) //Files for this task
    .pipe(assetRev()) //Module called by this task
    .pipe(gulp.dest("./temp/html")); //Compiled path
}
//Compress css and add sourcemap
function 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))
  );
}
//Compress the css in the html folder
function stylesMinifyHtml() {
  return (
    gulp
      .src(paths.styleshtml.src)
      .pipe(sourcemaps.init())
      .pipe(minifycss())
      .pipe(sourcemaps.write("./maps"))
      .pipe(gulp.dest(paths.styleshtml.dest))
  );
}
//Compress js and add sourcemap
function scriptsjs() {
  return gulp
    .src(paths.scriptsjs.src, { sourcemaps: true })
    .pipe(sourcemaps.init()) //Source mapping is easy to debug
    .pipe(sourcemaps.identityMap())
    .pipe(babel()) //es6 conversion
    .pipe(uglify()) //compress
    .pipe(sourcemaps.write("./maps"))
    .pipe(gulp.dest(paths.scriptsjs.dest));
}
//Compress js in html folder
function 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));
}
//Copy temporary files to production directory
function copy() {
  return gulp
    .src("./temp/**/*", { base: "./temp" })
    .pipe(gulp.dest(destDir + "/"));
}
//Create a json file and save the ID to identify whether it is an online environment
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(//Serial task
  clean,//Clear last production package
  revClean,//Delete temp folder
  revCopy,//Copy the development directory to temp
  revHtmlPathReplace,//html plus version number
  revAssetCsscss,//Add version number to css resource
  revAssetCsshtml,
   updateEnv,//Generate runtime environment json
  copy, //Compress after copy
  gulp.parallel( //Parallel task of resource compression and Optimization for two entrances
    stylesMinifyCss,
    stylesMinifyHtml,
    scriptsjs,
    scriptshtml
  ),
  revClean //Delete temp
);
exports.clean = clean;
exports.stylesMinifyCss = stylesMinifyCss;
exports.stylesMinifyHtml = stylesMinifyHtml;
exports.updateEnv = updateEnv;
exports.scriptsjs = scriptsjs;
exports.scriptshtml = scriptshtml;
exports.default = build;

It can be seen that each task is a function, and the defined tasks (functions) are executed in order at the end.
Packaging process:
Actually, look at the last gulp You can see from the series that the serial task contains a parallel task. You can directly see the packaging process by directly running the gulp command.

Order of execution of all tasks:

Delete the last production package > delete the temp folder > copy the development directory to the temp folder > HTML content plus version number > resource content plus version number > create JSON > copy the temp file to the production Directory > start parallel compression > finally delete the temp folder

gulp implements anti caching

Why anti caching?
If you don't prevent caching, after the native project goes online, the browser will cache the front-end css and js resources locally. The next time you open it, if the resources remain unchanged, you will directly use the local cache to load the page, which will cause users to manually clear the browser cache to use new functions and affect the experience, So you need to add a version number to the file when you introduce resources into the page? v=xxxx, so that the browser can recognize the change of resources and retrieve resources from the server
Let's take a look back at the task revHtmlPathReplace with the version number in the gulpfile file:

//Add the version number to the resource path in html, change the resource path in all files, so as to increase the version number next
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 () { //Use through to read all html files in the html folder
        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) { //Get the content of html file and replace it directly with replace + regular
            content = content.replace(
              ASSET_REG[type],
              function (str, tag, src) {
                var _f = str[0];
                src = src.replace(/(^['"]|['"]$)/g, "");

                if (/\.min\./gi.test(src)) {
                  //Compressed file without version number
                  return src;
                }
                var assetPath = path.join(filePath, src);
                if (fs.existsSync(assetPath)) {
                  var buf = fs.readFileSync(assetPath);
                  var md5 = ramdomHash(7); //To obtain the version number hash, only 7 bits are required, and the hash does not need to be too long
                  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/"));
}

When traversing the path of the new version of the html file, you need to add the text of the new version to the output file, and then use the path of the new version of the html file to replace it. At the same time, the task of assetvrev is similar
The code of this task is actually based on gulp asset rev / index JS source code, take out the resource path and version of the css file content in the original source code, and change it to add version to the html file content.
Effect after adding version:

Incidentally, another troublesome version anti caching scheme of gulp is to use the rev module, which needs to modify multiple source codes. This scheme has some disadvantages because it generates rev manifest Add the version according to the json correspondence. If there is no corresponding file cross reference in this json for some reasons, some files in special paths such as (.. /.. / xxx.js) will be omitted if the version number is not added. And node_modules source code will be overwritten after reinstallation.

last:

Optimize the packaging process
1. Because after each packaging, it is necessary to manually compress and name it before sending it to the back-end deployment. Free your hands and write a task distZip that automatically compresses the production package after packaging:
This distZip task should be executed last. By default, compress the dist folder under the current directory

//Automatically compress the production package into zip
function 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(""); //Current time splicing
  var output = fs.createWriteStream(filename);
  //Set the compression format to 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. When the original project is packaged, there is no process like webpack Env to distinguish whether it is a production environment. In the packaging stage, you can write a task updateEnv to generate the current running environment configuration file, and automatically generate a json file. When the project enters, load the json file in advance. In the code, you can use the env ID in this json to judge whether it is currently in the production environment.

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

3. The above packaged js and css resource files do not have the task of file merging. If js and css files are merged in advance, the problem of file dispersion can be solved, and there is no need to set more entries

Topics: Javascript Front-end gulp