学习笔记(八)——自动化构建

自动化构建

一切重复的工作都应该被自动化

自动化构建是前端工程化过程中一个重要的组成部分

自动化构建工作流可以使我们在开发阶段使用一些高效的语法、规范、标准和工具,脱离运行环境兼容性带来的问题,并最终自动转换成能够被运行环境支持的可执行代码

自动化构建初体验

尝试使用sass开发页面样式,并自动编译为css文件

  • 创建项目目录,并使用 yarn init -y 初始化

  • 添加index.html页面用于测试

  • yarn add sass --dev 添加sass模块作为开发依赖

    • 此时可以使用sass编写样式文件*.scss,并使用 yarn sass <source> <target> 命令将scss文件编译为css文件
  • 在package.json文件中,添加script属性,定义npm运行脚本

    • 此时可以通过 npm run <command>yarn <command> 运行script中定义的命令
  • yarn add npm-run-all --dev 安装npm-run-all作为开发依赖,可以用来顺序同时运行多个script中定义的命令

  • yarn add browser-sync --dev 安装browser-sync作为开发依赖,可以监控指定目录下的文件改动,并自动刷新浏览器

  • 上述步骤实现了开发过程中,自动将scss文件编译为css文件,并自动监听文件变化,刷新浏览器实时查看最新页面效果

    // package.json
    {
      "name": "sample",
      "version": "1.0.0",
      "main": "index.js",
      "license": "MIT",
      "scripts": {
        "build": "sass scss/main.scss css/main.css --watch",
        "serve": "browser-sync . --files \"*.html, css/*.css\"",
        "start": "run-p build serve"
      },
      "devDependencies": {
        "browser-sync": "^2.26.12",
        "sass": "^1.26.10",
        "npm-run-all": "^4.1.5"
      }
    }
    
    

常用的自动化构建工具

image-20200903162834889
  • Grunt
    • 插件生态丰富,可以实现任意类型项目的构建
    • 工作过程基于临时文件,磁盘读写频繁,构建速度较慢
  • Gulp
    • 插件生态丰富
    • 工作过程基于内存,构建速度较快
    • 支持同时进行多个构建任务
  • FIS
    • 由百度前端团队推出的构建工具
    • 捆绑式全家桶
    • 适合新手
  • Webpack?
    • Webpack属于模块打包工具

Grunt

  • 基本使用

    • 新建项目目录,并使用 yarn init -y 命令初始化

    • yarn add grunt 添加grunt模块

    • 添加gruntfile.js文件

      • Grunt的入口文件
      • 用于定义Grunt自动执行的任务
      • 需要导出一个函数
      • 函数接收grunt形参,用于提供创建任务时将会用到的的API
    • grunt.registerTask 注册待执行的task

      • 第一个参数为task的名称,如果名称为default,则为默认task,执行 yarn grunt 不指定task明时默认执行 default task
      • 第二个参数如果是回调函数,则表示task执行时要执行的内容
      • 第二个参数如果是字符串,则是对该task的描述信息,当执行yarn grunt --help 时会显示相应任务的描述
      • 第二个参数如果是数组,则接收由task名称组成的字符串数组,执行该task将会依次执行数组中指定的task
      • 如果task执行的是一个异步任务,需要使用调用 this.async() 返回的函数来标记异步操作执行完成,此时grunt会等待异步操作执行完成
    • yarn grunt <task> 执行gruntfile.js中定义的task

      // gruntfile.js
      module.exports = grunt => {
          grunt.registerTask('foo', () => {
              console.log('hello grunt')
          })
      
          grunt.registerTask('bar', 'description', () => {
              console.log('hello bar')
          })
      
          grunt.registerTask('default', ['foo', 'bar'])
      
          grunt.registerTask('async-task', function() {
              const done = this.async();
              setTimeout(()=> {
                  console.log('async task done')
                  done()
              }, 1000)
          })
      }
      
  • 标记任务失败

    • 同步任务通过在一个task的回调函数中 return false 来实现

    • 顺序执行多个任务时,当有一个任务被标记失败,后续任务将不再继续执行

    • 使用 --force 参数来强制执行所有任务

    • 异步任务标记失败,需要使用 this.async() 返回的函数,传入false

      // gruntfile.js
      module.exports = grunt => {
          grunt.registerTask('foo', () => {
              console.log('hello grunt')
          })
      
          grunt.registerTask('bar', 'description', () => {
              console.log('hello bar')
          })
      
          grunt.registerTask('bad', ()=> {
              console.log('bad task')
              return false
          })
      
          grunt.registerTask('default', ['foo', 'bad', 'bar'])
      
          grunt.registerTask('async-task', function() {
              const done = this.async();
              setTimeout(()=> {
                  console.log('async task fail')
                  done(false)
              }, 1000)
          })
      }
      
  • 配置方法

    • 使用 grunt.initConfig({}) 方法进行grunt配置参数的初始化

    • 在task的回调函数中,通过 grunt.config(key) 方法可以获取参数配置对象中的值

      // gruntfile.js
      module.exports = grunt => {
          grunt.initConfig({
              hello: {
                  what: 'world'
              }
          })
      
          grunt.registerTask('foo', () => {
              console.log(`hello ${grunt.config('hello.what')}`)
          })
      }
      
  • 多目标任务

    多目标模式,可以让任务根据配置形成多个子任务

    • 使用 grunt.registerMultiTask(<task>, <func> 来注册多目标任务

    • 多目标任务需要通过 grunt.initConfig 来配置相应的子任务

      • 以task名称作为key,value是配置对象
      • 配置对象以子任务名称作为key,在任务运行时可以通过 this.target 获取当前执行的子任务名称
      • 配置对象的值为运行时的配置数据,在任务运行时可以通过 this.data 获取当前执行的子任务的配置数据
      • 当配置对象的key为options时,代表任务运行时的选项配置,而不代表一个子任务
      • 子任务的配置数据中也可以包含options,此时该子任务中配置的options覆盖任务全局的options
      // gruntfile.js
      module.exports = grunt => {
          grunt.initConfig({
              build: {
                  options: {
                      hello: 'world'
                  },
                  css: {
                      value: 'css'
                  },
                  js: {
                      options: {
                          hello: 'grunt'
                      },
                      value: 'js'
                  }
              },
              hello: {
                  what: 'world'
              }
          })
          grunt.registerMultiTask('build', function() {
              console.log(this.options())
              console.log(this.target)
              console.log(this.data)
          })
      }
      
  • 插件的使用

    • 安装相应的grunt插件模块,例如 yarn add grunt-contrib-clean

    • 使用 grunt.loadNpmTasks 加载插件,并在 grunt.initConfig() 中配置相关的参数(这里grunt-contrib-clean是多目标任务)

    • 执行插件相关的task

      // gruntfile.js
      module.exports = grunt => {
          grunt.initConfig({     
              clean: {
                  temp: 'temp/*.txt'
              }
          })
          grunt.loadNpmTasks('grunt-contrib-clean')
      }
      
  • 常用插件及总结

    • grunt-sass 用于scss转css
      • yarn add grunt-sass sass --dev
    • grunt-babel 用于js语法转换
      • yarn add grunt-babel @babel/core @babel/present-env --dev
    • grunt-contrib-watch 开发阶段监控文件改动,并自动执行配置指定的task
      • yarn add grunt-contirb-watch --dev
    • load-grunt-tasks 用于自动加载所有grunt插件中的任务
      • yarn add load-grunt-tasks --dev

Gulp

  • gulp的基本使用

    • 新建项目目录,并使用 yarn init -y 命令初始化

    • yarn add gulp 添加gulp模块

    • 添加gulpfile.js文件

      • gulp的入口文件
      • 用于定义gulp自动执行的任务
      • gulp 4.0以上的版本通过exports.task的方式导出一个task,exports.default为默认的task
      • gulp定义任务为异步任务,需要在任务执行的函数中调用参数传入的方法,去标记任务是否执行完成
    • yarn gulp <task> 执行gulpfile.js中定义的task

      // gulpfile.js  gulp的入口文件
      
      exports.foo  = done => {
          console.log('gulp foo')
          done()
      }
      
      exports.default = done => {
          console.log('gulp default')
          done()
      }
      
      // gulp 4.0以前版本定义task方式
      const gulp = require('gulp')
      
      gulp.task('bar', done => {
          console.log('gulp bar')
          done()
      })
      
  • gulp的组合任务

    • gulp提供了 seriesparallel 两种方式来组合执行多个任务
    • series为串行执行多个任务
    • parallel为并行执行多个任务
    // gulpfile.js
    const { series, parallel } = require('gulp')
    
    const task1 = done => {
        setTimeout(() => {
            console.log('task 1 working')
            done()
        }, 1000);
    }
    
    const task2 = done => {
        setTimeout(() => {
            console.log('task 2 working')
            done()
        }, 1000);
    }
    
    const task3 = done => {
        setTimeout(() => {
            console.log('task 3 working')
            done()
        }, 1000);
    }
    
    exports.series = series(task1, task2, task3)
    
    exports.parallel = parallel(task1, task2, task3)
    
  • gulp的异步任务常用的几种方式

    • 回调函数方式

      • gulp异步任务函数接收一个回调函数参数,通过该回调函数,可以标记异步任务是否执行完成,或者发生异常,当发生异常,后续任务将不再继续执行

        exports.callback = done => {
            console.log('callback')
            done()
        }
        
        exports.callback_error = done => {
            console.log('callback error')
            done(new Error('callback error'))
        }
        
    • Promise

      • gulp异步任务支持使用Promise方式,标记异步任务执行完成,返回一个resolved状态的Promise对象,标记任务失败,返回一个rejected状态的Promise对象

        exports.promise = () => {
            console.log('promise')
            return Promise.resolve()
        }
        
        exports.promise_error = () => {
            console.log('promise error')
            return Promise.reject(new Error('promise error'))
        }
        
    • async/await

      • async/await是Promise的语法糖,使用node版本8以上时,gulp也支持使用该方式处理异步任务

        const timeout = time => {
            return new Promise((resolve) => {
                setTimeout(resolve, time)
            })
        }
        
        exports.async = async () => {
            await timeout(1000)
            console.log('async timeout')
        }
        
        
    • stream

      • 通常在自动化构建过程中需要处理大量的文件,stream也是gulp异步任务处理中常用的方式

      • 通过返回一个stream对象,当stream的end方法被调用时,异步任务会被标记完成

        const fs = require('fs')
        
        exports.stream = () => {
            const readStream = fs.createReadStream('package.json')
            const writeStream = fs.createWriteStream('temp.txt')
            readStream.pipe(writeStream)
            return readStream
        }
        // 等同于
        exports.stream_callback = done => {
            const readStream = fs.createReadStream('package.json')
            const writeStream = fs.createWriteStream('temp.txt')
            readStream.pipe(writeStream)
            readStream.on('end', () => {
                done()
            })   
        }
        
  • gulp构建过程核心工作原理

    官方对于gulp的定义是the streaming build system

    即基于流的构建系统

    • 构建的流程通常是:读取文件 -> 处理读取内容 -> 写入文件

    • 对应gulp的工作过程:读取流(read stream) -> 转换流(transform stream) -> 写入流 (write stream)

      // 尝试模拟实现css的压缩
      const fs = require('fs')
      const { Transform } = require('stream')
      
      exports.transform = () => {
          const read = fs.createReadStream('style.css')
          const write = fs.createWriteStream('style.min.css')
      
          const transform = new Transform({
              transform: (chunk, encoding, callback) => {
                  const input = chunk.toString()
                  const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '') // 去除空格及注释
                  callback(null, output)
              }
          })
      
          return read
              .pipe(transform)
              .pipe(write)
      }
      
  • gulp文件操作API

    • 使用gulp提供的src与dest来实现文件的读取流与写入流

      // 使用src批量读取文件,使用dest批量写入文件
      // 借助gulp-clean-css插件压缩css
      // 借助gulp-rename插件重命名
      const { src, dest } = require('gulp')
      const cleanCss = require('gulp-clean-css')
      const rename = require('gulp-rename')
      
      exports.minify = () => {
          return src('style.css')
              .pipe(cleanCss())
              .pipe(rename({
                  extname: '.min.css'
              }))
              .pipe(dest('dist'))
      }
      
  • gulp案例

    这里通过一个工程样例来展示,在实际开发过程中,对一个前端工程使用gulp进行自动化构建可能会涉及到的各种编译配置

    例如样式文件sass的编译,脚本ES6+的转换,HTML文件的转换等等

    工程demo见 https://github.com/zce/zce-gulp-demo.git

    • 样式编译

      const { src, dest } = require('gulp');
      const sass = require('gulp-sass');
      
      const style = () => {
          return src('src/assets/styles/*.scss', { base: 'src' })
              .pipe(sass({
                  outputStyle: 'expanded'
              }))
              .pipe(dest('dist/styles'))
      }
      
      module.exports = {
          style,
      }
      
    • 脚本编译

      // gulpfile.js
      const { src, dest } = require('gulp');
      const babel = require('gulp-babel');
      
      const script = () => {
          return src('src/assets/scripts/*.js', { base: 'src' })
              .pipe(babel({
                  presets: ['@babel/preset-env']
              }))
              .pipe(dest('dist'))
      }
      
      module.exports = {
          script,
      }
      
    • 页面模板编译

      // gulpfile.js
      const { src, dest } = require('gulp');
      const swig = require('gulp-swig');
      
      const data = {
          menus: [
            {
              name: 'Home',
              icon: 'aperture',
              link: 'index.html'
            },
            {
              name: 'Features',
              link: 'features.html'
            },
            {
              name: 'About',
              link: 'about.html'
            },
            {
              name: 'Contact',
              link: '#',
              children: [
                {
                  name: 'Twitter',
                  link: 'https://twitter.com/w_zce'
                },
                {
                  name: 'About',
                  link: 'https://weibo.com/zceme'
                },
                {
                  name: 'divider'
                },
                {
                  name: 'About',
                  link: 'https://github.com/zce'
                }
              ]
            }
          ],
          pkg: require('./package.json'),
          date: new Date()
      }
      
      const page = () => {
          return src('src/*.html', { base: 'src' })
              .pipe(plugins.swig({ data, defaults: { cache: false } })) // 关闭缓存防止修改不实时生效
              .pipe(dest('temp'))
      }
      
      module.exports = {
          page,
      }
      
    • 图片和字体文件转换

      // gulpfile.js
      const { src, dest} = require('gulp');
      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/fonts/**', { base: 'src' })
              .pipe(imagemin())
              .pipe(dest('dist'))
      }
      
      module.exports = {
          image,
          font,
      }
      
    • 其他文件及文件清除

      const del = require('del');
      
      const extra = () => {
          return src('public/**', { base: 'public' })       
              .pipe(dest('dist'))
      }
      
      const clean = () => {
          return del(['dist'])
      }
      
    • 自动加载插件

      const loadPlugins = require('gulp-load-plugins');
      const plugins = loadPlugins();
      // 使用gulp-load-plugins插件自动加载所有已安装的以gulp-开头的插件,并使用plugins.xxxx访问相应的插件
      // 例如使用plugins.sass访问gulp-sass插件
      
    • 开发服务器

      const browserSync = require('browser-sync');
      
      const bs = browserSync.create();
      
      const serve = () => {
          bs.init({
              notify: false,
              files: 'dist/**',
              server: {
                  baseDir: 'dist',
                  routes: {
                      '/node_modules': './node_modules'
                  }
              }
          })
      }
      
    • 监视变化以及构建优化

      • 使用gulp提供的watch API来监控指定文件,并在文件变化时,执行相应的task
      • watch接收两个参数,第一个参数是文件通配符字符串或者通配符字符串数组,表示要监视的文件,第二个参数是文件变化时要执行的task函数
      • 在开发阶段,对于图片及字体的压缩以及静态资源的拷贝意义不大,同时会增加构建任务的开销,将这些文件保留在源文件目录(需指定baseDir)并直接通过watch对这些目录进行监视,在文件发生变化时,执行bs.reload刷新页面
      // gulpfile.js
      const { src, dest, series, parallel, watch } = require('gulp');
      
      const bs = browserSync.create();
      
      const serve = () => {
          watch('src/assets/styles/*.scss', style)
          watch('src/assets/scripts/*.js', script)
      watch('src/**/*.html', page)
          // watch('src/assets/images/**', image) 开发阶段意义不大
          // watch('src/assets/fonts/**', font) 开发阶段意义不大
          // watch('public/**', extra) 开发阶段意义不大
          watch([
              'src/assets/images/**',
              'src/assets/fonts/**',
              'public/**',
          ], bs.reload)
      
          bs.init({
              notify: false,
              files: 'dist/**',
              server: {
                  baseDir: ['dist', 'src', 'public'],
                  routes: {
                      '/node_modules': './node_modules'
                  }
              }
          })
      }
      
    • useref文件引用处理

      • 依赖gulp-useref插件,可以将html中依赖的js、css根据注释提取并生成到指定的文件中,并替换依赖的资源文件路径为新生成的文件路径
      const useref = () => {
          return src('dist/*.html', { base: 'dist' })
              .pipe(plugins.useref({
                  searchPath: ['dist', '.']
              }))
              .pipe(dest('dist'))
      }
      
    • 文件压缩

      • 使用相关的gulp插件对相应类型的文件进行压缩处理
        • gulp-uglify 压缩js文件
        • gulp-clean-css 压缩css文件
        • gulp-htmlmin 压缩html文件
      • 为避免文件读写冲突,可以将压缩后的代码放入另外的文件夹,例如release
      const useref = () => {
          return src('dist/*.html', { base: 'dist' })
              .pipe(plugins.useref({
                  searchPath: ['dist', '.']
              }))
              .pipe(plugins.if(/\.js$/, plugins.uglify()))
              .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
              .pipe(plugins.if(/\.html$/, plugins.htmlmin({
                  collapseWhitespace: true,
                  minifyCSS: true,
                  minifyJS: true,
              })))
              .pipe(dest('release'))
      }
      
    • 重新规划构建过程

      • 对上述构建任务进行重新整理
      • style、script、page可以通过parallel组合成并行处理任务compile const compile = parallel(style, script, page),生成的文件由于需要后续进行useref压缩处理,构建过程中临时存放在temp目录,最终压缩后放入dist目录
      • useref需要先进行compile生成临时文件,可以使用series组合成串行处理任务,最终组合成构建任务build const build = series(clean, parallel(series(compile, useref), image, font, extra))
      • 去除不必要exports的任务
      • 将对应的构建任务加入package.json的script中
      • 完整的gulpfile.js如下
       // gulpfile.js
      const { src, dest, series, parallel, watch } = require('gulp');
      const loadPlugins = require('gulp-load-plugins');
      const browserSync = require('browser-sync');
      const del = require('del');
      
      const plugins = loadPlugins();
      
      const bs = browserSync.create();
      
      const data = {
        menus: [
          {
            name: 'Home',
            icon: 'aperture',
            link: 'index.html'
          },
          {
            name: 'Features',
            link: 'features.html'
          },
          {
            name: 'About',
            link: 'about.html'
          },
          {
            name: 'Contact',
            link: '#',
            children: [
              {
                name: 'Twitter',
                link: 'https://twitter.com/w_zce'
              },
              {
                name: 'About',
                link: 'https://weibo.com/zceme'
              },
              {
                name: 'divider'
              },
              {
                name: 'About',
                link: 'https://github.com/zce'
              }
            ]
          }
        ],
        pkg: require('./package.json'),
        date: new Date()
      }
      
      const style = () => {
          return src('src/assets/styles/*.scss', { base: 'src' })
              .pipe(plugins.sass({
                  outputStyle: 'expanded'
              }))
              .pipe(dest('temp'))
      }
      
      const script = () => {
          return src('src/assets/scripts/*.js', { base: 'src' })
              .pipe(plugins.babel({
                  presets: ['@babel/preset-env']
              }))
              .pipe(dest('temp'))
      }
      
      const page = () => {
          return src('src/**/*.html', { base: 'src' })
              .pipe(plugins.swig({ data }))
              .pipe(dest('temp'))
      }
      
      const image = () => {
          return src('src/assets/images/**', { base: 'src' })
              .pipe(plugins.imagemin())
              .pipe(dest('dist'))
      }
      
      const font = () => {
          return src('src/assets/fonts/**', { base: 'src' })
              .pipe(plugins.imagemin())
              .pipe(dest('dist'))
      }
      
      const extra = () => {
          return src('public/**', { base: 'public' })        
              .pipe(dest('dist'))
      }
      
      const clean = () => {
          return del(['dist', 'temp'])
      }
      
      const compile = parallel(style, script, page)
      
      const serve = () => {
          watch('src/assets/styles/*.scss', style)
          watch('src/assets/scripts/*.js', script)
          watch('src/**/*.html', page)
          // watch('src/assets/images/**', image) 开发阶段意义不大
          // watch('src/assets/fonts/**', font) 开发阶段意义不大
          // watch('public/**', extra) 开发阶段意义不大
          watch([
              'src/assets/images/**',
              'src/assets/fonts/**',
              'public/**',
          ], bs.reload)
      
      
          bs.init({
              notify: false,
              files: 'temp/**',
              server: {
                  baseDir: ['temp', 'src', 'public'],
                  routes: {
                      '/node_modules': './node_modules'
                  }
              }
          })
      }
      
      const useref = () => {
          return src('temp/*.html', { base: 'temp' })
              .pipe(plugins.useref({
                  searchPath: ['temp', '.']
              }))
              .pipe(plugins.if(/\.js$/, plugins.uglify()))
              .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
              .pipe(plugins.if(/\.html$/, plugins.htmlmin({
                  collapseWhitespace: true,
                  minifyCSS: true,
                  minifyJS: true,
              })))
              .pipe(dest('dist'))
      }
      
      const build = series(clean, parallel(series(compile, useref), image, font, extra))
      
      const dev = series(compile, serve)
      
      module.exports = {
          clean,
          build,
          dev,
      }
      
  • 封装工作流

    多个项目中复用gulpfile中的task

    • 准备

      • 创建项目托管repository(github/gitee等)
      • 创建项目目录xxx,并初始化 yarn init -y
      • 创建目录结构及文件lib/index.js
    • 提取gulpfile

      • 将原gulpfile.js中的内容复制到lib/index.js文件中
      • 将原package.json中devDependencies中的依赖添加到当前package.json的dependencies中
      • yarn安装相应的依赖
      • yarn link 建立软连接
      • 进入待构建项目目录,执行yarn link xxx,执行相应的命令yarn xxx,测试当前的封装
    • 解决模块中的问题

      • 将原gulpfile.js中使用到的data数据,改为读取配置文件xxx.config.js
      • 对babel使用的插件模块,使用require引入
    • 抽象路径配置

      • 将原gulpfile.js中使用到的项目路径,通过config进行配置,并提供默认值,同时替换所有使用位置为config读取

        // lib/index.js
        const { src, dest, series, parallel, watch } = require('gulp');
        const loadPlugins = require('gulp-load-plugins');
        const browserSync = require('browser-sync');
        const del = require('del');
        
        const plugins = loadPlugins();
        
        const bs = browserSync.create();
        
        const cwd = process.cwd()
        let config = {
            // default config
            build: {
                src: 'src',
                temp: 'temp',
                dist: 'dist',
                public: 'public',
                paths: {
                    styles: 'assets/styles/*.scss',
                    scripts: 'assets/scripts/*.js',
                    pages: '*.html',
                    images: 'assets/images/**',
                    fonts: 'assets/fonts/**',
                }
            }
        }
        
        try {
            const loaded = require(`${cwd}/pages.config.js`)
            config = { ...config, ...loaded }
        } catch(e) {}
         
        
        const style = () => {
            return src(config.build.paths.styles, { base: config.build.src, cwd: config.build.src })
                .pipe(plugins.sass({
                    outputStyle: 'expanded'
                }))
                .pipe(dest(config.build.temp))
        }
        
        const script = () => {
            return src(config.build.paths.scripts, { base: config.build.src, cwd: config.build.src })
                .pipe(plugins.babel({
                    presets: [require('@babel/preset-env')]
                }))
                .pipe(dest(config.build.temp))
        }
        
        const page = () => {
            return src(config.build.paths.pages, { base: config.build.src, cwd: config.build.src })
                .pipe(plugins.swig({ data: config.data }))
                .pipe(dest(config.build.temp))
        }
        
        const image = () => {
            return src(config.build.paths.images, { base: config.build.src, cwd: config.build.src })
                .pipe(plugins.imagemin())
                .pipe(dest(config.build.dist))
        }
        
        const font = () => {
            return src(config.build.paths.fonts, { base: config.build.src, cwd: config.build.src })
                .pipe(plugins.imagemin())
                .pipe(dest(config.build.dist))
        }
        
        const extra = () => {
            return src('**', { base: config.build.public, cwd: config.build.public })        
                .pipe(dest(config.build.dist))
        }
        
        const clean = () => {
            return del([config.build.dist, config.build.temp])
        }
        
        const compile = parallel(style, script, page)
        
        const serve = () => {
            watch(config.build.paths.styles, { cwd: config.build.src}, style)
            watch(config.build.paths.scripts, { cwd: config.build.src}, script)
            watch(config.build.paths.pages, { cwd: config.build.src}, page)
            // watch('src/assets/images/**', image) 开发阶段意义不大
            // watch('src/assets/fonts/**', font) 开发阶段意义不大
            // watch('public/**', extra) 开发阶段意义不大
            watch([
                config.build.paths.images,
                config.build.paths.fonts,        
            ], { cwd: config.build.src }, bs.reload)
        
            watch([
                '**',        
            ], { cwd: config.build.public }, bs.reload)
        
            bs.init({
                notify: false,
                files: `${config.build.temp}/**`,
                server: {
                    baseDir: [config.build.temp, config.build.src, config.build.public],
                    routes: {
                        '/node_modules': './node_modules'
                    }
                }
            })
        }
        
        const useref = () => {
            return src(config.build.paths.pages, { base: config.build.temp, cwd: config.build.temp })
                .pipe(plugins.useref({
                    searchPath: [config.build.paths.temp, '.']
                }))
                .pipe(plugins.if(/\.js$/, plugins.uglify()))
                .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
                .pipe(plugins.if(/\.html$/, plugins.htmlmin({
                    collapseWhitespace: true,
                    minifyCSS: true,
                    minifyJS: true,
                })))
                .pipe(dest(config.build.dist))
        }
        
        const build = series(clean, parallel(series(compile, useref), image, font, extra))
        
        const dev = series(compile, serve)
        
        module.exports = {
            clean,
            build,
            dev,
        }
        
    • 包装Gulp CLI

      • 项目目录下新建bin/xxx.js

      • package.js中增加bin选项配置对应的文件

      • bin/xxx.js中,读取process.argv命名行参数数组,并添加gulp执行相关参数

      • require('gulp/bin/gulp') 执行gulp命令

        #!/usr/bin/env node
        
        process.argv = process.argv.concat(['--cwd', process.cwd(), '--gulpfile', require.resolve('..')])
        
        require('gulp/bin/gulp')
        
    • 发布并使用模块

      • 执行yarn publish --registry=https://registry.yarnpkg.com发布封装的工作流模块到npm
      • 在待使用的项目中,执行yarn add xxx --dev添加相应的依赖
      • xxx build 进行使用

FIS

由百度前端团队推出的自动化构建工具,内置了很多常用的任务以及devServer,不需要开者自己去配置

  • 基本使用
    • yarn add fis3 --dev 安装fis3模块
    • fis3 release 编译,使用-d参数可以指定输出目录 fis3 release -d output
    • 添加配置文件fis-config.js,通过配置文件可以对fis3编译进行配置
  • 详细用法
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342