前端自动化构建-Grunt、Gulp、FIS

3 篇文章 0 订阅
1 篇文章 0 订阅

1. 自动化构建简介

简介

  • 就是将我们的源代码自动化的去转换成生产代码。
    • 这个转换的过程我们称之为自动化构建流
    • 脱离运行环境兼容带来的问题
    • 在开发环境使用提高效率的语法、规范和标准
      • 比如:
      • ESMAScript Next
      • Sass
      • 模板引擎 (这些用法大都不被浏览器直接支持)
    • 使用构建工具转换那些不被支持的『特性』

举例说明

  • sass.scss --> css
# 下载sass
npm i sass
# 执行
./node_modules/.bin/sass sass/main.scss css/style.css

# 可以使用 NPM Scripts
"scripts": {
    "build": "sass sass/main.scss css/style.css"
}
  • 使用 NPM Scripts是实现自动化构建工作流的最简方式
// browser-sync 用于启动一个测试服务器来运行我们的项目
scrips: {
    "preserve": "npm run build"// 这个命令是在 serve 之前是会自动执行 类似pre hook
    "serve": "browser-sync ."
}

// --watch

// 想执行多个命令可以使用 `npm-run-all` 模块
// 有了这个模块之后
scripts: {
    "start": "run-p build", "serve" // 同时执行 build  和 serve
}

// 在serve的命令下可以这样
scrips: {
    "serve": "browser-sync . --files \"css/*.css\"" // 加了这个之后会自动把改变后的css同步到浏览器
}

常用的自动化构建工具

  • Grunt
  • Gulp
  • FIS

注意: Webpack 属于模块打包工具

2. Grunt 的使用

1. Grunt的基本使用

  • npm init
  • npm i grunt
  • 增加 gruntfile.js (grunt的入口文件)
// Grunt 的入口文件
// 用户定义一些需要 Grunt 自动执行的任务
// 需要导出一个函数
// 此函数接收一个 grunt 的形参,内部提供一些创建任务时可以用到的API

module.exports = grunt => {
    grunt.registerTask('foo', () => {
        console.log('hello grunt!')
    })

    grunt.registerTask('bar', '任务描述', () => {
        console.log('other task!')
    })

    // yarn grunt 会默认执行 default 任务
    // grunt.registerTask('default', () => {
    //     console.log('default task')
    // })

    // 因此 可以这样
    grunt.registerTask('default', ['foo', 'bar']);

    // 异步操作
    grunt.registerTask('async-task',function () {
        const done = this.async()
        setTimeout(() => {
            console.log('async task workding~');
            done();
        }, 1000);
    })
}

1.1 Grunt标记任务失败

module.exports = grunt => {
  grunt.registerTask('bad', () => {
    console.log('bad workding')
    return false
  })

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

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

  // 1. 使用 yarn grunt default 当bad结束后 失败,则 bar不会执行
  // 2. 使用 yarn grunt default --force
  grunt.registerTask('default', ['foo', 'bad', 'bar'])


  // 异步的
  grunt.registerTask('bad-async', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('bad async')
      done(false)
    }, 1000)
  })

}

2. Grunt 的配置方法

module.exports = grunt => {
  grunt.initConfig({
    // foo: 'bar'
    foo: {
      bar: 123
    }
  })

  grunt.registerTask('foo', () => {
    // console.log(grunt.config('foo'))
    console.log(grunt.config('foo.bar'))
  })
}

3. Grunt 多目标任务

module.exports = grunt => {
  grunt.initConfig({
    build: {
      // 配置的key都会成为 下面 this.target 值成为this.data 注意,除了 options
      options: { // 作为任务的配置选项出现
        foo: 'bar'
      },
      // css: '1', // 也可以是对象
      css: {
        options: { // 这里会将替换build的options里面的配置
          foo: 'bar-css'
        }
      },
      js: '2'
    }
  })

  // 多目标模式, 可以让任务根据配置形成多个子任务
  grunt.registerMultiTask('build', function() {
    console.log(`build task -- target:${this.target}, data: ${this.data}`)
  })
}

4. Grunt 插件的使用

  • 举例 grun-contrib-clean 自动清除我们在项目开发过程中自动生成的临时文件
  • 使用 loadNpmTasks 加载插件
  • 执行 yarn grunt clean
module.exports = grunt => {
  grunt.initConfig({
    clean: {
      // temp: 'temp/app.js', // temp目录下的app.js
      // 也可以使用通配符
      temp: 'temp/*.txt'
    }
  })
  grunt.loadNpmTasks('grunt-contrib-clean')
}
// 使用 yarn grunt clean 将会把temp/app.js删除掉

4.1 Grunt 常用插件

  • grunt-sass sass
  • grunt-babel @babel/core @babel/preset-env
  • grunt-contrib-watch 监控文件更改
const sass = require('sass')
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
  grunt.initConfig({
    sass: {
      // 需要添加otions
      options: {
        sourceMap: true,
        // 没有这个可以根据报错信息的提示添加
        implementation: sass
      },
      main: {
        files: {
          'dist/css/main.css': 'src/scss/main.scss'
        }
      }
    },
    babel: {
      options: {
        presets: ['@babel/preset-env']
      },
      main: {
        files: {
          'dist/js/app.js': 'src/js/app.js'
        }
      }
    },
    watch: {
      js: {
        files: ['src/js/*.js'],
        tasks: ['babel'] // 监控到修改的时候需要执行的任务
      },
      css: {
        files: ['src/scss/*.scss'],
        tasks: ['sass']
      }
    }
  })
  // grunt.loadNpmTasks('grunt-sass')
  // 加载的任务过多,可以通过社区的 load-grunt-tasks 模块来处理
  loadGruntTasks(grunt)

  // yarn grunt watch 执行后并不会立马执行 watch下的任务
  // 因此做个映射 
  grunt.registerTask('default', ['sass', 'babel', 'watch'])
}

3. Gulp

1. Gulp的基本使用

  • The streaming build system 基于流的构建系统

核心特点: 高效、易用

// gulp 的入口让你说的

//$ yarn gulp foo
exports.foo = done => {
  console.log('this is foo task working~');
  done() // 标识任务完成
}

//$ yarn gulp
exports.default = done => {
  console.log('default task working~');
  done()
}
// 在4.0以前,在注册gulp任务时是需要gulp模块里面的方法去实现的。
// 具体来看 如下,但是不推荐使用了
const gulp = require('gulp')
gulp.task('bar', done => {
  console.log('bar working ~');
  done();
})

2. Gulp 的组合任务

  • series 创建串行任务
  • parallel 创建并行任务
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)
}
exports.foo = series(task1, task2, task3); // 创建串行任务
exports.bar = parallel(task1, task2, task3); // 创建并行任务

3. Gulp 的异步任务(三种方式)

  • 回调函数
  • Promise
  • async await
// 1. 回调函数
exports.callback = done => {
  console.log('callback task~')
  done()
}
// 以node的回调函数是一种标准,都是叫做错误优先的回调函数
exports.callback_error = done => {
  console.log('callback_error task~')
  done(new Error('task failed:')) // 可以阻绝后续工作执行
}
// 2. Gulp 支持 Promise
exports.promise = () => {
  console.log('promise task~')
  return Promise.resolve() // resolve不需要带参数,因为Gulp会忽略掉这个值
}
exports.promise_error = () => {
  console.log('promise_error task~')
  return Promise.reject(new Error('task failed:')) // resolve不需要带参数,因为Gulp会忽略掉这个值
}
// 3. node环境8以上,主要是支持 async await
const timeout = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  })
}
exports.async = async () => {
  await timeout(1000)
  console.log('async task~');
}

// 除了以上3种,gulp里面有一些类似的
const fs = require('fs')
// 文件流
exports.stream = done => {
  const readStream = fs.createReadStream('package.json')
  const writeStream = fs.createWriteStream('temp.txt')
  readStream.pipe(writeStream)
  // return readStream //如同以下
  readStream.on('end', () => {
    done()
  })
}

4. Gulp 构建过程核心工作原理

  • 输入–读取流
  • 加工–转换流
  • 输出–写入流
    cPU8Gd.png

5. Gulp 文件操作 API

const { src, dest } = require('gulp')
const cleanCss = require('gulp-clean-css')
const rename = require('gulp-rename')
exports.default = () => {
  // return src('src/normalize.css').pipe(dest('dist'))
  return src('src/*.css')
  .pipe(cleanCss())  // 可以继续加 比如 gulp-rename
  .pipe(rename({extname: '.min.css'}))
  .pipe(dest('dist'))
}
// gulp-clean-css // 压缩css的转换流

4. Gulp 案例

1. 样式、脚本、页面模板编译

1.1 样式编译

参考代码 https://github.com/zce/zce-gulp-demo

const { src, dest } = require('gulp')
const sass = require('gulp-sass')

// 1. 样式编译
const style = () => {
  // , options.base 是基准路径
  return src('src/assets/styles/*.scss', { base: 'src' })
    //sass 可以传参数
    // .pipe(sass()) // sass 是转换流的插件
    .pipe(sass({ outputStyle: 'expanded' }))
    .pipe(dest('dist'))
}

module.exports = {
  style
}

1.2. 脚本编译

const { src, dest } = require('gulp')
const babel = require('gulp-babel')

// 2. 脚本编译 gulp-babel @babel/core @babel/preset-env
const script = () => {
  return src('src/assets/scripts/*.js', { base: 'src' })
    .pipe(babel({ presets: ['@babel/preset-env'] }))
    .pipe(dest('dist'))
}

module.exports = {
  script
}

1.3. 页面模板编译

const { src, dest } = require('gulp')
const swig = require('gulp-swig')
// 3. 页面模板编译 使用了 swig 模板引擎
const data = {} // 按照模板内的结构填写好,这里只用名称代替了
const page = () => {
  return src('src/*.html', { base: 'src' })
    .pipe(swig({ data: data }))
    .pipe(dest('dist'))
}
module.exports = {
  page
}

1.4 将这三者组合起来

使用 parallel 创建并行任务

const { src, dest, parallel } = require('gulp')
const sass = require('gulp-sass')
const babel = require('gulp-babel')
const swig = require('gulp-swig')

// 1. 样式编译
const style = () => {
  // , options.base 是基准路径
  return src('src/assets/styles/*.scss', { base: 'src' })
    //sass 可以传参数
    // .pipe(sass()) // sass 是转换流的插件
    .pipe(sass({ outputStyle: 'expanded' }))
    .pipe(dest('dist'))
}

// 2. 脚本编译 gulp-babel @babel/core @babel/preset-env
const script = () => {
  return src('src/assets/scripts/*.js', { base: 'src' })
    .pipe(babel({ presets: ['@babel/preset-env'] }))
    .pipe(dest('dist'))
}

// 3. 页面模板编译 使用了 swig 模板引擎
const data = {} // 按照模板内的结构填写好,这里只用名称代替了
const page = () => {
  return src('src/*.html', { base: 'src' })
    .pipe(swig({ data: data }))
    .pipe(dest('dist'))
}

// 将这3个任务组合起来
const compile = parallel(style, script, page)

module.exports = {
  compile
}

2. 图片和字体文件转换

// 4. 图片转换 使用 gulp-imagemin
const image = () => {
  return src('src/assets/images/**', { base: 'src' })
    .pipe(imagemin())
    .pipe(dest('dist'))
}
// 5. 字体处理 svg的也是图片
const font = () => {
  return src('src/assets/fonts/**', { base: 'src' })
    .pipe(imagemin())
    .pipe(dest('dist'))
}

3. 其它文件及文件清除

  • 清除安装 del
  • yarn add del -D
const { src, dest, parallel, series } = require('gulp')

const del = require('del')
const clean = () => {
  return del(['dist'])
}
// 修改一个build任务

// 将这3个任务组合起来
const compile = parallel(style, script, page, image, font)

const build = series(clean, parallel(compile, extra))


4. 自动加载插件

  • gulp-load-plugins

const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()  // 使用这个 gulp-sass 如果是 gulp-sass-dev 会成为 plugins.sassDev

// 任务当中使用时为举例如下
// 1. 样式编译
const style = () => {
  // , options.base 是基准路径
  return src('src/assets/styles/*.scss', { base: 'src' })
    //sass 可以传参数
    // .pipe(sass()) // sass 是转换流的插件
    // 这是换成 plugins.sass
    // 注意 当插件命名为 gulp-sass-xxx 则为 plugins.sassXxx
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('dist'))
}

5. 开发服务器

  • yarn add browser-sync -D
const browserSync = require('browser-sync')
const bs = browserSync.create()

// 7. 创建serve 任务
const serve = () => {
  bs.init({
    notify: false, // 这个是浏览器打开后右上角有个 连接成功 的提示消息 ,这里设置为取消
    port: 2080, // 端口
    // open: false, // 是否默认打开浏览器
    files: 'dist/**',  // 监听dist下面的文件,发生修改后自动同步到浏览器上
    server: {
      baseDir: 'dist',
      routes: {
        '/node_modules': 'node_modules' // dist里面的html有直接引用node_modules里面的bootstrap/dist/css/bootstrap.css 所以这里加个映射 
      }
    }
  })
}

6. 监视变化以及构建优化

  • 使用 gulp 中的 watch
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) // 对于这三种文件, 在开发阶段不需要编译,只需要重新加载一下
  
 // 需要注意的地方,当上面那三种文件没有编译到dist时,需要把bs.init中的server.baseDir更换成数组 ["dist", "src", "public"]
 // baseDir 是一个数组,则会先在dist下找,找不到再往src下找,依次类推

  • 注意在bs.init({files: ‘dist/**’})

这个files可以去掉,不监听,然后在任务里面通过 .pipe(bs.reload({ stream: true })) 来使用。 stream: true 表示以流的方式

// 1. 样式编译
const style = () => {
  // , options.base 是基准路径
  return src('src/assets/styles/*.scss', { base: 'src' })
    //sass 可以传参数
    // .pipe(sass()) // sass 是转换流的插件
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('dist'))
    .pipe(bs.reload({ stream: true }))
}

7. useref 文件引用处理

在html当中有引用注释

  • 因为之前增加了node_modules的映射 使用了node_modules里面的css文件,所以打包出来的dist的html文件当中有引用node_modules的路径

  • 使用 useref

  <!-- 这是在打包好dist当中的,因为需要处理 -->
  <!-- build:css assets/styles/vendor.css -->
  <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css">
  <!-- endbuild -->
  <!-- build:css assets/styles/main.css -->
  <link rel="stylesheet" href="assets/styles/main.css">
  <!-- endbuild -->
// 创建 useref 任务
const useref = () => {
  return src('dist/*.html', { base: 'dist' })
    .pipe(plugins.useref( { searchPath: ['dist', '.'] } ))
    .pipe(dest('dist'))
}

8. 文件压缩

  • gulp-htmlmin
  • gulp-uglif
  • gulp-clean-css
  • gulp-if
const useref = () => {
  return src('dist/*.html', { base: 'dist' })
    .pipe(plugins.useref( { searchPath: ['dist', '.'] } ))
    // 在这里会有三种文件类型 html js css 要压缩 需要下载3个压缩插件
    .pipe(plugins.if(/\.js$/, plugins.uglify()))
    .pipe(plugins.if(/\.css$/, plugins.cleanCss())) // 
    .pipe(plugins.if(/\.html$/, plugins.htmlmin( 
      {
        collapseWhitespace: true, // 空白字符
        minifyCSS: true,  // 压缩 css
        minifyJS: true  // 压缩 scripts
      }
    )))
    .pipe(dest('temp')) // 这里出现了 dist包 和 temp 包 后续会统一调整
}

9. 重新规划构建过程

  • 生成包为 dist 临时包叫 temp ,需要将代码中的调整一下
// 举例

// 1. 样式编译
const style = () => {
  // , options.base 是基准路径
  return src('src/assets/styles/*.scss', { base: 'src' })
    //sass 可以传参数
    // .pipe(sass()) // sass 是转换流的插件
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('temp')) // 这里修改
    .pipe(bs.reload({stream: true}))
}

// 2. 脚本编译 gulp-babel @babel/core @babel/preset-env
const script = () => {
  return src('src/assets/scripts/*.js', { base: 'src' })
    .pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
    .pipe(dest('temp')) // 这里修改
    .pipe(bs.reload({stream: true}))
}
// 依次类推

10. 将构建任务提为模块

  • 将任务提为 package.json 中的 scripts
{
    "scripts": {
        "clean": "gulp clean",
        "build": "gulp build",
        "develop": "gulp develop"
    }
}
// 将任务提取出来只暴露常用的
module.exports = {
  clean,
  build,
  develop
}

11. 封装工作流

  • Gulpfile + Gulp = 构建工作流
  • Gulpfile + Gulp CLI = 工作流模块 (eg: zce-pages)
  • 约定专一配置文件 类似 vue.config.js 这里我们可以来一个 pages.config.js
  • 将封闭出来的工作流在本地调试时使用 npm link 放到全局上去
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值