前端工程化(下)之自动化构建

自动化构建

自动化:通过机器代替手工完成一些工作
构建:转换
开发中的自动化构建就是把开发中写出来的代码自动的转换成生产环境可以运行的代码,这个过程叫做自动化构建工作流

npm scripts

实现自动化构建最简单的方式。主要解决在项目开发阶段重复的一些命令,可以在里面定义一些与项目开发有关的脚本命令,这样就可以让这些命令跟着项目一起维护,便于后期在开发过程中的使用
键名:命令的名称
值:需要执行的命令
注:scripts可以自动的去发现node_modules里面的命令,所以不需要写完整的命令,只需要写命令的名称就可以。

  1. 初始化一个package.json
  2. 安装sass npm i sass --save --dev
  3. 安装browser-sync 用于启动一个服务器,运行项目 npm i browser-sync --save --dev
  4. preserve:npm scripts的钩子机制,他会自动在server命令执行之前执行。这样可以避免在browser-sync执行之前css文件还没有生成
  5. 为sass命令添加一个–watch的参数,这样sass在工作时就可以监听文件变化,一旦代码当中的sass问价发生改变,他就会被自动编译
  6. npm-run-all 同时执行多个文件 npm i npm-run-all --save--dev
  7. 给browser-sync添加 --files的参数,可以让项目启动之后自动监听项目下一些文件的变化,一旦变化之后browser-sync会将这些文件的变化自动同步到浏览器,从而更新浏览器中的界面。"serve": "browser-sync . --files \"css/*.css\"",
    package.json
    常用的构建工具
    构建工具
    grunt: 优点:插件生态完善 缺点:工作过程是基于临时文件完成的,所以构建速度相对较慢
    gulp:基于内存实现的,构建速度快,且同时可以执行多个任务,插件生态完善、
    FIS:百度的前端团队推出的一套构建系统,把项目中的典型需求尽可能集成在了内部,可以很轻松的处理资源加载,模块化开发,代码部署,甚至性能优化。

Grunt的基本用方法

  1. npm init -y 初始化package.json
  2. 安装grunt
  3. 创建gruntfile.js
// Grunt 的入口文件
// 用于定义一些需要 Grunt 自动执行的任务
// 需要导出一个函数
// 此函数接收一个 grunt 的对象类型的形参
// grunt 对象中提供一些创建任务时会用到的 API

module.exports = grunt => {
    grunt.registerTask('foo', 'a sample task', () => {
      console.log('hello grunt')
    })
  
    grunt.registerTask('bar', '任务描述', () => { // 第二个参数可以是任务描述,可以在--help中看到
      console.log('other task')
    })
  
    // // default 是默认任务名称
    // // 通过 grunt 执行时可以省略
    // grunt.registerTask('default', () => {
    //   console.log('default task')
    // })
  
    // 第二个参数可以指定此任务的映射任务,
    // 这样执行 default 就相当于执行对应的任务
    // 这里映射的任务会按顺序依次执行,不会同步执行
    grunt.registerTask('default', ['foo', 'bar'])
  
    // 也可以在任务函数中执行其他任务
    grunt.registerTask('run-other', () => {
      // foo 和 bar 会在当前任务执行完成过后自动依次执行
      grunt.task.run('foo', 'bar')
      console.log('current task runing~')
    })
  
    // 默认 grunt 采用同步模式编码
    // 如果需要异步可以使用 this.async() 方法创建回调函数
    // grunt.registerTask('async-task', () => {
    //   setTimeout(() => {
    //     console.log('async task working~')
    //   }, 1000)
    // })
  
    // 由于函数体中需要使用 this,所以这里不能使用箭头函数
    grunt.registerTask('async-task', function () { // grunt代码默认支持同步模式
      const done = this.async() // 如果要使用异步模式,必须要通过this的async方法得到一个回调函数
      setTimeout(() => {
        console.log('async task working~')
        done() // 在异步完成之后调用回调函数,表示异步任务已经被完成
      }, 1000)
    })
  }
  1. 在package.json中添加执行命令
 "scripts": {
    "grunt": "grunt"
  },
Grunt标记失败任务

通过return false。

module.exports = grunt => {
  // 任务函数执行过程中如果返回 false
  // 则意味着此任务执行失败
  grunt.registerTask('bad', () => {
    console.log('bad working~')
    return false
  })

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

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

  // 如果一个任务列表中的某个任务执行失败
  // 则后续任务默认不会运行
  // 除非 grunt 运行时指定 --force 参数强制执行
  grunt.registerTask('default', ['foo', 'bad', 'bar'])

  // 异步函数中标记当前任务执行失败的方式是为回调函数指定一个 false 的实参
  grunt.registerTask('bad-async', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('async task working~')
      done(false)
    }, 1000)
  })
}
多目标任务
module.exports = grunt => {
  // 多目标模式,可以让任务根据配置形成多个子任务

  // grunt.initConfig({
  //   build: {
  //     foo: 100,
  //     bar: '456'
  //   }
  // })

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

  grunt.initConfig({
    build: {
      options: { // 作为一个任务的配置选项出现
        msg: 'task options'
      },
      foo: {
        options: { // 这里添加options会覆盖掉任务当中的选项
          msg: 'foo target options'
        }
      },
      bar: '456'
    }
  })

  grunt.registerMultiTask('build', function () {
    console.log(this.options()) // 拿到他的配置选项
  })
}

Grunt插件的使用
  1. 找到插件,然后把他安装到npm模块当中
  2. 在gruntfile当中通过grunt.loadNpmTasks()方法,去把插件当中提供的任务加载进来
  3. 在grunt.initConfig()中去给这些任务添加一些配置选项
  4. 例如 grunt-contrib-clean ,清除项目中的一些无用文件
    module.exports = grunt => {
      grunt.initConfig({
        clean: {
          temp: 'temp/**'
        }
      })
      
      grunt.loadNpmTasks('grunt-contrib-clean')
    }
    

Grunt中常用的插件

  • grunt-sass,需要有sass的模块支持 npm i grunt-sass sass --dev
  • grunt-babel,处理es6语法,需要babel的核心模块 npm i grunt-babel @babel/core @babel/preset-env --dev
    这个@babel是私有包的表示方法(私有又分公开私有和私密私有,私密私有是要收费的)
    翻译过来就是 @后面接的是个scope 一个作用区域,也可以是说这个包的归属者,防止包名称被提前占用,比如防止以前 babel-core 这个包名可能被人提前占用了,而babel官方还想使用那就可以把包放在自己的作用区域下,@组织名(babel)/属于该组织的包名(core)
  • 随着npm包的越来越多,那么使用loadNpmTasks的操作也会越来越多,这时,可以安装loadGruntTasks可以减少loadNpmTasks的使用 npm i load-grunt-tasks --dev
  • npm i grunt-contrib-watch --dev 当文件修改完成之后自动编译
const sass = require('sass')
const loadGruntTasks = require('load-grunt-tasks')

module.exports = grunt => {
  grunt.initConfig({
    sass: { // 给sass的任务去配置目标
      options: {
        sourceMap: true,
        implementation: sass // implementation用来去指定在grunt-sass当中,使用哪个模块去处理sass的编译
      },
      main: { // 执行输入文件和输出文件 运行npm run grunt sass
        files: {
          'dist/css/main.css': 'src/scss/main.scss'
        }
      }
    },
    babel: {
      options: {
        sourceMap: true,
        presets: ['@babel/preset-env'] // preset-env默认根据最新的es特性做转换
      },
      main: {
        files: {
          'dist/js/app.js': 'src/js/app.js'
        }
      }
    },
    watch: {
      js: {
        files: ['src/js/*.js'], // 监视特定文件
        tasks: ['babel'] // 当文件发生改变的时候需要执行的任务
      },
      css: {
        files: ['src/scss/*.scss'], // scss是sass的新扩展名
        tasks: ['sass'] // 
      }
    }
  })

  // grunt.loadNpmTasks('grunt-sass')
  loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务

  grunt.registerTask('default', ['sass', 'babel', 'watch']) // 启动的同时执行
}

Gulp

gulp的基本使用
// // 导出的函数都会作为 gulp 任务
// exports.foo = () => {
//   console.log('foo task working~')
// }

// 在最新的gulp当中取消了同步代码模式。约定每一个任务都必须是一个异步的任务,当任务执行结束之后需要调用回调函数去标记这个任务是否已经完成
// gulp 的任务函数都是异步的
// 可以通过调用回调函数标识任务完成
exports.foo = done => {
    console.log('foo task working~')
    done() // 标识任务执行完成
  }
  
  // default 是默认任务
  // 在运行是可以省略任务名参数
  exports.default = done => {
    console.log('default task working~')
    done()
  }
  
  // v4.0 之前需要通过 gulp.task() 方法注册任务
  const gulp = require('gulp')
  gulp.task('bar', done => {
    console.log('bar task working~')
    done()
  })
  
Gulp创建组合任务
const { series, parallel } = require('gulp')

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)  
}

// series让多个任务按照顺序依次执行(例如部署,需要先执行编译任务)
exports.foo = series(task1, task2, task3)

// parallel让多个任务同时执行(例如编译css和编译js的时候可以使用)
exports.bar = parallel(task1, task2, task3)
Gulp异步任务
const fs = require('fs')

exports.callback = done => {
  console.log('callback task')
  done()
}
// 与node当中的回调函数是同样的标准,都是一种错误优先的回调函数,且如果是多个任务同时执行的话,后续的任务将不会执行
exports.callback_error = done => {
  console.log('callback task')
  done(new Error('task failed'))
}
// promise避免了回调函数嵌套过深的问题
exports.promise = () => {
  console.log('promise task')
  return Promise.resolve() // 返回一个成功的状态,意味着这个函数正常的结束了
}

exports.promise_error = () => {
  console.log('promise task')
  return Promise.reject(new Error('task failed')) // 返回一个失败的状态,意味着失败的执行
}

const timeout = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  })
}
// async是promise的语法糖
exports.async = async () => {
  await timeout(1000)
  console.log('async task')
}
// 异步处理文件
exports.stream = () => {
  const read = fs.createReadStream('yarn.lock') // 创建一个读取文件的文件流
  const write = fs.createWriteStream('a.txt') // 创建一个写入文件的文件流
  read.pipe(write) // 将read导入到write当中,文件复制的作用
  return read // 结束的时机就是read end的时候
}

// exports.stream = done => {
//   const read = fs.createReadStream('yarn.lock')
//   const write = fs.createWriteStream('a.txt')
//   read.pipe(write)
//   read.on('end', () => { // 模拟end事件
//     done() 
//   })
// }

gulp构建过程

gulp定义是基于流的构建系统gulp构建过程

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

exports.default = () => {
  // 文件读取流
  const readStream = fs.createReadStream('normalize.css')

  // 文件写入流
  const writeStream = fs.createWriteStream('normalize.min.css')

  // 文件转换流
  const transformStream = new Transform({
    // 核心转换过程
    transform: (chunk, encoding, callback) => {
      // 核心转换过程实现
      // chunk =》 读取流中读取到的内容(Buffer)
      const input = chunk.toString() // 读取出来的是一个字节数组,通过t`oString的方式转换成字符串
      const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '')
      callback(null, output) // callback是错误优先的回调函数,第一个参数应该传入错误对象,如果说没有发生错误,可以传入null
    }
  })
  // pipe 把读取出来的文件流导入写入文件流
  return readStream
    .pipe(transformStream) // 转换
    .pipe(writeStream) // 写入
}

Gulp文件操作API

使用Gulp的常规过程

const { src, dest } = require('gulp')
const cleanCSS = require('gulp-clean-css') // 提供了压缩css代码的转换流
const rename = require('gulp-rename') // 重命名的扩展名

exports.default = () => {
  return src('src/*.css') // 通配src下所有的css文件
    .pipe(cleanCSS())
    .pipe(rename({ extname: '.min.css' }))
    .pipe(dest('dist'))
}

Gulp案例
  1. 用browser-sync提供的一个web服务器去启动web服务,支持开发阶段的热更新
  2. watch监视文件路径的通配符,根据监视结果去决定是否去执行一个任务
  3. 思考开发阶段哪些任务需要去执行,哪些任务不是必须要执行的。比如sass,js等必须执行,image,font等不必要执行
  4. useref 插件会自动处理html中的构建注释。会自动将开始标签和结束标签中间的文件打包到一个文件当中
const { src, dest, parallel, series, watch } = require('gulp')

const del = require('del')
const browserSync = require('browser-sync') // 提供一个开发服务器,支持热更新(不是gulp插件,所以需要单独引用)

const loadPlugins = require('gulp-load-plugins') // 自动加载全部的插件,避免require操作过多

const plugins = loadPlugins()
const bs = browserSync.create() // browser-sync提供了一个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()
}
// 删除dist下的文件
const clean = () => {
  return del(['dist', 'temp'])
}

const style = () => {
  return src('src/assets/styles/*.scss', { base: 'src' }) // { base: 'src' } 按照以src为基准扩展目录
  //sass模块编译的时候会自动认为下划线开头的文件都是主文件依赖的文件,所以会被忽略掉,只有没有下划线开头的文件会被转义 // outputStyle: 'expanded'样式结尾的‘}’展开
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true })) // stream: true以流的方式去往浏览器推
}

const script = () => {
  return src('src/assets/scripts/*.js', { base: 'src' })
    .pipe(plugins.babel({ presets: ['@babel/preset-env'] })) // babel()只是帮助唤醒@babel/core这个转换过程,不会像gulp-sass自动安装node-sass转换模块,需要手动安装@babel/core,@babel/preset
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}
// 使用swig模板插件
const page = () => {
  return src('src/*.html', { base: 'src' })
  // 这里可能会因为swig模板引擎缓存的机制导致页面不会变化,此时需要额外将swig选项中的cache设置为false
    .pipe(plugins.swig({ data, defaults: { cache: false } })) // 防止模板缓存导致页面不能及时更新
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}
// 使用sgulp-imagemin插件处理图片压缩
const image = () => {
  return src('src/assets/images/**', { base: 'src' })
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}
// 处理字体文件中的svg
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 serve = () => {
  watch('src/assets/styles/*.scss', style) // watch自动监听一个文件路径的通配符,根据文件的变化决定是否要执行某一个任务
  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的reload任务

  bs.init({
    notify: false, // 是否显示提示browser-sync的链接状态(默认true)
    port: 2080, // 设置默认端口,(默认3000)
    // open: false, // 是否自动打开浏览器(默认true// files: 'dist/**', // browser-sync监听的文件,可以用bs.reload代替
    server: {
      baseDir: ['temp', 'src', 'public'], // 这里会先去寻找temp下的文件,找不到再去src,public下面去寻找,目的是减少开发过程中的一些不必要的构建
      routes: { // 优先于baseDir配置
        '/node_modules': 'node_modules' // 对于一些特殊的引用文件自动映射对应的文件目录下
      }
    }
  })
}

const useref = () => { // useref 插件会自动处理html中的构建注释。会自动将开始标签和结束标签中间的文件打包到一个文件当中
  return src('temp/*.html', { base: 'temp' }) // 读写会有冲突,所以需要有一个临时文件
    .pipe(plugins.useref({ searchPath: ['temp', '.'] }))
    // html js css
    .pipe(plugins.if(/\.js$/, plugins.uglify())) // 压缩js
    .pipe(plugins.if(/\.css$/, plugins.cleanCss())) // 压缩css
    .pipe(plugins.if(/\.html$/, plugins.htmlmin({ // 压缩html
      collapseWhitespace: true, // 折叠掉所有的空白字符
      minifyCSS: true, // 压缩文件中的css
      minifyJS: true // 压缩文件中的js
    })))
    .pipe(dest('dist'))
}

const compile = parallel(style, script, page)

// 上线之前执行的任务
const build =  series(
  clean, // 先删除dist下的文件,再进行构建
  parallel(
    series(compile, useref),
    image,
    font,
    extra
  )
)
// 开发阶段执行的任务
const develop = series(compile, serve)

module.exports = {
  clean,
  build,
  develop
}

封装自动化构建工作流
  1. 创建一个脚手架工具
  2. 将之前创建的自动化构建工作流提取到脚手架工具项目当中,在脚手架项目当中封装好Gulp工作流
  3. 解决模块中的问题,在项目根目录下创建一个配置文件,然后在模块当时读取项目配置文件,例如:vue-cli工作时会读取vue.config.js
  4. 抽象路径配置,把项目当中写死的文件地址提取出来放到配置文件里面去配置
  5. 包装Gulp cli,将项目中的gulpfile文件删除,然后以命令的形式运行
  6. 发布并使用模块

Fis

相比于Gulp、Grunt,Fls的特点是高度集成,他把前端开发中常见的构建任务和调试任务都集成在内部,开发者可以通过简单的配置文件的方式去配置构建过程中需要完成的工作,也就是说Fls不需要想Gulp、Grunt当中一样去定义一些任务,Fls当中有些内置的任务,这些内置的任务会根据开发者的配置自动完成,除此之外Fls当中还配置了一款用于调试的webserver,可以方便的调试构建结果,像这一系列的东西,在Gulp和Grunt中都是需要自己去实现的。

基本使用
  1. 全局安装 npm i fls3 -g
  2. fis3 release release是fis3中默认的构建任务,这个构建任务会将项目中所有需要被构建的文件构建到一个临时文件夹当中,如果需要指定输出的文件,可以通过fis3 release -d 文件名 整个过程默认只会讲代码当中对资源文件的引用相对路径自动转换为绝对路径,从而实现资源的定位,通过fis的资源定位的能力,可以提高代码的可移植性。
  3. 在项目根目录下添加fis.config.js,通过fis.match方法去为项目构建过程当中匹配到的一些文件添加一些指定的配置
  4. fis inspect可以看到需要转换的文件
fis.match('*.{js,scss,png}', {
  release: '/assets/$0' // 将release过后的结果放在assts下,$0指的是当前文件的原始目录结构
})

fis.match('**/*.scss', {
  rExt: '.css', // 修改扩展名
  parser: fis.plugin('node-sass'), // parser额外的插件,需要安装npm i fis-parser-node-sass
  optimizer: fis.plugin('clean-css') // 压缩
})

fis.match('**/*.js', {
  parser: fis.plugin('babel-6.x'),
  optimizer: fis.plugin('uglify-js')
})

以上就是关于自动化构建的笔记啦~还需要多练习才可以真的掌握!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值