前端自动化构建

三种主流自动化构建工具介绍与实例

Posted by QY on August 21, 2019

前言

随着前端的不断发展, 项目也发展的越来越庞大, 预处理工具的出现帮助工程师们更好地解放生产力, 然而随着预处理工具数量不断地增多, 对于自动化进行预处理并且能够将项目以工程化的形式呈现的需求越来越强烈, 自动化构建工具便应运而生

由于一些工具比较老久且效率不足导致运用已经不多, 但是当我们需要维护旧项目时了解这些曾经风光一时的构建工具也是非常必要的

Grunt

  • grunt官网中contrib开头且有星号表示的插件代表是官方开发并维护的, 其他的则是第三方插件

  • 本质基于nodeJS的自动化任务运行器

  • 项目结构

      .project
        |--build              项目构建完成后的文件所在目录
        |--src                项目源码
            |---js
            |---css
        |--Gruntfile.js       grunt的配置文件
    

安装

npm install -g grunt-cli
npm insatll --save-dev grunt

使用插件

  • 常用插件

    1. grunt-contrib-clear
    2. grunt-contrib-concat 将多个js文件合并成一个
    3. grunt-contrib-uglify 压缩js文件
    4. grunt-contrib-jshint js语法检查
    5. grunt-contrib-cssmin 压缩css文件
  • 安装

      npm install --save-dev grunt-contrib-concat
      npm install --save-dev grunt-contrib-uglify
      npm install --save-dev grunt-contrib-jshint
      npm install --save-dev grunt-contrib-cssmin
      npm install --save-dev grunt-contrib-watch      //真正实现自动化
    
  • 配置

      module.exports = function(grunt){
          grunt.initConfig({
              concat:{    //给指定插件进行配置,通常这里的插件名就是官网中插件名的后缀
                  options: {   //可选项
                      seperator: ';'  //指定使用分号来分隔不同js文件
                  },
                  dist:{  //输出的文件名可任意
                      files: {
                          'src/js/build.js': ['src/js/test1.js', 'src/js/test2.js'],    //指定输出的js文件,指定需要合并的js文件
                      }
                  }
              },
              pkg: grunt.file.readJSON('package.json'),       //读取json文件方便插件中调用json文件中的属性
              uglify: {
                options: {
                  //在压缩后的代码最前方加上自定义标题
                  banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
                    '<%= grunt.template.today("yyyy-mm-dd") %> */'
                },
                my_target: {     //输出的文件名可任意
                  files: {
                    'dist/js/build.min.js': ['src/js/build.js']
                  }
                }
              },
              jshint:{
                  //配置代码检查工具
                  options: {
                    "curly": true,
                    "eqnull": true,
                    "eqeqeq": true,
                    "undef": true,
                    "asi": true,
                    "predef": true,
                    "esversion": 5,
                    "globals": {          //设置需要忽略的全局变量
                      "jQuery": true,
                      "module": true,
                      'environment': true
                    }
                  },
                  build: {
                      files: {
                      src: ['Gruntfile.js', 'src/js/*.js']
                      }
                  }
              },
              cssmin:{
                  options: {
                  mergeIntoShorthands: false,
                  roundingPrecision: -1
                },
                build: {      //输出的文件名可任意
                  files: {
                    'dist/css/output.min.css': ['src/css/test1.css', 'src/css/test2.css']
                  }
                }
              },
              watch: {
                  scripts: { 
                    files: ['src/js/*.js', 'src/css/*.css'],
                    tasks: ['jshint', 'concat', 'uglify', 'cssmin'],
                    options: {
                      spawn: false,       //false:变量更新,只执行与修改了的文件相关的任务 true:全量更新,即无论修改哪个文件都会执行所有的任务
                    },
                  },
                }
          })
          grunt.loadNpmTasks('grunt-contrib-concat')  //加载插件
          grunt.loadNpmTasks('grunt-contrib-uglify')  //加载插件
          grunt.loadNpmTasks('grunt-contrib-jshint')  //加载插件
          grunt.loadNpmTasks('grunt-contrib-cssmin')  //加载插件
          grunt.loadNpmTasks('grunt-contrib-watch')
          grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'cssmin'])   //注册默认执行插件, 执行任务过程是同步的按照注册的顺序执行
          grunt.registerTask('myWatch', ['default', 'watch'])   //当调用此任务时才按照这个任务包含的任务顺序执行
      }
    
  • 运行

    只需要直接执行最后一行代码, 前三行代码作为注释

      //grunt concat    指定执行某个插件
      //grunt uglify    指定执行某个插件
      //grunt           当注册了默认任务插件时就能省略需要执行的插件名
      grunt myWatch   //调用注册的任务,此时才会监听
    

构建示例

grunt构建示例

gulp

  • gulp官方中文参考文档

  • 与grunt功能类似的项目构建工具, 也是基于nodejs的自动任务运行器

  • 能自动化完成js/coffee/sass/less/html/image/css的合并,压缩,检查,监听,浏览器刷新,测试等任务

  • gulp更高效(异步多任务), 更易于使用,插件高质量

  • 基于流的操作

  • 项目结构

      .project
          |---dist
          |---src
              |---js
              |---css
              |---less
          |---gulpfile.js
    

安装

npm install -g gulp-cli
npm install --save-dev gulp

使用插件

  • 常用插件

    1. gulp-concat 合并js/css文件
    2. gulp-uglify 压缩js文件
    3. gulp-rename 文件重命名
    4. gulp-less 编译less
    5. gulp-clean-css 压缩css
    6. gulp-htmlmin 压缩html
    7. gulp-livereload 实时自动编译刷新,半自动
    8. gulp-connect 实时自动编译刷新,服务器监听的全自动
    9. open 自动打开指定链接
    10. gulp-load-plugins 可以自动引入已下载好的所有gulp相关插件,无需重新引入
  • 安装

npm install -g gulp
npm install --save-dev gulp gulp-rename gulp-concat gulp-uglify
  • 配置

    网上的很多教程有使用task这个api, 但是官方文档提到已经不再推荐, 所以我们使用exports来代替

      const {src, dest, series, parallel, watch} = require('gulp')
      const $ = require('gulp-load-plugins')()
      const concat = require('gulp-concat')
      const rename = require('gulp-rename')
      const uglify = require('gulp-uglify')
      const cleanCss = require('gulp-clean-css')
      const htmlmin = require('gulp-htmlmin')
      const livereload = require('gulp-livereload')
      const connect = require('gulp-connect')
      const less = require('gulp-less')
      const open = require('open') 
    
      function js(){
          return src('src/js/*.js')
                 .pipe($.concat('build.js'))
                 .pipe(dest('dist/js/'))
                 .pipe($.uglify())
                 .pipe($.rename({suffix: '.min'}))
                 .pipe(dest('dist/js/'))
                 .pipe($.livereload())    //livereload插件的执行完重载
                 .pipe($.connect.reload())  //connect插件的执行完重载
      }
    
      function css(){
          return src('src/css/*.css')
                 .pipe($.concat('build.css'))
                 .pipe($.cleanCss({compatibility: 'ie8'}))
                 .pipe($.rename({suffix: '.min'}))
                 .pipe(dest('dist/css/'))
                 .pipe($.livereload())
                 .pipe($.connect.reload())
      }
      function lessfunc(){
          return src('src/less/*.less')
                 .pipe($.less())
                 .pipe(dest('src/css/'))
                 .pipe($.livereload())
                 .pipe($.connect.reload())
      }
      function html(){
          return src('index.html')
                 .pipe($.htmlmin({collapseWhitespace: true}))
                 .pipe(dest('dist/'))
                 .pipe($.livereload())
                 .pipe($.connect.reload())
      }
      function live(){
          $.livereload.listen()
          watch(['src/js/*.js'], js)    //监听特定文件的修改,然后执行特定流
          watch(['src/css/*.css'], css)
          watch(['src/less/*.less'], series(lessfunc, css))
          watch(['index.html'], html)
      }
      function connection(){
          $.connect.server({
              root: 'dist/',
              port: 5000,
              livereload: true
          })
          watch(['src/js/*.js'], js)
          watch(['src/css/*.css'], css)
          watch(['src/less/*.less'], series(lessfunc, css))
          watch(['index.html'], html)
          open('http://localhost:5000/index.html')
      }
      exports.js = js
      exports.css = css
      exports.less = lessfunc
      exports.html = html
      exports.allcss = series(lessfunc, css)    //同步执行方法
      exports.all = parallel(js, html, series(lessfunc, css))   //异步执行方法
      exports.live = series(parallel(js, html, series(lessfunc, css)), live)
      exports.connect = series(parallel(js, html, series(lessfunc, css)), connection)
    

构建示例

gulp构建示例

webpack

  • webpack中文官网

  • 什么是webpack

    • 现代javascript程序的模块打包器

    • 当webpack处理应用程序时,它会递归构造一个依赖关系图

    • 前端的所有资源都会当做模块处理,默认支持commonjs,AMD,ES6模块化语法

    • 将根据模块的依赖关系进行静态分析,生成对应的静态资源

  • 配置文件

    • webpack.config.js 是一个node模块, 返回一个json的格式的配置对象

核心概念

  • 入口entry

    • 用来指示webpack应该从哪个模块开始构建依赖关系图

    • 入口文件默认为src/index.js

  • 出口output

    • 用来指示webpack在哪里输出它所创建的bundle以及如何命名这些文件

    • 出口文件默认为dist/main.js,其他文件路径默认为dist/

    • 使用path参数配置出口路径

    • 使用filename参数配置出口文件名

  • loader

    • webpack本身只能加载js/json模块,如果要加载其他类型的文件(模块),就要使用对应的loader进行转换/加载

    • 当加载json模块时会默认获取一个对象

    • loader本身也是运行在nodejs环境中的javascript模块

    • 它本身是个函数,接收源文件作为参数,返回转换结果

    • loader一般以xxx-loader的方式命名,xxx表示这个loader要做的转换功能

    • 在配置loader时使用module.rules属性,它有test与use两个参数,test用来正则匹配需要使用loader的对应文件,use配置使用哪些loader

  • 插件plugin

    • 用以进行更加广泛的任务,包括打包优化,资源管理等

    • 想要使用插件只需要先require这个插件然后在plugins属性对应的数组中传入这个插件的实例即可使用

安装

npm install -g webpack-cli
npm install -D webpack webpack-cli
  • 文件结构

      .project
        |--dist              项目构建完成后的文件所在目录
        |--src                项目源码
            |---js
            |---css
        |--webpack.config.js       webpack的配置文件
    

快速使用

  • 命令行参数指定入口文件与出口文件

  • 按照模块化的形式依次加载各个模块,然后最终输出到一个文件中,减少请求数量

      webpack ./src/js/test1.js ./dist/js/build.js
    
  • 通过配置文件加载

      const path = require('path')
      module.exports = {
        entry: './src/js/index.js', //与模块加载路径规则相同,不可省略./
        output: {
          path: path.resolve(__dirname, 'dist/js'),
          filename: 'build.js'
        }
      }
    
  • 使用loader,plugin,webpack-dev-server的实例

      const path = require('path')
      const HtmlWebpackPlugin = require('html-webpack-plugin')
      module.exports = {
        entry: './src/js/test1.js', //与模块加载路径规则相同,不可省略./
        output: {
          path: path.resolve(__dirname, 'dist/js'),
          filename: 'build.js'
        },
        module: {
          rules: [{
            test: /\.css$/,       //匹配所有css文件
            use: ['style-loader', 'css-loader']     //以从右到左的顺序执行loader,前一个作用是将获取到的css文件内容以style标签的形式添加到引入入口文件的html中,后一个用来引入css文件
          },{
            test: /\.(gif|png|jpg)$/, //匹配所有图片
            use: {
              loader: 'url-loader',
              options: {
                limit: 8192       //单位为比特,小于这个大小的图片会使用base64编码放入入口文件,减少请求数量
              }
            }
          }]
        },
        plugins: [new HtmlWebpackPlugin({template: './index.html'})],   //此插件可创建以指定模板为内容的html文件,然后添加到出口文件所在目录中
        devServer: {
          contentBase: './dist/js/'     //设置访问此热加载服务器时的根路径,默认会加载index.html文件,不设置此参数会从根目录中查找
        }
      }
    

构建示例

[webpack构建示例](https://github.com/chuyueZhang/frontEndLearning/tree/master/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%9E%84%E5%BB%BA%E5%B7%A5%E5%85%B7/webpacktest)