An automated build is the automatic conversion of source code written during development into code or programs that can be run in production. This process of transformation is commonly referred to as an automated build workflow. What it does is keep us as far away from runtime compatibility issues as possible, using efficient syntax, specifications, and standards during development. The most typical application scenarios are the use of the latest ECMAScript standard to improve coding efficiency and quality, the use of Sass or Less to enhance the programmability of CSS, and the use of template engines to abstract repetitive HTML pages. Most of these uses are not directly supported by browsers. This is where automated build tools come in handy, transforming unsupported features through automated builds.

Grunt

The basic use

// Initialize package.json
npm init -y
// Install the grunt dependency
npm i grunt
Copy the code

Add the gruntfile.js file to the root directory

// Grunt entry file
// Used to define tasks that need to be performed automatically by Grunt
// We need to export a function that takes a parameter of the grunt object type
// The grunt object provides some apis for creating tasks

module.exports = grunt= > {
  // Register a task, registerTask(task name, task description, task function [function that is automatically executed when the task occurs])
  grunt.registerTask('foo'.'a sample task'.() = > {
    console.log('hello grunt')
  }

  grunt.registerTask('bar'.() = > {
    console.log('other task')})// Default is the default task name
  // Can be omitted when executed through grunt
  grunt.registerTask('default'.() = > {
    console.log('default task')})// The second argument can specify the mapping task for this task,
  // Execute default as the corresponding task
  // The mapped tasks are executed sequentially, not synchronously
  grunt.registerTask('default'['foo'.'bar'])

  // Other tasks can also be performed in task functions
  grunt.registerTask('run-other'.() = > {
    // Foo and bar are automatically executed after the current task is completed
    grunt.task.run('foo'.'bar')
    console.log('current task runing~')})// By default grunt is encoded in synchronous mode
  The this.async() method can be used to create a callback if asynchron is required
  grunt.registerTask('async-task'.() = > {
    setTimeout(() = > {
      console.log('async task working~')},1000)})The arrow function cannot be used because the function body needs to use this
  grunt.registerTask('async-task'.function () {
    const done = this.async()
    setTimeout(() = > {
      console.log('async task working~')
      done()
    }, 1000)})}Copy the code

Perform tasks using the NPM grunt task name

Grunt indicates that the task failed

// gruntfile.js
module.exports = grunt= > {
  // If false is returned during the execution of the task function, the task failed
  grunt.registerTask('bad'.() = > {
    console.log('bad working~')
    return false
  })

  grunt.registerTask('foo'.() = > {
    console.log('foo working~')
  })

  grunt.registerTask('bar'.() = > {
    console.log('bar working~')})// If a task in a task list fails to execute, subsequent tasks will not run by default
  // Unless the grunt runtime specifies the --force parameter to enforce
  grunt.registerTask('default'['foo'.'bad'.'bar'])

  // Async functions mark the failure of the current task by specifying a false argument to the callback function
  grunt.registerTask('bad-async'.function () {
    const done = this.async()
    setTimeout(() = > {
      console.log('async task working~')
      done(false)},1000)})}Copy the code

Grunt configuration option method

module.exports = grunt= > {
  // Multi-target mode allows tasks to form multiple sub-tasks according to configuration
  
  grunt.initConfig({
    build: {
      foo: 100.bar: '456'
    }
  })

  grunt.registerMultiTask('build'.function () {
    console.log(`task: build, target: The ${this.target}, data: The ${this.data}`)})}Copy the code

Grunt Multi-objective mission

module.exports = grunt= > {
  // Multi-target mode allows tasks to form multiple sub-tasks according to configuration
  
  grunt.initConfig({
    build: {
      options: {
        msg: 'task options'
      },
      foo: {
        options: {
          msg: 'foo target options'}},bar: '456'
    }
  })

  grunt.registerMultiTask('build'.function () {
    console.log(this.options())
  })
}
Copy the code

Grunt plug-in

The plug-in mechanism is at the heart of Grunt. The process of using the plug-in is to install the plug-in through NPM, load the tasks provided by the plug-in into the gruntfile.js file, and finally complete the relevant configuration options according to the documentation of the plug-in.

// the grunt-contrib-clean plugin automatically cleans up temporary files generated during project development
npm i grunt-contrib-clean

// gruntfile.js
module.exports = grunt= > {
  // Add configuration options for the clean task
  grunt.initConfig({
    clean: {
      temp: 'temp/**'}})// Load the tasks in the plug-in
  grunt.loadNpmTasks('grunt-contrib-clean')}Copy the code

Common plug-in

// gruntfile.js
const sass = require('sass')
const loadGruntTasks = require('load-grunt-tasks')

module.exports = grunt= > {
  grunt.initConfig({
    // [sass](https://www.npmjs.com/package/grunt-sass)
    NPM I --dev node-sass grunt-sass
    sass: {
      options: {
        // The sourceMap file is automatically generated during compilation
        sourceMap: true.// implementation specifies which module to use in grunt-sass for SASS compilation
        implementation: sass
      },
      main: {
        // Specify the input file of sass and the output CSS file path
        // Format: files:{output file path: input file path}
        files: {
          'dist/css/main.css': 'src/scss/main.scss'}}},/ / / compile es6 grammar (https://www.npmjs.com/package/grunt-babel)
    // Install: NPM I --dev grunt-babel@babel/core@babel /preset-env
    babel: {
      options: {
        sourceMap: true.// Presets specify which features need to be transformed
        presets: ['@babel/preset-env']},main: {
        files: {
          'dist/js/app.js': 'src/js/app.js'}}},// [watch](https://www.npmjs.com/package/grunt-contrib-watch)
    // The file is automatically compiled after modification
    // Install: NPM I grunt-contrib-watch --dev
    watch: {
      js: {
        files: ['src/js/*.js'].tasks: ['babel']},css: {
        files: ['src/scss/*.scss'].tasks: ['sass']
      }
    }
  })

  loadGruntTasks(grunt) // Automatically loads all tasks in grunt plug-ins

  grunt.registerTask('default'['sass'.'babel'.'watch'])}Copy the code

Gulp

The core features of Gulp are efficiency and ease of use. Using Gulp is as simple as installing Gulp dependencies and adding a gulpfile.js file to the root of your project to write the build tasks that Gulp needs to perform, which can then be run using the GULp-provided CLI.

The basic use

// Install the gulp command-line tool
npm i -g gulp-cli
// Create the project directory and enter
npx mkdirp my-project
cd my-project
// Create a package.json file in the project directory
npm init -y
Install gulp as a development-time dependency
npm i gulp -D


// Create the gulpfile.js entry file

// Gulp task functions are asynchronous and can be called to indicate that the task is complete
exports.foo = done= > {
  console.log('foo task working~')
  done() // Indicates that the task is complete
}
// Default is the default task. You can omit the task name parameter when running
exports.default = done= > {
  console.log('default task working~')
  done()
}
Copy the code

Combination of task

Tasks can be of either public or private type. Public tasks are exported from gulpfile and can be invoked directly using the gulp command. Private tasks are designed for internal use, often as part of a series() or PARALLEL () combination. A private task looks and behaves like any other task, but cannot be invoked directly by the user. To register a task as public, simply export it from gulpfile.

const { series, parallel } = require('gulp')

Task1, task2, task3 are not exported and are considered private tasks.
// They can still be used in series() or parallel() combinations.
const task1 = done= > {
  setTimeout(() = > {
    console.log('task1 working~')
    done()
  }, 1000)}const task2 = done= > {
  setTimeout(() = > {
    console.log('task2 working~')
    done()
  }, 1000)}const task3 = done= > {
  setTimeout(() = > {
    console.log('task3 working~')
    done()
  }, 1000)}// Let multiple tasks be executed in sequence
exports.foo = series(task1, task2, task3)

// Let multiple tasks execute simultaneously
exports.bar = parallel(task1, task2, task3)

Copy the code

Asynchronous tasks

Gulp’s task functions are asynchronous and can be called to indicate that the task is complete.

When multiple tasks are combined using series(), errors in any one task result in the end of the entire task group and no further tasks. When combining multiple tasks using parallel(), the error of one task completes the entire task combination, but other parallel tasks may or may not complete.

const fs = require('fs')
// If the task does not return anything, a callback must be used to indicate that the task is complete. In the following example, the callback is passed to your task as a single argument named done().
exports.callback = done= > {
  console.log('callback task')
  done()
}
// To inform gulp of errors in a task via callback, use Error as the only argument.
exports.callback_error = done= > {
  console.log('callback task')
  done(new Error('task failed'))}// Returns Promise, which avoids the problem of deep nested callbacks compared to callback functions
exports.promise = () = > {
  console.log('promise task')
  return Promise.resolve() // resolve does not return any value because Gulp ignores it
}

exports.promise_error = () = > {
  console.log('promise task')
  return Promise.reject(new Error('task failed'))}/ / use the async/await
const timeout = time= > {
  return new Promise(resolve= > {
    setTimeout(resolve, time)
  })
}

exports.async = async() = > {await timeout(1000)
  console.log('async task')}// Returns stream, which is most commonly used for file operations
exports.stream = () = > {
  const read = fs.createReadStream('yarn.lock')
  const write = fs.createWriteStream('a.txt')
  read.pipe(write)
  return read
}

Copy the code

Core working principle of Gulp construction process

Most of the build process involves reading the file out, doing some transformation, and finally writing it to another location.

The following code implements the function shown above

const fs = require('fs')
const { Transform } = require('stream')

exports.default = () = > {
  // File read stream
  const readStream = fs.createReadStream('normalize.css')

  // Write the file to the stream
  const writeStream = fs.createWriteStream('normalize.min.css')

  // File conversion stream
  const transformStream = new Transform({
    // Core conversion process
    transform: (chunk, encoding, callback) = > {
      const input = chunk.toString()
      const output = input.replace(/\s+/g.' ').replace(/ / \ \ *. +? \*\//g.' ')
      callback(null, output)
    }
  })

  return readStream
    .pipe(transformStream) / / conversion
    .pipe(writeStream) / / write
}

Copy the code

File manipulation API

The gulp process for creating a build task is to create a read stream via SRC (), then use the plugin-provided transformation stream to process the file, and finally create a write stream through dest() to write to the target file.

const { src, dest } = require('gulp')
const cleanCSS = require('gulp-clean-css')
const rename = require('gulp-rename')

exports.default = () = > {
  return src('src/*.css')// File read stream
    .pipe(cleanCSS())// Convert the stream to complete the compression conversion of CSS code
    .pipe(rename({ extname: '.min.css' }))// Convert the stream to define the CSS file rename extension
    .pipe(dest('dist'))// Write the file to the stream
}

Copy the code

SRC () takes the glob argument, reads the file from the file system and generates a Node stream. It reads all matching files into memory and processes them through a stream. The stream generated by SRC () should return from the task and signal asynchronous completion. The main API provided by streams is the.pipe() method used to connect Transform Streams or Writable Streams. Dest () takes an output directory as an argument and produces a Node stream, usually a terminator stream. When it receives a pipeline file, it writes the file contents and file properties to the specified directory. Gulp also provides the symlink() method, which operates like Dest (), but creates links instead of files. In most cases, the.pipe() method is used to place the plug-in between SRC () and dest() and convert the files in the stream.

case

1. Install the gulp command-line tool

npm install -g gulp-cli
Copy the code

Install gulp as a development-time dependency

npm install -D gulp
Copy the code

3. Create gulpfile.js

  • Compile style
// Install gulp-sass
npm i sass gulp-sass -D


const { src, dest } = require("gulp");
// gulp-sass: used for sASS file conversion. When gulp-sass is installed, Node-sass will be automatically downloaded
const sass = require('gulp-sass') (require('sass'));
// Style compilation
const style = () = > {
  // Add {base:' SRC '} to ensure that the generated file structure is the same as the original
  return src("src/assets/styles/*.scss", { base: "src" })
    .pipe(sass({outputStyle:'expanded'}))
    .pipe(dest("dist"));
};
module.exports = {
  style,
};


/ / execution
gulp style
Copy the code
  • The script compiler
/ / install gulp - Babel
npm i -D gulp-babel @babel/core @babel/preset-env


const { src, dest } = require("gulp");
const babel = require("gulp-babel");
// Script compilation
const script = () = > {
  return src("src/assets/scripts/*.js", { base: "src" })
    .pipe(babel())
    .pipe(dest("dist"));
};
module.exports = {
  script,
};


/ / execution
gulp script
Copy the code
  • Page template compilation
/ / install gulp - swig
npm i -D gulp-swig


const { src, dest } = require("gulp");
const swig = require("gulp-swig");
// Page template compilation
const page = () = > {
  return src("src/*.html", { base: "src" })
    .pipe(swig())
    .pipe(dest("dist"));
};
module.exports = {
  page,
};


/ / execution
gulp page
Copy the code
  • Run style, script and page at the same time
const compile = parallel(style, script, page);

module.exports={
  compile
}


/ / execution
gulp compile
Copy the code
  • Image and font file conversion
/ / install gulp - imagemin
npm i -D gulp-imagemin


const imagemin = require("gulp-imagemin");
const image = () = > {
  return src("src/assets/images/**", { base: "src" }).pipe(imagemin()).pipe(dest("dist"));
};
const font = () = > {
  return src("src/assets/font/**", { base: "src" }).pipe(imagemin()).pipe(dest("dist"));
};
module.exports={
  image,
  font
}


/ / execution
gulp image
gulp font
Copy the code
  • Handling of additional files
const extra = () = > {
  return src("src/public/**", { base: "public" }).pipe(dest("dist"));
};


/ / execution
gulp extra
Copy the code
  • File to remove
Del / / installation
npm i -D del

const del = require("del");
// File cleanup
const clean = () = > {
  return del(["dist"]);
};
/ / packaging
const build = series(clean, parallel(compile, extra));
module.exports = {
  build,
};

/ / execution
gulp build
Copy the code
  • Autoloading plug-in
/ / install gulp - clean
npm i -D gulp-load-plugins

const loadPlugins = require("gulp-load-plugins");
const plugins = loadPlugins();

// Change the name of all plugins to plugins. plugins
Copy the code
  • Hot update the development server
/ / install browser - sync
npm i -D browser-sync


// Hot update the development server
const browserSync = require("browser-sync");
const browser = browserSync.create();
// Development server
const serve = () = > {
  browser.init({
    notify: false.// Whether to display prompt information when the page is refreshed
    port: 2080./ / the port number
    open: true.// Whether to open the browser automatically
    files: "dist/**".// define a file to listen for automatic refresh
    server: {
      baseDir: "dist".// The root of the page
      routes: {
        / / node_modules mapping
        "/node_modules": "node_modules",}}}); };module.exports = {
  serve,
};


/ / execution
gulp serve
Copy the code
  • Listen for changes and build optimizations
const { src, dest, parallel, series, watch } = require("gulp");
// Development server
const serve = () = > {
  // Listen for changes
  watch("src/assets/styles/*.scss", style);
  watch("src/assets/script/*.js", script);
  watch("src/*.html", page);
  watch("src/assets/images/**", image);
  watch("src/assets/fonts/**", font);
  watch("public/**", extra);

  browser.init({
    notify: false.// Whether to display prompt information when the page is refreshed
    port: 2080./ / the port number
    open: true.// Whether to open the browser automatically
    files: "dist/**".// define a file to listen for automatic refresh
    server: {
      baseDir: "dist".// The root of the page
      routes: {
        / / node_modules mapping
        "/node_modules": "node_modules",}}}); };Copy the code
  • Useref file reference processing

    The Useref plugin automatically handles build comments in HTML, mainly CSS and JS, for example,

<! -- build:css assets/styles/main.css -->
<link rel="stylesheet" href="assets/styles/main.css" />
<! -- endbuild -->

<! -- build:js assets/scripts/vendor.js -->
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script src="/node_modules/popper.js/dist/umd/popper.js"></script>
<script src="/node_modules/bootstrap/dist/js/bootstrap.js"></script>
<! -- endbuild -->
Copy the code
/ / install gulp - useref
npm i -D gulp-useref


// useref file reference processing
const useref = () = > {
  return (
    src("dist/*.html", { base: "dist" })
      .pipe(plugins.useref({ searchPath: ["dist"."."]}))// Use gulp-htmlmin, gulp-clean-css, gulp-uglify for HTML, CSS, and javascript respectively
      // the gulp-if plugin is used to make judgments
      .pipe(
        plugins.if(
          /\.html$/,
          plugins.htmlmin({
            collapseWhitespace: true.minifyCSS: true.minifyJS: true,})))// collapseWhitespace:true collapses white space characters; MinifyCSS Compression CSS; MinifyJS compression js
      .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
      .pipe(plugins.if(/\.js$/, plugins.uglify()))
      .pipe(dest("release"))); };/ / execution
gulp compile
gulp useref
Copy the code