说一说自动化构建以及Gulp

简介: 自动化构建是前端工程化当中的一个重要组成部分,自动化就是使用机器来代替人工来完成一些工作,构建我们可以将其理解为转换。总的来说就是将我们的源代码自动转换为生产环境当中可以运行的代码或程序。一般来说我们
Hi~,我是 一碗周,一个在舒适区垂死挣扎的前端,如果写的文章有幸可以得到你的青睐,万分有幸~

写在前面

自动化构建是前端工程化当中的一个重要组成部分,自动化就是使用机器来代替人工来完成一些工作,构建我们可以将其理解为转换。总的来说就是将我们的源代码自动转换为生产环境当中可以运行的代码或程序。一般来说我们将这个转换的过程称之为自动化转换工作流。

它的作用就是让我们尽可能的脱离运行环境的种种问题去在开发阶段去是先用使用一些提高效率的语法、标准和规范。最典型的一个例子就是我们开发Web网站时经常使用ECMAScript的最新标准、Sass等来提高编码效率和质量,但是这些不被支持的特性通过自动化构建来转换为支持的特性。

常用的自动化构建工具

常用的自动化构架工具有如下几个

由于Grunt几乎已经退出历史舞台了,而FIS也不再维护了,这里就不做介绍了,我们重点介绍一下Gulp。

Gulp

image.png

Gulp的特点就是简单、高效、具有一个完整的生态。

Gulp与Webpack的区别

从本质上来说两者不应放在一起去比较,Gulp是一个自动化构建工具,强调的是前端开发的流程,通过配置一系列的task,去构建前端项目,可以将Gulp看做一个task runner,即任务调度器

Webpack是一个前端模块化方案,它的侧重是模块打包,把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loaderplugins对资源进行处理,打包成符合生产环境的前端资源。

  • Gulp是构建工具,可以配合各种插件做css压缩等,解放了双手,实现了自动化。
  • Gulp严格上讲,它旨在规范前端开发流程,不包括模块化功能。
  • WebPack是文件打包工具,可把各个项目的css压缩文件等打包合并成一个或多个文件,主要就是应用于模块化操作。
  • WebPack更是明显强调模块化开发,而那些文件压缩合并、预处理等功能,只是他附带的功能。
  • WebPack整合了Gulp的优点,当我们想要一步一步来配置自己的环境时,那么gulp就可以满足我们的需要,但是如果我们想一下就配备大部分我们所需要的环境,那么此时可以选用WebPack,前提是写好package.json。
  • gulp与webpack上是互补的,还是可替换的,取决于你项目的需求,它们可不存在冲突的关系哈。
  • Gulp与WebPack可以组合起来使用,以便快速编译(依靠Gulp丰富的组件可以让JS与HTML实现联动,从而控制WebPack应用程序,达到高自动化)

Gulp的使用步骤

  1. 项目中安装一个Gulp的开发依赖
  2. 在根目录下添加一个gulpfile.js的入口文件,该文件用于编写一些Gulp自动构建的一些任务
  3. 在命令行中通过cli去运行这些任务

Gulp的基本使用

首先安装Gulp,命令如下:

yarn add gulp --dev

然后创建一个gulpfile.js的Gulp的入口文件,在该文件中创建具体任务。值得注意的是在最新的Gulp中,取消了同步代码模式,约定每一个任务都是异步任务,当任务完成过后要标记任务完成,否则会抛出异常。

gulpfile.js

// Gulp的入口文件

// 参数 done 表示运行结束需要调用的函数,
exports.foo = done => {
  console.log('foo task working~')
  // 表示运行结束
  done()
}

导出的函数名foo即任务的名称

命令行运行如下命令

yarn gulp foo

最终的运行结果如下:

Using gulpfile E:\Repository\...\gulpfile.js
Starting 'foo'...
foo task working~
Finished 'foo' after 1.74 ms
这里我省略了部分路径

默认任务

我们可以通过default来定义默认任务,该任务无需指定任务名称即可使用,示例代码如下:

gulpfile.js

// Gulp的入口文件

// 参数 done 表示运行结束需要调用的函数,
exports.default = done => {
  console.log('default task working~')
  // 表示运行结束
  done()
}

shell

yarn gulp

最终的输出结果与上面类似。

Gulp的组合任务

Gulp模块的seriesparallel的API可以创建组合任务,这两个的区别就是前者属于串行任务,后者属于并行任务。

gulpfile.js

// Gulp的入口文件
const { series, parallel } = require('gulp')

const task1 = done => {
  setTimeout(() => {
    console.log('task1 working~')
  }, 1000)
  done()
}
const task2 = done => {
  setTimeout(() => {
    console.log('task2 working~')
  }, 1000)
  done()
}
const task3 = done => {
  setTimeout(() => {
    console.log('task3 working~')
  }, 1000)
  done()
}

// 创建串行任务 依次执行三个任务
exports.series = series(task1, task2, task3)
// 创建并行任务 三个任务同时执行
exports.parallel = parallel(task1, task2, task3)

当我们执行串行任务,命令如下

shell

yarn gulp series

最终结果如下:

Using gulpfile E:\Repository\...\gulpfile.js
Starting 'series'...
Starting 'task1'...
task1 working~
Finished 'task1' after 1.01 s
Starting 'task2'...
task2 working~
Finished 'task2' after 1.01 s
Starting 'task3'...
task3 working~
Finished 'task3' after 1.01 s
Finished 'series' after 3.04 s

执行并行任务,命令如下

shell

yarn gulp parallel

最终结果如下:

Using gulpfile E:\Repository\...\gulpfile.js
Starting 'parallel'...
Starting 'task1'...
Starting 'task2'...
Starting 'task3'...
task1 working~
Finished 'task1' after 1.01 s
task2 working~
Finished 'task2' after 1.01 s
task3 working~
Finished 'task3' after 1.01 s
Finished 'parallel' after 1.01 s

用途

  • 例如部署项目,先需要执行编译任务,就需要串行任务。
  • 例如cssjs的编译和压缩,彼此互不干扰,就可以使用并行任务。

异步任务

Gulp中解决异步任务通常有如下几种方式:

  • 普通回调函数
  • promise
  • async/await
  • stream

首先我们来看一下普通回调函数的方式,该方式是错误优先的,如果我们需要返回一种错误的回调,需要在done中作物参数传递,示例代码如下:

// Gulp的入口文件
exports.error = done => {
  console.log('error task~')
  done(new Error('task failed~'))
}

现在执行yarn gulp error,打印完error task~后即可看到错误信息。

现在我们来看一下Promise的方式完成回调,示例代码如下:

// Gulp的入口文件
exports.default = () => {
  console.log('promise task~')
  if (true) {
    // 这里通过promise的resolve方法返回一个成功的promise
    // 一旦resolve了,那么任务就结束了
    // resolve里面不需要传参数,因为gulp会忽略这个值
    return Promise.resolve()
  } else {
    // 通过reject去触发一个异常
    return Promise.reject(new Error('promise failed~'))
  }
}

然后就是async/await的方式,这种方式必须node版本在8以上才可以,示例代码如下:

// Gulp的入口文件
const delay = time => {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, time)
  })
}
exports.default = async () => {
  await delay(1000)
  console.log('async task~')
}

最后我们来看一下Gulp提供的stream的方式,使用这种方式就是在任务函数中返回一个stream对象,示例代码如下:

// Gulp的入口文件
const fs = require('fs')
exports.stream = () => {
  // 读取文件的文件流对象
  const readStream = fs.createReadStream('package.json')
  // 写入文件的文件流对象
  const writeStream = fs.createWriteStream('temp.txt')
  // 文件复制从读入通过管道倒入到写入里面
  readStream.pipe(writeStream)
  // 把readStream返回
  return readStream
}

整个任务完成的时机就是stream对象end的时候。因为stream对象都有一个end事件,文件流读取完成过后,end事件就会执行。gulp就会知道任务已经完成了。

上面的写法类似于下面这种写法

// Gulp的入口文件
const fs = require('fs')
exports.stream = done => {
  const readStream = fs.createReadStream('package.json')
  const writeStream = fs.createWriteStream('temp.txt')
  readStream.pipe(writeStream)
  // 监听了end事件执行done
  readStream.on('end', () => {
    done()
  })
}

Gulp构建过程核心工作原理

Gulp是一个基于流的构建系统,其工作原理就是将文件读取出来,做完操作之后写入另一个文件中。流程就是输入/读取流→加工/转换流→输出/写入流

如下代码展示了Gulp的构建流程

// Gulp的入口文件
const fs = require('fs')
const { Transform } = require('stream')

exports.default = () => {
  // 文件读取流
  const read = fs.createReadStream('style.css')
  // 文件写入流
  const write = fs.createWriteStream('style.min.css')
  // 文件转换流
  const transform = new Transform({
    transform: (chunk, encoding, callback) => {
      // ★★★核心转换过程实现★★★
      // chunk => 读取流中读取到的内容(Buffer)
      // 使用toString将Buffer数组转化成字符串
      const input = chunk.toString()
      // 替换掉空白字符和css注释
      // 将转换后的结果放在output变量中
      const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '')
      // 执行callback的时候返回,错误优先第一个传错误参数,没有的话传null
      // output是成功之后作为结果导出
      callback(null, output)
    },
  })

  // 文件复制从读入通过管道先转换,后倒入到写入里面
  read.pipe(transform).pipe(write)

  return read
}

Gulp读取流和写入流的API

Gulp中专门提供了读取流和写入流的API,比原生Node.js提供的API更加的强大、简单且易用,转换流的过程通常都是通过独立的插件来实现。

这里我们先举一个简单的例子:

首先我们安装一个压缩CSS的插件gulp-clean-css和修改文件类型的插件gulp-rename,安装命令如下:

yarn add gulp-clean-css gulp-rename --dev

然后在gulpfile.js中写入如下代码

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

exports.default = done => {
  // 读取流,参数是文件路径,比较强大的地方是这里可以使用通配符匹配多个文件
  src('*.css')
    // 压缩代码
    .pipe(cleanCss())
    // 修改文件类型
    .pipe(rename({ extname: '.min.css' }))
    // 输出流,参数是输出路径
    .pipe(dest('dist'))
  done()
}

Gulp案例

现在我们对Gulp已经有所了解,现在我们就通过Gulp来实现一个自动构建的案例,主要实现如下功能

  • 所有的scss、less文件编译成css(scss中下划线开头的不会被编译)
  • 将所有的js文件中的ES6语法进行适配
  • html中的模板要进行编译
  • 图片和文字要进行压缩
  • 公共文件原封不动输出
  • 在输出文件之前要对之前的输出目录进行删除
  • 可以实现开发时浏览器热更新
  • 第三方文件引用处理
  • 对html/css/js文件进行压缩
  • 导出内容发布到dist文件夹

样式编译

编译sass需要的插件是gulp-cass,编译less的插件是gulp-less,现在我们来安装一下这两个插件

yarn add sass gulp-sass gulp-less --dev

安装完成之后,在gulpfile.js中编写如下代码:

const { src, dest, parallel } = require('gulp')
// 引入sass模块
const sass = require('gulp-sass')(require('sass'))
// 引入less模块
const less = require('gulp-less')

// 创建style任务
const sassStyle = () => {
  // 输入scss的文件路径,并且指定根目录是src,在dist输出目录中,以src目录的结构输出
  return (
    // 匹配 src/assets/styles 下所有的 sass 文件
    src('src/assets/styles/*.scss', { base: 'src' })
      // 进行sass向css转换,并且指定的样式是完全展开
      // (如果不设置完全展开,那么默认css样式的右花括号不折行)
      .pipe(sass({ outputStyle: 'expanded' }))
      // 输出到dist文件夹
      .pipe(dest('dist'))
  )
}

const lessStyle = () => {
  return (
    src('src/assets/styles/*.less', { base: 'src' })
      // 进行less向css转换
      .pipe(less())
      // 输出到dist文件夹
      .pipe(dest('dist'))
  )
}

// 创建并行任务
const style = parallel(sassStyle, lessStyle)

module.exports = {
  style,
}

然后在命令行中执行yarn gulp style即可将样式编译完毕

脚本编译

我们通过gulp-babel插件对JavaScript代码进行编译,将ES6+的语法转换为ES5,首先安装gulp-babel以及相关的转换插件,安装代码如下:

yarn add gulp-babel @babel/core @babel/preset-env --dev

对应的gulpfile.js的代码如下:

const { src, dest, parallel } = require('gulp')
// 引入sass模块
const sass = require('gulp-sass')(require('sass'))
// 引入less模块
const less = require('gulp-less')
// 引入babel
const babel = require('gulp-babel')

// 创建style任务
const sassStyle = () => {
  // 输入scss的文件路径,并且指定根目录是src,在dist输出目录中,以src目录的结构输出
  return (
    // 匹配 src/assets/styles 下所有的 sass 文件
    src('src/assets/styles/*.scss', { base: 'src' })
      // 进行sass向css转换,并且指定的样式是完全展开
      // (如果不设置完全展开,那么默认css样式的右花括号不折行)
      .pipe(sass({ outputStyle: 'expanded' }))
      // 输出到dist文件夹
      .pipe(dest('dist'))
  )
}

const lessStyle = () => {
  return (
    src('src/assets/styles/*.less', { base: 'src' })
      // 进行less向css转换
      .pipe(less())
      // 输出到dist文件夹
      .pipe(dest('dist'))
  )
}
// 创建babel任务
const script = () => {
  return (
    src('src/assets/scripts/*.js', { base: 'src' })
      // 使用babel转换ES6语法
      .pipe(
        babel({
          // 插件集合,最新特性的全部打包,不写这个转换没有效果
          presets: ['@babel/preset-env'],
        }),
      )
      // 输出到dist文件夹
      .pipe(dest('dist'))
  )
}

// 创建并行任务
const style = parallel(sassStyle, lessStyle)

module.exports = {
  style,
  script,
}

现在我们就可以执行script来编译脚本文件了。

页面模板编译

这里使用的插件是gulp-swig,示例代码如下:

const { src, dest} = require('gulp')
// 引入swig模块
const swig = require('gulp-swig')
const data = {
  /* 模板中使用的数据 */
}

// 创建模板引擎任务
const page = () => {
  // 通配符匹配src下main的所有子目录中的html
  return src('src/**/*.html', { base: 'src' })
    .pipe(swig({ data }))
    .pipe(dest('dist'))
}

module.exports = {
  page,
}

图片压缩

图片压缩使用的是gulp-imagemin这个插件,示例代码如下:

const { src, dest, parallel } = require('gulp')
// 引入imagemin模块
const imagemin = require('gulp-imagemin')

// 图片压缩任务
const image = () => {
  // 匹配images下面的所有文件
  return src('src/assets/images/**', { base: 'src' })
    .pipe(imagemin())
    .pipe(dest('dist'))
}

// 图片压缩任务
const font = () => {
  // 匹配images下面的所有文件
  return src('src/assets/fonts/**', { base: 'src' })
    .pipe(imagemin())
    .pipe(dest('dist'))
}
module.exports = {
  image,
  font,
}

值得注意的是这里的gulp-imagemin安装的是7.10的版本

其他源文件输出

项目中都有public目录,这些存放一些静态的公共目录,直接输出到dist文件即可。

// 将public的文件进行额外输出
const extra = () => {
  return src('public/**', { base: 'public' }).pipe(dest('dist'))
}

清除文件

在编译输出文件之前,需要把之前的目录删掉,使用del模块进行操作。这个不是gulp插件,但是因为其返回一个promise对象,所以在gulp中也可以使用。

首先我们先安装del,命令如下:

yarn add del --dev

然后在gulpfile.js中写入如下内容:

const del = require('del')
// 删除 dist 目录
const clean = () => {
  return del(['dist'])
}

module.exports = {
  clean,
}

然后执行对应命令,即可删除对应文件夹。

自动加载插件

上面引用了那么多的模块,我们可以安装一个自动加载插件的模块,这样可以减少很多代码的书写。

首先我们安装gulp-load-plugins,命令如下:

yarn add gulp-load-plugins --dev

然后在gulpfile中将所有带gulp-的前缀的引用直接删除,然后我们引入gulp-load-plugins,并调用该插件,然后通过.插件名的方式访问,示例代码如下:

const { src, dest, parallel } = require('gulp')
// 引入 gulp-load-plugins 后直接调用
const plugins = require('gulp-load-plugins')()
// 非 gulp 插件需要单独引入
const del = require('del')

const sassStyle = () => {
  return (
    src('src/assets/styles/*.scss', { base: 'src' })
      .pipe(plugins.sass(require('sass'))({ outputStyle: 'expanded' }))
      .pipe(dest('dist'))
  )
}

const lessStyle = () => {
  return (
    src('src/assets/styles/*.less', { base: 'src' })
      .pipe(plugins.less())
      .pipe(dest('dist'))
  )
}
// 创建babel任务
const script = () => {
  return (
    src('src/assets/scripts/*.js', { base: 'src' })
      .pipe(
        plugins.babel({presets: ['@babel/preset-env']}),
      )
      .pipe(dest('dist'))
  )
}

.......

热更新开发服务器browser-sync

现在我们来配置一个热更新服务器,首先安装browser-sync模块,命令如下:

yarn add browser-sync --dev

然后我们在gulpfile.js中写入

// 引入热更新模块
const browserSync = require('browser-sync')// 创建一个开发服务器
const bs = browserSync.create()
// 创建服务任务
const serve = () => {
  // 进行初始化,里面可以指定一些配置
  bs.init({
    // 设置开屏右上角链接提示:false去掉
    notify: false,
    // 端口,默认3000
    port: 2080,
    // 是否会自动打开浏览器:false是关闭,默认是开启
    // open: false,
    // 启动过后监听的文件,如果文件有修改就主动刷新
    files: 'dist/*',
    // 核心配置
    server: {
      // 网站根目录
      baseDir: 'dist',
      // 优先于baseDir,会先匹配这个配置,没有就会去baseBir中获取,如果引用的css,js文件中有不在dist文件夹里面的,可以匹配这个。如果没有可以不用写
      routes: {
        '/node_modules': 'node_modules',
      },
    },
  })
}
module.exports = {
  serve,
}

现在我们使用yarn gulp serve命令即可创建一个服务器,然后我们修改dist下面的文件,就会自动的进行热更新。

现在我们监听的是dist文件夹,这样需要我们在编写完成后需要执行各种gulp命令,才会更新到浏览器,这个过程是比较繁琐的,这里我们通过gulpwatch方法自动监听\`各种文件的改变,然后去自动进行编译。

现在将上面的文件修改一下,修改后的代码为

// 创建一个开发服务器
const bs = browserSync.create()
// 创建服务任务
const serve = () => {
  watch(['src/assets/styles/*.scss', 'src/assets/styles/*.less'], style)
  watch('src/assets/scripts/*.js', script)
  watch('src/**/*.html', page)
  // 开发阶段不需要每一次修改都压缩文件,这些只是修改的时候重新加载即可
  watch(['src/assets/images/**', 'src/assets/fonts/**', 'public/**'], bs.reload)

  // 进行初始化,里面可以指定一些配置
  bs.init({
    // 设置开屏右上角链接提示:false去掉
    notify: false,
    // 端口,默认3000
    port: 2080,
    // 是否会自动打开浏览器:false是关闭,默认是开启
    // open: false,
    // 启动过后监听的文件,如果文件有修改就主动刷新
    files: 'dist/*',
    // 核心配置
    server: {
      // 网站根目录,多个的时候写成数组,如果路径找不到会依此去路径中寻找
      baseDir: ['dist', 'src', 'public'],
      // 优先于baseDir,会先匹配这个配置,没有就会去baseBir中获取,如果引用的css,js文件中有不在dist文件夹里面的,可以匹配这个。如果没有可以不用写
      routes: {
        '/node_modules': 'node_modules',
      },
    },
  })
}

或者通过下面这种写法,就是在任务后面手动调用更新,就可不用写init里面的files,这些写法更为常见。

代码如下:

// 创建模板引擎任务
const page = () => {
  // 通配符匹配src下main的所有子目录中的html
  let opt = {
    // swig因为模板缓存的关系无法热更新,所以需要默认设置里面关闭缓存
    defaults: { cache: false },
    data,
  }
  return (
    src('src/**/*.html', { base: 'src' })
      .pipe(plugins.swig(opt))
      .pipe(dest('dist'))
      // 以流的方式往浏览器推,每次任务执行完,都自动reload一下
      .pipe(bs.reload({ stream: true }))
  )
}

值得注意的是,我们需要关闭swig模板的缓存,否则可能导致无法热更新。

文件引用处理

如果我们的项目中引用了没有被打包进去的文件,比如node_modules中的,这个时候我们将项目上线就会存在问题,这个时候我们可以通过gulp-useref来解决这个问题,首先我们安装该插件,命令如下:

yarn add gulp-useref --dev

然后改写我们的gulpfile.js文件,代码如下:

const useref = () => {
  // dist当中的所有文件注释,进行打包压缩
  return (
    src('dist/*.html', { base: 'dist' })
      // 文件寻找路径依此进行查找,找到之后根据写的文件注释进行打包
      .pipe(plugins.useref({ searchPath: ['dist', '.'] }))
      // 读和写如果是一个文件夹可能会有问题
      .pipe(dest('release'))
  )
}

然后我们可以看到release文件夹中出现了vender.jsvender.css的一系列打包文件。

文件压缩

现在我们将打包后的js/html/css文件进行压缩,这里需要的插件有gulp-htmlmingulp-uglifygulp-clean-cssgulp-if,安装命令如下:

yarn add gulp-htmlmin gulp-uglify gulp-clean-css gulp-if --dev

然后改写我们的gulpfile.js文件,代码如下:

const useref = () => {
  // dist当中的所有文件注释,进行打包压缩
  return (
    src('dist/*.html', { base: 'dist' })
      // 文件寻找路径依此进行查找,找到之后根据写的文件注释进行打包
      .pipe(plugins.useref({ searchPath: ['dist', '.'] }))
      // 在这里会生成html js css三种类型的文件,需要对这三种文件进行压缩操作
      .pipe(plugins.if(/\.js$/, plugins.uglify()))
      .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
      // 不加collapseWhitespace只是压缩一些空格,加上会把这行等空白字符都压缩
      .pipe(
        plugins.if(
          /\.html$/,
          plugins.htmlmin({
            collapseWhitespace: true,
            // 行内样式里面的css和js用这个参数可以进行压缩
            minifyCSS: true,
            minifyJS: true,
          }),
        ),
      )
      .pipe(dest('release'))
  )
}

最后我们执行yarn gulp useref命令即可看到压缩后的css和js。

调整和整合

  • 我们的目标是在src目录下开发,使用dist目录上线,中间的转换利用临时目录temp保存
  • 有一个开发环境的任务develop,有一个生产环境的任务build
  • develop任务需要编译cssjstemplate,还需要本地起服务进行热更新
  • build任务需要编译cssjstemplate,需要压缩css,js,template,image,font和其他文件,需要外联文件进行打包压缩
  • 最后在package.json中注册script,直接用命令行就可以运行

完整代码如下:

const { src, dest, series, parallel, watch } = require('gulp')
// 引入 gulp-load-plugins 后直接调用
const plugins = require('gulp-load-plugins')()
// 非 gulp 插件需要单独引入
const del = require('del')
// 引入热更新模块
const browserSync = require('browser-sync')
// 创建一个开发服务器
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(),
}

// 创建style任务
const sassStyle = () => {
  // 输入scss的文件路径,并且指定根目录是src,在dist输出目录中,以src目录的结构输出
  return (
    // 匹配 src/assets/styles 下所有的 sass 文件
    src('src/assets/styles/*.scss', { base: 'src' })
      // 进行sass向css转换,并且指定的样式是完全展开
      // 如果不设置完全展开,那么默认css样式的右花括号不折行
      .pipe(plugins.sass(require('sass'))({ outputStyle: 'expanded' }))
      // 输出到temp临时文件夹
      .pipe(dest('temp'))
      .pipe(bs.reload({ stream: true }))
  )
}
const lessStyle = () => {
  return (
    src('src/assets/styles/*.less', { base: 'src' })
      // 进行less向css转换
      .pipe(plugins.less())
      // 输出到temp临时文件夹
      .pipe(dest('temp'))
      .pipe(bs.reload({ stream: true }))
  )
}
const style = parallel(sassStyle, lessStyle)

// 创建babel任务
const script = () => {
  return (
    src('src/assets/scripts/*.js', { base: 'src' })
      // 使用babel转换ES6语法
      .pipe(
        plugins.babel({
          // 插件集合,最新特性的全部打包,不写这个转换没有效果
          presets: ['@babel/preset-env'],
        }),
      )
      // 输出到temp临时文件夹
      .pipe(dest('temp'))
      .pipe(bs.reload({ stream: true }))
  )
}

// 创建模板引擎任务
const page = () => {
  // 通配符匹配src下main的所有子目录中的html
  let opt = {
    // swig因为模板缓存的关系无法热更新,所以需要默认设置里面关闭缓存
    defaults: { cache: false },
    data,
  }
  return (
    src('src/**/*.html', { base: 'src' })
      .pipe(plugins.swig(opt))
      // 输出到temp临时文件夹
      .pipe(dest('temp'))
      // 以流的方式往浏览器推,每次任务执行完,都自动reload一下
      .pipe(bs.reload({ stream: true }))
  )
}

// 图片压缩任务
const image = () => {
  // 匹配images下面的所有文件
  return src('src/assets/images/**', { base: 'src' })
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}

// 字体压缩任务
const font = () => {
  // 匹配images下面的所有文件
  return src('src/assets/fonts/**', { base: 'src' })
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}
// 将public的文件进行额外输出
const extra = () => {
  return src('public/**', { base: 'public' }).pipe(dest('dist'))
}

// 删除编译后的目录
const clean = () => {
  return del(['temp', 'dist'])
}
const cleanTemp = () => {
  return del(['temp'])
}

// 创建服务任务
const serve = () => {
  watch(['src/assets/styles/*.scss', 'src/assets/styles/*.less'], style)
  watch('src/assets/scripts/*.js', script)
  watch('src/**/*.html', page)
  // 开发阶段不需要每一次修改都压缩文件,这些只是修改的时候重新加载即可
  watch(['src/assets/images/**', 'src/assets/fonts/**', 'public/**'], bs.reload)

  // 进行初始化,里面可以指定一些配置
  bs.init({
    // 设置开屏右上角链接提示:false去掉
    notify: false,
    // 端口,默认3000
    port: 2080,
    // 是否会自动打开浏览器:false是关闭,默认是开启
    // open: false,
    // 启动过后监听的文件,如果文件有修改就主动刷新
    // files: 'dist/*',
    // 核心配置
    server: {
      // 网站根目录,多个的时候写成数组,如果路径找不到会依此去路径中寻找
      baseDir: ['temp', 'src', 'public'],
      // 优先于baseDir,会先匹配这个配置,没有就会去baseBir中获取,如果引用的css,js文件中有不在dist文件夹里面的,可以匹配这个。如果没有可以不用写
      routes: {
        '/node_modules': 'node_modules',
      },
    },
  })
}

// 整合css和js
const useref = () => {
  // dist当中的所有文件注释,进行打包压缩
  return (
    src('temp/*.html', { base: 'temp' })
      // 文件寻找路径依此进行查找,找到之后根据写的文件注释进行打包
      .pipe(plugins.useref({ searchPath: ['temp', '.'] }))
      // 在这里会生成html js css三种类型的文件,需要对这三种文件进行压缩操作
      .pipe(plugins.if(/\.js$/, plugins.uglify()))
      .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
      // 不加collapseWhitespace只是压缩一些空格,加上会把这行等空白字符都压缩
      .pipe(
        plugins.if(
          /\.html$/,
          plugins.htmlmin({
            collapseWhitespace: true,
            // 行内样式里面的css和js用这个参数可以进行压缩
            minifyCSS: true,
            minifyJS: true,
          }),
        ),
      )
      .pipe(dest('dist'))
  )
}

// 执行编译的组合任务
const compile = parallel(style, script, page)
// 生产环境时候的构建任务
/* - 先删除所有的文件夹
   - 下面的东西都可以并行,但是其中应该先编译sass,less,es6,template到temp临时目录,然后再压缩到dist目录
   - 图片、文字和其他的东西可以直接放到dist目录下
   - 最后删除创建的temp临时目录
*/
const build = series(
  series(clean, parallel(series(compile, useref), image, font, extra)),
  cleanTemp,
)

// 开发时候的构建任务
// 只需要编译之后发布到本地服务器上即可
const develop = series(compile, serve)

// 暴露开发和生产打包的任务命令
module.exports = {
  clean, // 删除文件可以暴露
  build,
  develop,
}

最后我们可以在npm stript中注册该命令,可以优化我们使用的指令,代码如下:

//package.json
{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "clean": "gulp clean",
    "build": "gulp build",
    "develop": "gulp develop"
  }
}

现在就可以使用yarn build命令来代替yarn gulp build命令。

写在最后

虽然Gulp在应用越来越少,但是这个工具是前端工程化的道路上的重要一环,有的项目还是使用Gulp构建,了解和熟悉还是有必要的。

目录
相关文章
|
1月前
|
机器学习/深度学习 人工智能 运维
构建高效运维体系:从自动化到智能化的演进
本文探讨了如何通过自动化和智能化手段,提升IT运维效率与质量。首先介绍了自动化在简化操作、减少错误中的作用;然后阐述了智能化技术如AI在预测故障、优化资源中的应用;最后讨论了如何构建一个既自动化又智能的运维体系,以实现高效、稳定和安全的IT环境。
65 4
|
11天前
|
测试技术 开发者 Python
自动化测试之美:从零构建你的软件质量防线
【10月更文挑战第34天】在数字化时代的浪潮中,软件成为我们生活和工作不可或缺的一部分。然而,随着软件复杂性的增加,如何保证其质量和稳定性成为开发者面临的一大挑战。自动化测试,作为现代软件开发过程中的关键实践,不仅提高了测试效率,还确保了软件产品的质量。本文将深入浅出地介绍自动化测试的概念、重要性以及实施步骤,带领读者从零基础开始,一步步构建起属于自己的软件质量防线。通过具体实例,我们将探索如何有效地设计和执行自动化测试脚本,最终实现软件开发流程的优化和产品质量的提升。无论你是软件开发新手,还是希望提高项目质量的资深开发者,这篇文章都将为你提供宝贵的指导和启示。
|
27天前
|
运维 监控 jenkins
运维自动化实战:利用Jenkins构建高效CI/CD流程
【10月更文挑战第18天】运维自动化实战:利用Jenkins构建高效CI/CD流程
|
1月前
|
运维 监控 测试技术
构建高效运维体系:从监控到自动化的实践之路
【10月更文挑战第9天】 在当今信息技术飞速发展的时代,运维作为保障系统稳定性与效率的关键角色,正面临前所未有的挑战。本文将探讨如何通过构建一个高效的运维体系来应对这些挑战,包括监控系统的搭建、自动化工具的应用以及故障应急处理机制的制定。我们将结合具体案例,分析这些措施如何帮助提升系统的可靠性和运维团队的工作效率。
51 1
|
1月前
|
运维 监控 安全
构建高效运维体系:从监控到自动化的全面指南在当今数字化时代,运维作为保障系统稳定性和效率的重要环节,其重要性不言而喻。本文将深入探讨如何构建一个高效的运维体系,从监控系统的搭建到自动化运维的实施,旨在为读者提供一套完整的解决方案。
本文详细介绍了高效运维体系的构建过程,包括监控系统的选择与部署、日志分析的方法、性能优化的策略以及自动化运维工具的应用。通过对这些关键环节的深入剖析,帮助运维人员提升系统的可靠性和响应速度,降低人工干预成本,实现业务的快速发展和稳定运行。
|
1月前
|
运维 jenkins 持续交付
自动化运维之路:构建高效CI/CD流水线
在软件开发的快节奏中,持续集成和持续部署(CI/CD)流水线是提升效率、保障质量的关键。本文将引导你理解CI/CD流水线的重要性,并手把手教你如何搭建一个高效的自动化运维系统。通过实际代码示例,我们将一步步实现从代码提交到自动测试、部署的全流程自动化,确保软件交付过程既快速又可靠。
|
1月前
|
机器学习/深度学习 运维 监控
构建高效运维体系:从自动化到智能化的演进之路
在当今数字化时代,运维工作的重要性日益凸显。随着企业业务的不断扩展和技术的日新月异,传统的运维方式已难以满足现代企业的需求。因此,构建一个高效、智能的运维体系成为了企业发展的关键。本文将探讨如何从自动化逐步演进到智能化,以实现运维工作的高效化和智能化。
|
1月前
|
机器学习/深度学习 运维 监控
构建高效运维体系:从自动化到智能化的演进之路
在当今数字化浪潮中,运维作为信息技术的重要支柱,其重要性日益凸显。本文将探讨如何通过自动化和智能化手段,提升运维效率,保障系统稳定性,促进业务持续发展。
|
1月前
|
JavaScript 前端开发 搜索推荐
Gulp:构建自动化与任务管理的强大工具
【10月更文挑战第13天】Gulp:构建自动化与任务管理的强大工具
67 0
|
1月前
|
机器学习/深度学习 运维 自然语言处理
构建高效运维体系:从自动化到智能化的演进之路
随着信息技术的飞速发展和企业数字化转型的加速,运维管理作为保障业务连续性和系统稳定性的关键环节,正面临着前所未有的挑战与机遇。本文深入探讨了如何通过引入自动化工具和技术,实现运维流程的标准化、自动化和智能化,进而提升整个组织的运维效率和响应速度。同时,文章也详细分析了在实施自动化运维过程中需要考虑的关键因素,如人员技能提升、流程优化和文化变革等,以期为企业提供一套全面、实用的运维管理升级方案。