gulp的基础使用与模块发布

文章说明:本文章为拉钩大前端训练营所做笔记和心得,若有不当之处,还望各位指出与教导,谢谢 ! 

一、gulp

Gulp核心特点是高效,易用。

  • 创建文件夹,并初始化package.json包管理文件:
      $ mkdir project-name
      $ cd project-name
      $ yarn init --yes # or npm init -y
    

     

  •  安装gulp模块,同时会安装一个Gulp CLI的命令

      $ yarn add gulp --dev # or npm install gulp --save-dev
    

     

  •  使用命令添加一个 gulp 的 入口文件 gulpfile.js ,并进行编码
      $ code gulpfile.js
    

    在gulpfile.js文件中定义一些Gulp需要执行的一些Gulp的构建任务

// gulp的入口文件,因为这个文件时运行在node.js的环境当中,所以说可以在在这个文件当中运行commentJS规范
//这个文件定义构建任务的方式就是通过导出成员的方式去定义

exports.foo = () =>{
    console.log('foo task working~')
}

运行结果:yarn gulp foo 代码指定执行foo这个任务

报错:我们的foo任务执行没有完成,问我们是否忘记去标识这个任务的结束,这是因为在最新的gulp当中取消了同步代码模式,约定每一个任务都必须是一个异步的任务,当我们的任务执行完成过后需要通过调用回调函数或者其他的方式去标记任务已经完成,所以说我们在解决这个问题这一块,需要做的事情就是手动去调用一个回调函数,这个回调函数我们可以在代码当中通过foo这个函数的形式参数得到:


//这个回调函数我们可以在代码当中通过foo这个函数的形式参数得到,接收一个done的参数,这个done就是一个函数,可以在任务执行过后调用这个函数
exports.foo = done =>{
    console.log('foo task working~')

    done()//标识任务完成
}//这就是我们在gulp当中去定义一个任务的操作方式

运行结果:

标识已经完成foo任务。

如果说你的任务方式是default的话,它会作为gulp的默认任务出现

exports.default = done => {
    console.log('default task working~')
    done()
}

使用 yarn gulp 命令运行,会直接执行默认任务

在gulp4.0以前,我们注册任务时需要通过gulp模块里面的一个方法去实现,通过require的一个方法:

const gulp = require('gulp')

gulp.task('bar',done =>{
    console.log('bar working~')
    done()
})//注册了一个叫bar的任务,这种方式不被推荐

Gulp组合任务的API:series和parallel,可以创建并行任务和串行任务,使用series实现串行任务,使用parallel实现并行任务,使用parallel实现并行任务,使用这两种方法对实际创建构建工作流很有用。

const {series,parallel} = require('gulp')

//这些未被导出的函数可以理解成私有的任务,各自模拟了一个需要执行1秒的任务,并不能通过gulp去直接运行他们,通过gulp的series和parallel这两个API去把这些任务组合
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是一个函数,每一个参数都可以是一个任务
exports.bar = parallel(task1,task2,task3)

串行任务一般用在项目部署上,即需要先执行编译的任务,再进行部署。并行任务一般用于同时开始编译js和css文件时。

Gulp的异步任务

我们调用一个异步函数时通常没有直接明确这个函数是否是完成的,都是在函数内部或者事件的方式通知外部这个函数执行完成,我们在异步任务当中同样面临如何通知gulp我们的完成情况这样一个问题,针对这个问题gulp有很多解决方法,以下是常用的几种方式:

  • callback_error
exports.callback = done => {
    console.log('callback')
    done()//调用done表示这个任务执行完成
}//这个回调函数它与node当中回调函数是同样的标准都是一种错误优先的回调函数,也就是说当我们想在执行过程中报出一个错误去阻止剩下的任务执行的时候,这个时候我们可以通过给回调函数的第一个回调参数去指定一个错误对象就可以了:
exports.callback_error = done =>{
    console.log('callback task~')
    done(new Error('task failed!'))
}

  • promise
//在gulp当中同样支持promise的方式
exports.promise = () => {
    console.log('promise task~')
    return Promise.resolve()//返回一个成功的promise,一旦当我们返回的对象resolve了就意味着我们这个任务结束了,resolve的时候我们不需要返回任何的值,因为gulp当中它会忽略掉这个值
}

exports.promise_error = () =>{
    console.log('promise task~')
    return Promise.reject(new Error('task failed~'))//gulp会认为这是一个失败的任务,它同样会结束后续所有的任务的执行
}

  • async
const timeout = time =>{//把setTimeout包装成promise函数
    return new Promise(resolve =>{
        setTimeout(resolve,time)
    })
}
exports.async = async() =>{//在async当中await这个函数
    await timeout(1000)//在这个位置等待promis的resolve,resolve完成过后就会执行下面的代码
    console.log('async task~')
}

  • 通过stream的方式处理异步任务最为常见的,因为我们的构建系统大都是在处理文件,所以这种方式也是最常用到的一种

exports.stream = () =>{
    //通过fs的createReadStream方法读取文件的文件流
    const readStream = fs.createReadStream('package.json')
    //创建写入文件的文件流
    const writeStream = fs.createWriteStream('temp.txt')
    //把readStream通过pipe的方式导到writeStream当中,可以理解为从一个水池子往另外一个水池子倒水,就会起到文件复制的作用
    readStream.pipe(writeStream)
    return readStream
}

stream当中有一个end事件,当文件流读取完成过后就会触发end事件,gulp就了解了这个任务已经完成了

exports.stream = done =>{
    //通过fs的createReadStream方法读取文件的文件流
    const readStream = fs.createReadStream('package.json')
    //创建写入文件的文件流
    const writeStream = fs.createWriteStream('temp.txt')
    //把readStream通过pipe的方式导到writeStream当中,可以理解为从一个水池子往另外一个水池子倒水,就会起到文件复制的作用
    readStream.pipe(writeStream)
    readStream.on('end',() =>{//gulp为stream注册了一个end事件,这个事件当中结束了这个任务执行,
        done()//调用done函数模拟gulp当中结束这个任务的操作
    })
}

以上是gulp当中经常会用到的处理异步流程的操作

Gulp构建过程核心工作原理

构建过程大多数情况下都是将文件读出来然后进行一些转换,最后去写入到另外一个位置,在没有构建系统的情况下,我们也就是人工去按照这个过程去做的。例如我们压缩一个css文件,需要把代码复制出来,然后到压缩工具当中压缩一下,然后把压缩过后的结果粘贴到一个新的文件当中,这是一个手动的过程,其实通过代码的方式解决也是类似的,通过对底层的原始流的api去实现一下这样一个过程:

const fs = require('fs')

exports.default = () =>{
  //文件读取流
  const read = fs.createReadStream('normalize.css')
  //文件写入流
  const write = fs.createWriteStream('normalize.min.css')

  //把读取出来的文件流导入写入文件流
  read.pipe(write)

  return read
}

将normalize.css文件复制到了normalize.min.css文件当中

现在我们将文件读出来经过转换后再写入文件:

const fs = require('fs')
const { Transform } = require('stream')//导入stream模块当中的transform这个类型,这个类型可以创建一个文件转换流对象

exports.default = () =>{
  //文件读取流
  const read = fs.createReadStream('normalize.css')
  //文件写入流
  const write = fs.createWriteStream('normalize.min.css')
  //文件转换流
  const transform = new Transform({
    transform:(chunk,encoding,callback) =>{
      //核心转换过程实现
      //chunk =>读取流中读取的内容(Buffer)
      //因为读出来是一个自己数组,通过tostring的方式转换为字符串
      const input = chunk.toString()
      //替换掉空白字符,然后替换掉css当中的注释
      const output = input.replace(/\s+/g,'').replace(/\/\*.+?\//g,'')
      //将output返回出去,callback是一个错误优先的回调函数,如果没有发生错误的话,先传入null
      callback(null,output)
    }
  })
  //把读取出来的文件流导入写入文件流
  read
  .pipe(transform)//到转换流当中完成转换
  .pipe(write)//写入流

  return read
}

转换后的文件:

Gulp文件操作API

相对于底层node的api,它更强大也更容易使用,对于独立的文件转换流,我们一般使用强大的插件。我们在 实际去通过gulp创建构建任务时的流程,就是先通过src方法去创建一个读取流,然后在借助于插件的转换流实现文件加工,最后通过gulp提供的dest方法创建一个写入流,从而写入目标文件。

先复制文件:

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

exports.default = () => {
  //通过src方法创建一个文件的读取流,将读取流return出去,这样gulp能控制任务完成
  return src('src/normalize.css')
  //通过pipe的方式导出到dest所创建的写入流当中
    .pipe(dest('dist'))
}

文件复制到dist中的norma.css中了:

使用gulp插件当中提供的一些装换流:

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

exports.default = () => {
  //通过src方法创建一个文件的读取流,将读取流return出去,这样gulp能控制任务完成
  return src('src/*.css')//src下所有的css文件
  //pipe到cleanCss当中提供的转换流
    .pipe(cleanCss())
    //若是要添加额外的多个转换的操作,例如我们添加gulp-rename插件
    .pipe(rename({extname:'.min.css'}))//pipe到转换流当中,这个rename可以指定一个extname参数,这个参数用于指定重命名的扩展名为.min.css
  //通过pipe的方式导出到dest所创建的写入流当中
    .pipe(dest('dist'))
}
//gulp-clean-css 这个插件提供了压缩css代码的转换流
//dist目录下出现了bootstrap.min.css和normalize.min.css,意味着第二种转换流插件也正常工作了

安装gulp-clean-css插件:

运行:

可以看到src下所有的css文件都被转换压缩为dist目录下的css文件:

若是要添加额外的多个转换的操作,例如我们添加gulp-rename插件:

运行后:

src下的css文件生成了以.min.css为后缀的扩展名的文件:

四、Gulp案例:样式编译

项目案例目录:

在gulpfile.js文件里构建任务:

gulp-sass:gulp-sass 模块 用来将 sass 样式文件转换成 css 样式文件,同时 gulp-sass 模块 需要npm提供sass模块进行支持,因此两个模块都需要安装。

yarn add gulp-sass --dev

将sass文件转换成css文件:

const {src,dest} = require('gulp')
//gulp-sass 模块 用来将 sass 样式文件转换成 css 样式文件,同时 gulp-sass 模块 需要npm提供sass模块进行支持,因此两个模块都需要安装。
const sass = require('gulp-sass')
//sass在转换的时候,前面有下划线开头的文件名不会被转换,认为这些是我们主文件当中所依赖的文件,会忽略掉。

const style = () => {//style是一个私有的任务,并不能通过gulp去执行
  return src('src/assets/styles/*scss',{base:'src'})//base的作用是能保证原来的目录结构,此时会把src后面的目录结构保留下来
    //基本上每个插件都会提供一个函数,每个函数会返回一个转换流
    .pipe(sass({outputStyle:'expanded'}))//转换成将样式代码完全展开的格式
    .pipe(dest('dist'))
}
//通过module.exports导出一个对象
module.exports = {
  style
}

sass在转换的时候,前面有下划线开头的文件名不会被转换,认为这些是我们主文件当中所依赖的文件,会忽略掉。

五、Gulp案例:脚本编译

gulp-babel

gulp-babel 模块,用来将 ES6 语法转换成 ES5 语法,但是 gulp-babel 模块只是唤醒 @babel/core 模块中的转换过程,并不会自动的去调用 @babel/core模块中的转换方法,因此,需要同时安装 @babel/core模块。并且,若需要将ECMAScript 所有的新特性进行转换时,还需要安装 @babel/preset-env 模块。

安装:

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

将ES6语法编译为ES5语法

const {src,dest} = require('gulp')
//gulp-sass 模块 用来将 sass 样式文件转换成 css 样式文件,同时 gulp-sass 模块 需要npm提供sass模块进行支持,因此两个模块都需要安装。
const sass = require('gulp-sass')
//sass在转换的时候,前面有下划线开头的文件名不会被转换,认为这些是我们主文件当中所依赖的文件,会忽略掉。
//使用babel 依赖将ES6语法编译为ES5语法
const babel = require('gulp-babel')
const style = () => {
  return src('src/assets/styles/*scss',{base:'src'})
    .pipe(sass({outputStyle:'expanded'}))
    .pipe(dest('dist'))
}

const script = () =>{
  return src('src/assets/scripts/*.js',{base:'src'})
  //babel默认只是ECMAScript的转换平台,平台是不做任何事情的,它只是提供一个环境,具体去做转换的实际上是babel里面它内部里的插件,而preset就是一些插件的集合,preset-env就是一些最新的整体的特性进行打包,
//使用它的话就把所有的最新的特性做转换,也可以根据需要做对应的转换插件然后在这个地方制定babel下对应的插件,这样的话只会转换对应的特性
    .pipe(babel({presets:['@babel/preset-env']}))
    .pipe(dest('dist'))
}
module.exports = {
  style,
  script
}

六、Gulp案例:页面模板编译

模板文件就是html文件,在这些html文件当中我们为了可以去让页面当中重复的地方被抽象出来,这使用的模板引擎叫做swig:

gulp-swig用来将使用swig模板引擎的html文件转换成正常的html文件,安装:

yarn add gulp-swig --dev
const {src,dest,parallel} = require('gulp')
const swig = require('gulp-swig')
const sass = require('gulp-sass')
//sass在转换的时候,前面有下划线开头的文件名不会被转换,认为这些是我们主文件当中所依赖的文件,会忽略掉。
//使用babel 依赖将ES6语法编译为ES5语法
const babel = require('gulp-babel')
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(sass({outputStyle:'expanded'}))
    .pipe(dest('dist'))
}

const script = () =>{
  return src('src/assets/scripts/*.js',{base:'src'})
  //babel默认只是ECMAScript的转换平台,平台是不做任何事情的,它只是提供一个环境,具体去做转换的实际上是babel里面它内部里的插件,而preset就是一些插件的集合,preset-env就是一些最新的整体的特性进行打包,
//使用它的话就把所有的最新的特性做转换,也可以根据需要做对应的转换插件然后在这个地方制定babel下对应的插件,这样的话只会转换对应的特性
    .pipe(babel({presets:['@babel/preset-env']}))
    .pipe(dest('dist'))
}
const page = () => {
  return src('src/*.html',{base:'src'})
  //将编写的data自动的根据模板文件填入然后转化成html文件,这样可以将网页当中经常写死的数据提取出来在代码当中配置
    .pipe(swig({data:data}))
    .pipe(dest('dist'))
}
//将三个任务组合起来并行执行,这样可以提高构建效率
const compile = parallel(style,script,page)
module.exports = {
  compile
}

七、Gulp案例:图片和字体文件转换

gulp-imagemin模块,用来对图片进行压缩后,转换到目标目录:

yarn add gulp-imagemin --dev//或者 npm install gulp-imagemin --save-dev

将图片压缩后编译:

  const { src, dest, parallel } = require('gulp')
  // 将图片进行压缩后转换
  const imagemin = require('gulp-imagemin')
  
  // 图片压缩编译
  const image = () => {
      return src('src/assets/images/**', { base: 'src' })
              .pipe(imagemin())
              .pipe(dest('dist'))
  }
  // 字体文件编译
  const font = () => {
      // 字体文件可以直接拷贝进目标文件,但存在 .svg文件,因此可以使用imagemin() 进行转换
      return src('src/assets/fonts/**', { base: 'src' })
              .pipe(imagemin())
              .pipe(dest('dist'))
  }
  
  // 创建并行任务,完成 src目录下面需要编译的文件
  const compile = parallel(style, script, page, image, font)
  
  module.exports = {
      compile
  }

 八、其他文件及文件清除

安装del插件,这个插件不是gulp的单独插件:

yarn add del --dev
const {src,dest,parallel,series} = require('gulp')

const del = require('del')

const swig = require('gulp-swig')
const sass = require('gulp-sass')
const babel = require('gulp-babel')
const imagemin = require('gulp-imagemin')
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(sass({outputStyle:'expanded'}))
    .pipe(dest('dist'))
}

const clean = () => {
  return del(['dist'])//返回的是promise,意味着delete完成后,它可以标记clean任务执行完成
}
const script = () =>{
  return src('src/assets/scripts/*.js',{base:'src'})
    .pipe(babel({presets:['@babel/preset-env']}))
    .pipe(dest('dist'))
}
const page = () => {
  return src('src/*.html',{base:'src'})
    .pipe(swig({data:data}))
    .pipe(dest('dist'))
}

//字体文件编译
const font = () =>{
  // 字体文件可以直接拷贝进目标文件,但存在 .svg文件,因此可以使用imagemin() 进行转换
  return src('src/assets/fonts/**',{base:'src'})
    .pipe(imagemin())
    .pipe(dest('dist'))
}
//图片压缩编译
const image = () =>{
  return src('src/assets/images/**',{base:'src'})
    .pipe(imagemin())
    .pipe(dest('dist'))
}
//这是个额外拷贝的任务
const extra = () =>{
  return src('public/**',{base:'public'})
    .pipe(dest('dist'))
}

//将三个任务组合起来并行执行,这样可以提高构建效率
const compile = parallel(style,script,page,image,font)
//使用series串行执行任务,先清除文件,再执行其它
const build = series(clean,parallel(compile,extra))
module.exports = {
  compile,
  build
}

九、Gulp案例:自动加载插件

手动的方式加载插件,require的操作会非常多,不太利于后期维护代码,可以通过插件解决这个问题:

Gulp-load-plugins:

yarn add gulp-load-plugins --dev

使用:

const loadPlugins = require('gulp-load-plugins')//导出的是一个方法
const plugins = loadPlugins() //plugins是一个对象,所有的插件都是这个对象下面的属性

const style = () => {
  return src('src/assets/styles/*scss',{base:'src'})
  //plugins.xxx  xxx 代表插件的名称,即去掉 gulp- 前缀后的名称,若有多级,采用驼峰命名
    .pipe(plugins.sass({outputStyle:'expanded'}))
    .pipe(dest('dist'))
}

module.exports = {
    style
}

十、Gulp案例:自动化构建案例

除了对文件构建操作以外,我们这里还需要一个开发服务器,用于去在开发阶段调试我们的应用,用于gulp去启动和管理我们的应用,这样的话我们就可以后续配合我们其它的一些构建任务去实现代码修改过后自动编译,并且自动刷新浏览器页面,这样就能大大提高开发效率:

browser-sync:

browser-sync 模块,提供开发服务器,他支持修改过后自动热更新到浏览器中,让我们可以即时的看到最新页面效果。它并不是gulp插件,只是可以通过 gulp 进行管理。安装插件模块:

 yarn add browser-sync --dev // 或者 npm install browser-sync --save-dev
const browserSync = require('browser-sync')
const bs = browserSync.create()//自动创建一个开发服务器

//把开发服务器定义到单独的一个任务当中使用
const serve = () => {
  //通过bs的init方法去初始化一下web服务器的一些相关配置,最核心的配置就是server
  bs.init({
    notify:false,//关掉右上角的小提示
    port:2080//端口
    open:false,//取消自动打开浏览器
    files:'dist/**',//可以指定一个字符串,这个字符串就是用来去被browser-sync监听的路径通配符,这里面的文件发生改变过后,browser-sync自动更新浏览器
    //server当中需要去指定一下我们的网站的根目录,也就是web服务器它需要帮你把哪个目录作为网站的根目录,通过baseDir去设置
    server:{
      baseDir:'dist'//src下是未加工的代码,所以用dist
      //加一个特殊的路由,让它对于这种/node_modules开头的网页请求的话,我们都给它指到同一个目录下面
      routes:{
        //这里面会优先于我们baseDir的配置,先看routes里面有没有配置,再看baseDir下对应的文件,键是我们请求的前缀,然后我们把它指到目录下面
        '/node_modules':'node_modules'//相对于我们网站根目录下的node_modules
      }
    }
  })
}

module.exports = {
  compile,
  build,
  serve
}

现在我们来监视src下的文件的变化,一旦他们发生变化过后,我们不是直接去刷新浏览器而是去执行我们的构建任务:

监视变化以及构建过程优化:

Gulp提供了一个watch的api,会自动监视一个文件路径的通配符,然后根据这个文件的变化决定是否要重新去执行某个任务。

const {src,dest,parallel,series,watch} = require('gulp')

const del = require('del')
const browserSync = require('browser-sync')
const bs = browserSync.create()//自动创建一个开发服务器


const loadPlugins = require('gulp-load-plugins')//导出的是一个方法
const plugins = loadPlugins() //plugins是一个对象,所有的插件都是这个对象下面的属性
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'})
  //plugins.xxx  xxx 代表插件的名称,即去掉 gulp- 前缀后的名称,若有多级,采用驼峰命名
    .pipe(plugins.sass({outputStyle:'expanded'}))
    .pipe(dest('dist'))
}
//把开发服务器定义到单独的一个任务当中使用
const serve = () => {
  watch('src/assets/styles/*scss',style)//指定两个参数,第一个参数路径通配符,第二个是对应的任务
  watch('src/assets/scripts/*.js',script)
  watch('src/*.html',page)
  watch('src/assets/fonts/**',font)
  watch('src/assets/images/**',image)
  watch('public/**',extra)
  //通过bs的init方法去初始化一下web服务器的一些相关配置,最核心的配置就是server
  bs.init({
    notify:false,//关掉右上角的小提示
    port:2080//端口
    open:false,//取消自动打开浏览器
    files:'dist/**',//可以指定一个字符串,这个字符串就是用来去被browser-sync监听的路径通配符,这里面的文件发生改变过后,browser-sync自动更新浏览器
    //server当中需要去指定一下我们的网站的根目录,也就是web服务器它需要帮你把哪个目录作为网站的根目录,通过baseDir去设置
    server:{
      baseDir:'dist'//src下是未加工的代码,所以用dist
      //加一个特殊的路由,让它对于这种/node_modules开头的网页请求的话,我们都给它指到同一个目录下面
      routes:{
        //这里面会优先于我们baseDir的配置,先看routes里面有没有配置,再看baseDir下对应的文件,键是我们请求的前缀,然后我们把它指到目录下面
        '/node_modules':'node_modules'//相对于我们网站根目录下的node_modules
      }
    }
  })
}

const clean = () => {
  return del(['dist'])//返回的是promise,意味着delete完成后,它可以标记clean任务执行完成
}
const script = () =>{
  return src('src/assets/scripts/*.js',{base:'src'})
    .pipe(plugins.babel({presets:['@babel/preset-env']}))
    .pipe(dest('dist'))
}
const page = () => {
  return src('src/*.html',{base:'src'})
    .pipe(plugins.swig({data:data}))
    .pipe(dest('dist'))
}

//字体文件编译
const font = () =>{
  // 字体文件可以直接拷贝进目标文件,但存在 .svg文件,因此可以使用imagemin() 进行转换
  return src('src/assets/fonts/**',{base:'src'})
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}
//图片压缩编译
const image = () =>{
  return src('src/assets/images/**',{base:'src'})
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}
//这是个额外拷贝的任务
const extra = () =>{
  return src('public/**',{base:'public'})
    .pipe(dest('dist'))
}

//将三个任务组合起来并行执行,这样可以提高构建效率
const compile = parallel(style,script,page,image,font)
//使用series串行执行任务,先清除文件,再执行其它
const build = series(clean,parallel(compile,extra))
module.exports = {
  compile,
  build,
  serve
}

哪些任务在开发阶段不需要执行,哪些需要?然后进行优化:图片文件只是做了个画质无损的压缩,还有文字和公共的部分都做了一个压缩,可以在开发过程中减少构建次数

const {src,dest,parallel,series,watch} = require('gulp')

const del = require('del')
const browserSync = require('browser-sync')
const bs = browserSync.create()//自动创建一个开发服务器


const loadPlugins = require('gulp-load-plugins')//导出的是一个方法
const plugins = loadPlugins() //plugins是一个对象,所有的插件都是这个对象下面的属性
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'})
  //plugins.xxx  xxx 代表插件的名称,即去掉 gulp- 前缀后的名称,若有多级,采用驼峰命名
    .pipe(plugins.sass({outputStyle:'expanded'}))
    .pipe(dest('dist'))
}
//把开发服务器定义到单独的一个任务当中使用
const serve = () => {
  watch('src/assets/styles/*scss',style)//指定两个参数,第一个参数路径通配符,第二个是对应的任务
  watch('src/assets/scripts/*.js',script)
  watch('src/*.html',page)
  // watch('src/assets/fonts/**',font)
  // watch('src/assets/images/**',image)
  // watch('public/**',extra)
  watch([
    'src/assets/fonts/**',
    'src/assets/images/**',
    'public/**'
  ],bs.reload)//这些文件发生变化过后它自动更新浏览器

  //通过bs的init方法去初始化一下web服务器的一些相关配置,最核心的配置就是server
  bs.init({
    notify:false,//关掉右上角的小提示
    port:3000,//端口
    // open:false,//取消自动打开浏览器
    files:'dist/**',//可以指定一个字符串,这个字符串就是用来去被browser-sync监听的路径通配符,这里面的文件发生改变过后,browser-sync自动更新浏览器
    //server当中需要去指定一下我们的网站的根目录,也就是web服务器它需要帮你把哪个目录作为网站的根目录,通过baseDir去设置
    server:{
      //指定一个数组,当请求过来之后,回到数组中第一个目录去找,找不到这个文件的话就依次往后去找
      baseDir:['dist','src','public'],
      //加一个特殊的路由,让它对于这种/node_modules开头的网页请求的话,我们都给它指到同一个目录下面
      routes:{
        //这里面会优先于我们baseDir的配置,先看routes里面有没有配置,再看baseDir下对应的文件,键是我们请求的前缀,然后我们把它指到目录下面
        '/node_modules':'node_modules'//相对于我们网站根目录下的node_modules
      }
    }
  })
}

const clean = () => {
  return del(['dist'])//返回的是promise,意味着delete完成后,它可以标记clean任务执行完成
}
const script = () =>{
  return src('src/assets/scripts/*.js',{base:'src'})
    .pipe(plugins.babel({presets:['@babel/preset-env']}))
    .pipe(dest('dist'))
}
const page = () => {
  return src('src/*.html',{base:'src'})
    .pipe(plugins.swig({data:data}))
    .pipe(dest('dist'))
}

//字体文件编译
const font = () =>{
  // 字体文件可以直接拷贝进目标文件,但存在 .svg文件,因此可以使用imagemin() 进行转换
  return src('src/assets/fonts/**',{base:'src'})
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}
//图片压缩编译
const image = () =>{
  return src('src/assets/images/**',{base:'src'})
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}
//这是个额外拷贝的任务
const extra = () =>{
  return src('public/**',{base:'public'})
    .pipe(dest('dist'))
}

//将三个任务组合起来并行执行,这样可以提高构建效率
const compile = parallel(style,script,page)
//上线之前执行的任务
const build = series(clean,parallel(compile,image,font,extra))
 
const develop = series(compile,serve)
module.exports = {
  compile,
  build,
  serve,
  develop
}

useref文件引用处理:

对于dist文件下还有一些小问题,dist里面的一些html里面可能会有一些node_modules里面的一些依赖,这里面的一些文件并没有拷贝到dist目录下面,dist目录部署上线的话肯定会出现问题,会找不到下面的bootsrap的这些文件,开发阶段没有出现问题的原因是因为我们 在serve命令里面已经做了一个路由的映射,并不能在线上环境去做,还需要单独处理,常见的是用useref处理:

useref 模块,会自动处理 HTML文件中的构建注释,但是只有在编译后的 HTML文件中才会存在构建注释,因此,这个插件提供的 useref 任务,需要在编译后再运行。

构建注释 ,即包含一个开始的 build 和一个结束的 endbuild,会将里面包裹的多个标签,合并一个文件,如下面的 vendor.css

    <!-- 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 -->

安装插件模块

  $ yarn add gulp-useref --dev # or npm install gulp-useref --save-dev

经过useref过后,会把构建注释当中引入的一些资源全部合并到同一个文件当中:

const {src,dest,parallel,series,watch} = require('gulp')
const loadPlugins = require('gulp-load-plugins')//导出的是一个方法
const plugins = loadPlugins() //plugins是一个对象,所有的插件都是这个对象下面的属性

const useref = () => {
  return src('dist/*.html',{base:'dist'})//找dist下面的html
  .pipe(plugins.useref({searchPath:['dist','.']}))//创建一个转换流,把代码当中的构建注释去做对应的转换,searchPath中的是注释中引用的根目录
  .pipe(dest('dist'))
}

module.exports = {
  compile,
  build,
  serve,
  develop,
  useref
}

构建注释当中引用的资源全部合并到同一个文件当中,比如第一个引用的css文件全部合并到了vendor.css当中

文件压缩:

useref把我们需要的文件全部拿过来了,现在将这些文件压缩,三种不同类型的文件:html,js,css文件,先安装各个类型的压缩插件,gulp-htmlmin, gulp-uglify ,gulp-clean-css分别对应压缩html,js,css文件的插件:

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

Gulp-if:

安装gulp-if模块,可以用来判断读取流中的文件类型,在其内部会自动创建转换流:

  $ yarn add gulp-if --dev # or npm install gulp-if --save-dev

  const { src, dest, parallel, series, watch } = require('gulp')

const del = require('del')
const browserSync = require('browser-sync')

const loadPlugins = require('gulp-load-plugins')

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 clean = () => {
  return del(['dist', 'temp'])
}

const style = () => {
  return src('src/assets/styles/*.scss', { base: 'src' })
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}

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

const page = () => {
  return src('src/*.html', { base: 'src' })
    .pipe(plugins.swig({ data, defaults: { cache: false } })) // 防止模板缓存导致页面不能及时更新
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}

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 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,
    port: 2080,
    // open: false,
    // files: 'dist/**',
    server: {
      baseDir: ['temp', 'src', 'public'],
      routes: {
        '/node_modules': 'node_modules'
      }
    }
  })
}

const useref = () => {
  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()))
    .pipe(plugins.if(/\.html$/, plugins.htmlmin({
      collapseWhitespace: true,
      minifyCSS: true,
      minifyJS: true
    })))
    .pipe(dest('dist'))
}

const compile = parallel(style, script, page)

// 上线之前执行的任务
const build =  series(
  clean,
  parallel(
    series(compile, useref),
    image,
    font,
    extra
  )
)

const develop = series(compile, serve)

module.exports = {
  clean,
  build,
  develop
}

Gulp案例补充:

把module.exports 中导出的三个任务都放到package.json的scripts当中,可以更容易让别人理解一点。想要了解一个项目的构建过程的话一般会从scripts里面着手。下面来看如何提取多个项目自动化构建的过程:

封装工作流:

提取一个可复用的自动化工作流,通过我们去创建一个新的模块去包装一下gulp,然后把这个自动化工作流给它包装进去,因为gulp只是一个自动化工作流的平台,他不负责帮你提供任何的构建任务,你的构建任务需要通过你的Gulpfile去定义,把gulpfile和gulp通过一个模块结合到一起,结合到一起过后我们在以后同类型项目当中就使用这个模块去提供自动化工作流就好了,以上就是我们的一个办法。先做一些准备性的工作,具体的方法就是创建一个模块,然后把这个模块发布到npm仓库上面,最后在项目当中使用这个模块就可以了:

  • 创建gulp-pages目录结构

在这里插入图片描述

  • 将package.json中指向的入口文件地址,改为“lib/index.js”,如下图所示:

  • 编写 lib/index.js 入口文件,此入口文件,就是上面Gulp案例中的 gulpfile.js文件:
const {src,dest,parallel,series,watch} = require('gulp')

const del = require('del')
const browserSync = require('browser-sync')
const bs = browserSync.create()//自动创建一个开发服务器


const loadPlugins = require('gulp-load-plugins')//导出的是一个方法
const plugins = loadPlugins() //plugins是一个对象,所有的插件都是这个对象下面的属性
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 serve = () => {
  watch('src/assets/styles/*scss',style)//指定两个参数,第一个参数路径通配符,第二个是对应的任务
  watch('src/assets/scripts/*.js',script)
  watch('src/*.html',page)
  watch([
    'src/assets/fonts/**',
    'src/assets/images/**',
    'public/**'
  ],bs.reload)

  bs.init({
    notify:false,
    port:3000,
    files:'dist/**',
    server:{
      baseDir:['temp','src','public'],
      routes:{
        '/node_modules':'node_modules'
      }
    }
  })
}

const clean = () => {
  return del(['dist','temp'])//返回的是promise,意味着delete完成后,它可以标记clean任务执行完成
}
const style = () => {
  return src('src/assets/styles/*scss',{base:'src'})
    .pipe(plugins.sass({outputStyle:'expanded'}))
    .pipe(dest('temp'))
    .pipe(bs.reload({stream:true}))
}
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}))
}
const page = () => {
  return src('src/*.html',{base:'src'})
    .pipe(plugins.swig({data:data}))
    .pipe(dest('temp'))
    .pipe(bs.reload({stream:true}))
}

//字体文件编译
const font = () =>{
  return src('src/assets/fonts/**',{base:'src'})
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}
//图片压缩编译
const image = () =>{
  return src('src/assets/images/**',{base:'src'})
    .pipe(plugins.imagemin())
    .pipe(dest('dist'))
}
//这是个额外拷贝的任务
const extra = () =>{
  return src('public/**',{base:'public'})
    .pipe(dest('dist'))
}

const useref = () => {
  return src('temp/*.html',{base:'temp'})//找dist下面的html
  .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({
    collapseWhitespace:true,
    minifyCss:true,
    minifyJs:true
  })))
  .pipe(dest('dist'))
}

//将三个任务组合起来并行执行,这样可以提高构建效率
const compile = parallel(style,script,page)
//上线之前执行的任务
const build = series(
  clean,
  parallel(
  series(compile,useref),
  image,
  font,
  extra
  )
  )
 
const develop = series(compile,serve)
module.exports = {
  clean,
  build,
  develop,
}
  • Gulpfile.js中的一些构建任务是依赖一些模块的,肯定是需要把这些模块作为我们ly-pages这个项目(模块)的依赖安装,这样的话在别的项目中去使用这个模块的时候会自动的去安装依赖,所以需要把依赖的模块在原项目案例当中package.json对应的devDependencies中的开发依赖复制到要构建的ly-pages当中的Dependencies。原项目当中的devdependencies中可以删掉,因为不同的项目里面的生产依赖是不一样的,但是对于自动化构建任务中的开发依赖都是相同的 :

安装ly-pages时,它会自动的把dependencies中的依赖安装下来,并不会去安装里面的开发依赖,开发依赖指的是开发我们这个模块所需要的的东西 ,最终工作环节只会有dependencies里面的东西,所以复制到dependencies里面。

  • 使用命令安装所有依赖:
  $ yarn # or npm i
  • 根据准备工作,清空原始案例项目ly-gulp-demo当中的gulpfile.js和删除node_modules与package.json当中的一些依赖,正常的流程是把ly-pages这个模块发布到npm当中,然后在ly-gulp-demo里面安装这个模块,但是现在开发阶段,ly-pages这个模块没有完成,需要本地调试,最简单的方式是通过link的方式把ly-pages这个模块link到ly-gulp-demo项目的node_modules当中,打开ly-pages的终端:
yarn link
  • 将 ly-pages 模块作为 ly-gulp-demo 的依赖模块,在 ly-gulp-demo 的命令终端执行:
  yarn link ly-pages # or npm link ly-pages

运行后如下图所示:

可以看到,ly-gulp-demo 的目录结构中,会添加一个node_modules的文件夹,其目录结构和 ly-pages 的一模一样。这其实是一个软连接,当 ly-pages 中的内容改变时,ly-gulp-demo 会随着改变.

简单工作流:

在ly-pages中的gulpfile.js文件中,我们会采用的是定义任务,再将任务以模块进行导出的形式。现在,我们导入了公共的 gulp-pages 模块,因此,可以直接导出载入的模块。

  • 在ly-gulp-demo的gulpfile.js文件中,导入载入模块后的命令
  module.exports = require('ly-pages')

运行结果,如下图所示:

在这里插入图片描述

可以看到,此时运行会报错。因为用到的 gulp命令是从bin目录中获取的,而此时是不存在这个目录的,此时可以先手动安装 gulp,使编译成功。

  • 手动安装 gulp 依赖,临时解决 gulp 不识别问题
  $ yarn add gulp --dev # or npm install gulp --save-dev

运行结果,如下图所示:

可以看到,此时运行会报错。因为在 index.js中,使用了配置数据,但是每个项目的配置数据可能都有所不同,考虑到约定大于配置,因此需要将配置数据单独抽象出来,形成配置文件,被 gulpfile.js 文件引入。

  • 在 gulp-demo 创建 pages.config.js 配置文件,书写配置数据(数据省略,可以参考上面的data数据),例如像vue-cli它在工作的时候就会读取项目根目录下的vue.config.js。
  module.exports = {
      data: {
          // 要配置的data
        }
  }
  • 改造ly-pages的入口文件index.js,将原来的data配置数据改为以下代码
  // 返回当前命令行所在的工作目录
  const cwd = process.cwd()
  let config = {
    // default config
  }
  
  // 使用 try ... catch ,防止 pages.config.js 不存在的情况
  try {
    const loadConfig = require(`${cwd}/pages.config.js`)
    config = Object.assign({}, config, loadConfig)
  } catch (e) {}
  
  const page = () => {
    return src('src/*.html', { base: 'src' })
      .pipe(plugins.swig({ data: config.data })) // 此时,data属性名和属性值不同,不可以简写
      .pipe(dest('temp'))
      .pipe(bs.reload({ stream: true }))
  }

在ly-gulp-demo中运行yarn build后出现下图的情况:

可以看到,此时运行会报错。这是因为 根据上面的写法,会在 ly-gulp-demo的node_modules中查找对应的依赖,而 ly-gulp-demo 中并没有

  • 解决找不到 ‘@babel/preset-env’ 依赖的问题,在ly-pages中的index.js文件里的script任务里presets修改为require的形式获取:
  const script = () => {
    return src('src/assets/scripts/*.js', { base: 'src' })
      .pipe(plugins.babel({ presets: [require('@babel/preset-env')] }))
      .pipe(dest('temp'))
      .pipe(bs.reload({ stream: true }))
  }

可以看到,采用了require 导包的方法,因为 require 会从当前文件开始,逐级往上查找对应的依赖包。然后yarn build 运行成功。

封装工作流:抽象路径配置

对于ly-pages里面的index.js里面写死的路径,这些路径在我们使用的项目当中可以看做是一个约定,提供可配置的能力也很重要,在我们使用的项目当中,如果要求src下的目录不叫src,此时就可以通过配置的方式去覆盖,这样的话更灵活一些,在config里面的默认配置里添加:

const {src,dest,parallel,series,watch} = require('gulp')

const del = require('del')
const browserSync = require('browser-sync')
const bs = browserSync.create()//自动创建一个开发服务器


const loadPlugins = require('gulp-load-plugins')//导出的是一个方法
const plugins = loadPlugins() //plugins是一个对象,所有的插件都是这个对象下面的属性
const cwd = process.cwd()//cwd会返回它当前所在的工作目录
let config = {
  // default config
  build:{
    src:'src',
    dist:'dist',
    temp:'temp',
    public:'public',
    paths:{
      styles:'assets/styles/*.scss',
      scripts:'assets/scripts/*.js',
      pages:'*.html',
      image:'assets/images/**',
      fonts:'assets/fonts/**'
    }
  }
}

try {
  const loadConfig = require(`${cwd}/pages.config.js`)
  config = Object.assign({},config,loadConfig)
}catch(e) {

}
//把开发服务器定义到单独的一个任务当中使用
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([
    config.build.paths.fonts,
    config.build.paths.images,
  ],{cwd:config.build.src},bs.reload)
  watch('**',{cwd:config.build.public},bs.reload)
  bs.init({
    notify:false,
    port:3000,
    files:'dist/**',
    server:{
      baseDir:[config.build.temp,config.build.src,config.build.public],
      routes:{
        '/node_modules':'node_modules'
      }
    }
  })
}

const clean = () => {
  return del([config.build.dist,config.build.temp])//返回的是promise,意味着delete完成后,它可以标记clean任务执行完成
}
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))
    .pipe(bs.reload({stream:true}))
}
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))
    .pipe(bs.reload({stream:true}))
}
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))
    .pipe(bs.reload({stream:true}))
}

//字体文件编译
const font = () =>{
  return src(config.build.paths.fonts,{base:config.build.src,cwd:config.build.src})
    .pipe(plugins.imagemin())
    .pipe(dest(config.build.temp))
    .pipe(bs.reload({stream:true}))
}
//图片压缩编译
const image = () =>{
  return src(config.build.paths.images,{base:config.build.src,cwd:config.build.src})
    .pipe(plugins.imagemin())
    .pipe(dest(config.build.dist))
    .pipe(bs.reload({stream:true}))
}
//这是个额外拷贝的任务
const extra = () =>{
  return src('**',{base:config.build.public,cwd:config.build.public})
    .pipe(dest(config.build.dist))
}

const useref = () => {
  return src(config.build.paths.pages,{base:config.build.temp,cwd:config.build.temp})//找dist下面的html
  .pipe(plugins.useref({searchPath:[config.build.temp,'.']}))//
  //html js css
  .pipe(plugins.if(/\.js$/,plugins.uglify()))//是否以js结尾
  .pipe(plugins.if(/\.css$/,plugins.cleanCss()))//是否以css结尾
  .pipe(plugins.if(/\.html$/,plugins.htmlmin({
    collapseWhitespace:true,
    minifyCss:true,
    minifyJs:true
  })))
  .pipe(dest(config.build.dist))
}

//将三个任务组合起来并行执行,这样可以提高构建效率
const compile = parallel(style,script,page)
//上线之前执行的任务
const build = series(
  clean,
  parallel(
  series(compile,useref),
  image,
  font,
  extra
  )
  )
 
const develop = series(compile,serve)
module.exports = {
  clean,
  build,
  develop,
}

在ly-gulp-demo中的pages.config.js文件中添加build的配置路径:

module.exports = {
  build:{
    src:'src',
    dist:'release',
    temp:'.tmp',//temp我们一般会放在.开头的目录
    public:'public',
    paths:{
      styles:'assets/styles/*.scss',
      scripts:'assets/scripts/*.js',
      pages:'*.html',
      image:'assets/images/**',
      fonts:'assets/fonts/**'
    }
  },
     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()
      }
}

封装工作流:包装Gulp CLI:

因为gulpfile.js文件比较冗余,可以直接执行下面命令代替 gulpfile.js 文件

  $ # --cwd 设置当前执行目录
  $ yarn gulp --gulpfile ./node_modules/gulp-pages/lib/index.js --cwd 

上面的命令需要传参,不太易操作,下面采用脚手架的形式,代替上面的操作。

  • 创建 cli 的入口文件,一般存放在 项目根目录/bin目录中,这里命名为 ly-pages.js。

  • 在 ly-pages的项目中的package.json 文件中,添加 cli 入口文件的配置指向,其余配置省略

此时,需要重新将 ly-pages 模块 link到全局。

  • 配置ly-pages.js 入口文件,替代上述的手动输入
#!/usr/bin/env node

process.argv.push('--cwd')
process.argv.push(process.cwd())//指定当前命令的执行工作目录
process.argv.push('--gulpfile')
process.argv.push(require.resolve('..'))//,指向gulpfile的入口文件,resolve是找到这个模块对应的路径

require('gulp/bin/gulp')//集成gulp
  • 在ly-gulp-demo目录命令界面使用命令运行任务
  $ ly-pages clean
  $ ly-pages build
  $ ly-pages develop

运行结果跟未包装时候一致。

发布和使用模块详见前端工程化概述与脚手架工具

使用发布后的模块:

  • 新建项目文件夹,并创建public文件夹、src文件夹、pages.config.js文件等
  $ mkdir project-name
  $ cd project-name

目录结构,如下图所示:

  • 创建并初始化package.json 包管理文件
  $ yarn init --yes # or npm init -y
  • 安装发布的ly-pages模块
  $ yarn add ly-pages --dev # or npm install ly-pages --save-dev
  • 运行 ly-pages 中暴露出的 任务命令
  $ yarn alisone-gulp-pages clean
  $ yarn alisone-gulp-pages build
  $ yarn alisone-gulp-pages develop
  • 在 NPM Scripts中,进行配置,即 package.json 中的 scripts 属性,代码示例如下:
  {
      "scripts": {
          "clean": "alisone-gulp-pages clean",
          "build": "alisone-gulp-pages build",
          "develop": "alisone-gulp-pages develop"
       }
  }
  • 运行命令,进行操作:
  $ yarn clean   # or npm run clean
  $ yarn build   # or npm run build
  $ yarn develop # or npm run develop

FIS

fis的核心特点是高度集成,它把开发过程中常见的构建任务和调试任务都集成在了内部,开发者通过简单的配置文件的方式去配置我们构建过程需要完成的工作,不需要像gulp和grunt一样定义一些任务,fis自己内置了一个配置任务,不像gulp和grunt一样要自定义。

  • 全局安装 或 局部安装 fis3
  $ yarn global add fis3 # or npm install fis3 -g
  • 运行 fis3 中默认的构建任务 release,会将项目中所有需要被构建的目录,存放到一个临时目录中
  $ fis3 release 
  • 指定项目的输出目录为 output
  $ fis3 release -d output

创建 fis-conf.js 配置文件,进行资源定位

  // 利用 fis 资源定位
  
  // match() 的第一个参数,是指选择器
  // 后面的参数,是对于匹配到的文件的配置
  fis.match('*.{js,scss,png}', {
      release: '/assets/$0' // $0 指的是当前文件的原始结构
  })
  • 安装 fis-parser-node-sass 插件模块,用来编译 sass 文件
  $ yarn global add fis-parser-node-sass # or npm install fis-parser-node-sass -g
  • 在 fis-conf.js 配置文件中,配置 sass 的编译并压缩

代码示例如下:

  fis.match('**/*.scss', {
      rExt: '.css',                    // 修改编译后的扩展名
      parser: fis.plugin('node-sass'), // 自动载入编译插件 
      optimizer: fis.plugin('clean-css') // css的压缩插件,内置插件
  })
  • 安装 fis-parser-babel-6.x 插件模块,将 ES6 语法转换为 ES5 语法
  $ yarn global add fis-parser-babel-6.x # or npm install fis-parser-babel-6.x -g
  • 在 fis-conf.js 配置文件中,配置 ES6 转换为 ES5
  fis.match('**/*.js', {
      parser: fis.plugin('babel-6.x'), // 自动载入编译插件 
      optimizer: fis.plugin('uglify')  // js的压缩插件,内置插件
  })
  • 使用 fis3 inspect 命令,查看在转换过程中,有哪些文件被转换
  $ fis3 inspect

以上为fis的基本介绍,更详细的自行百度。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值