Gulp4(二)——手撕一个gulp构建案例

1,006 阅读10分钟

目录

  • Gulp案例
  • 自动化构建诉求
    • 样式编译
      • gulp-sass
      • gulp-less
    • 脚本编译
      • gulp-babel
    • 页面模板编译
      • gulp-swig
    • 图片和文字转换
      • gulp-imagemin
    • 其他原本文件输出
    • 清除文件
    • 自动加载插件
    • 热更新开发服务器
    • useref文件引用处理
    • html/css/js文件压缩
    • 对所有的插件和目录进行调整

Gulp案例

模板下载 gulp-pro-demo

自动化构建诉求

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

样式编译

关于样式这里的操作我们需要将所有的scssless文件编译成css文件,所以下面进行具体操作。

gulp-sass

sass转化成css

  1. 下载模块npm install gulp-sass
  2. gulpfile.js里面写
const { src, dest } = require('gulp')
// 引入sass模块
const sass = require('gulp-sass')

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

module.exports = {
  style
}
  1. 在命令行输入gulp style可以看到样式已经编译成css文件
gulp-less

less转化成css

  1. 下载模块npm install gulp-less
  2. 进行私有前缀匹配,下载less插件npm install less-plugin-autoprefix
  3. gulpfile.js里面写
const { src, dest} = require('gulp')
// 引入less模块
const less = require('gulp-less')
// 引入less插件,私有前缀兼容
const LessAutoprefix = require('less-plugin-autoprefix')
const autoprefix = new LessAutoprefix({browsers: ["last 2 versions"]});

const lessStyle = () => {
  return src('src/assets/styles/*.less', { base: 'src'})
  // 进行less向css转换,并且引用插件私有前缀适配
  .pipe(less({
    plugins: [autoprefix]
  }))
  // 输出到dist文件夹
  .pipe(dest('dist'))
}

module.exports = {
  lessStyle
}
  1. 命令行写gulp lessStyle可以看到文件进行了编译
/* hello.less */
@bg : #000;
@fontColor: #fff;

.main{
  background-color: @bg;
  color: @fontColor;
  transform: translateX(30px);
}


/* hello.css */
.main {
  background-color: #000;
  color: #fff;
  -webkit-transform: translateX(30px);
          transform: translateX(30px);
}

PS: less里面有很多别的插件可以参考:Pre-Loaded Plugins

脚本编译

我们还需要将ES6的语法进行编译

gulp-babel
  1. 下载模块npm install gulp-babel
  2. 使用还需要下载插件npm install @babel/core @babel/preset-env
  3. gulpfile.js里面写
const { src, dest} = require('gulp')
// 引入babel模块
const babel = require('gulp-babel')

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

module.exports = {
  script
}
  1. 命令行写gulp script可以看到文件进行了编译
/* 编译前main.js */
// TODO: site logics

$(($) => {
  const $body = $('html, body')

  $('#scroll_top').on('click', () => {
    $body.animate({ scrollTop: 0 }, 600)
    return false
  })
})



/* 编译后main.js */
"use strict";

// TODO: site logics
$(function ($) {
  var $body = $('html, body');
  $('#scroll_top').on('click', function () {
    $body.animate({
      scrollTop: 0
    }, 600);
    return false;
  });
});

页面模板编译

gulp-swig
  1. 下载模块npm install gulp-swig
  2. gulpfile.js里面写
const { src, dest} = require('gulp')
// 引入swig模块
const swig = require('gulp-swig')

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

module.exports = {
  page
}
  1. 命令行写gulp page可以看到文件进行了编译
  2. 可以添加固定的参数,也可以加一个json文件去写项目长用常数参数
const data = {
    a: 1,
    b: 2
}

const page = () => {
  return src('src/**/*.html', { base: 'src' })
    // 通过参数传递进去
    .pipe(swig({data: data}))
    .pipe(dest('dist'))
}

图片和文字转换

gulp-imagemin

这个插件可以压缩图片,字体文件如果是svg也可以进行压缩

  1. 下载模块npm i gulp-imagemin --save-dev

因为这个插件里面有用到c++二进制流的编译,所以如果上面这样安装不成功或者安装成功但是使用的时候显示缺少默认插件的时候,就这样做:

nrm use taobao

npm install -g cnpm

cnpm install gulp-imagemin --save-dev

等同于

npm install -g cnpm --registry=https://registry.npm.taobao.org

cnpm install gulp-imagemin --save-dev

  1. gulpfile.js里面写
const { src, dest} = 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
}
  1. 命令行写gulp image可以看到文件进行了压缩,图片压缩26.5%
gulp image
# Using gulpfile E:\professer\Gulp\gulpfile.js
# Starting 'image'...
# gulp-imagemin: Minified 2 images (saved 22.8 kB - 26.5%)
# Finished 'image' after 985 ms
  1. 命令行写gulp font可以看到文字的svg进行了压缩,其他的不能被压缩的文件原封不动被输出
gulp font
# Using gulpfile E:\professer\Gulp\gulpfile.js
# Starting 'font'...
# gulp-imagemin: Minified 1 image (saved 679 B - 5.9%)
# Finished 'font' after 503 ms

其他原本文件输出

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

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

清除文件

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

  1. 下载模块npm i del --save-dev

  2. gulpfile.js里面写

const { src, dest, parallel, series } = require('gulp')
// 引入清除文件模块
const del = require('del')

const clean = () => {
  return del(['dist'])
}

module.exports = {
  clean
}
  1. 命令行写gulp clean可以把对应文件进行删除

自动加载插件

上面引用了那么多的模块,那么需要安装一个自动加载插件的模块,可以减少很多代码

  1. 下载模块npm i gulp-load-plugins --save-dev
  2. gulpfile.js里面写
const { src, dest, parallel, series } = require('gulp')
// 引入加载文件模块
const loadPlugins = require('gulp-load-plugins')

// 调用方法,也可以引用的时候直接调用
// const plugins = require('gulp-load-plugins')()
const plugins = loadPlugins()

// 输出结构可以看到
console.log(plugins)
/* 
默认匹配gulp-后面所有的插件
如果有gulp-clean-css的后面采用驼峰命名为键
这些模块在使用的时候必须都安装好
{
  babel: [Getter],
  cleanCss: [Getter],
  imagemin: [Getter],
  less: [Getter],
  rename: [Getter],
  sass: [Getter],
  swig: [Getter]
}
*/

// 不是gulp开头的要单独安装,或者修改loadPlugins的匹配规则
const LessAutoprefix = require('less-plugin-autoprefix')
const autoprefix = new LessAutoprefix({browsers: ["last 2 versions"]});

// 创建sass任务
const style = () => {
  return src('src/assets/styles/*.scss', { base: 'src'})
    // 之后使用的时候就用plugins点出来即可
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('dist'))
}

// 创建less任务
const lessStyle = () => {
  return src('src/assets/styles/*.less', { base: 'src'})
  .pipe(plugins.less({
    plugins: [autoprefix]
  }))
  .pipe(dest('dist'))
}


// 创建babel任务
const script = () => {
  ...
}

// 创建模板引擎任务
const page = () => {
  ...
}

// 图片压缩任务
const image = () => {
  ...
}

// 图片压缩任务
const font = () => {
 ...
}

热更新开发服务器browser-sync

当我们修改的时候,这个插件可以让我们直接看到浏览器刷新

  1. 下载模块npm install browser-sync --save-dev
  2. gulpfile.js里面写
const { src, dest, parallel, series } = require('gulp')
// 引入开发服务器模块
const browserSync = require('browser-sync')

// 使用create方法会自动创建一个开发服务器
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
}
  1. 使用命令行执行gulp serve,可以看到浏览器自动执行,而且修改dist下面的文件,会自动热更新。
  2. 是监听dist文件,但是我们需要在开发的目录下面,修改之后自动编译文件再更新到浏览器。需要用到gulp的方法watch
  3. gulpfile.js中这样写
const { src, dest, parallel, series, watch } = require('gulp')

const browserSync = require('browser-sync')
const bs = browserSync.create()

const serve = () => {
  // 监听文件变化并执行对应任务
  watch('src/assets/styles/*.scss', style)
  watch('src/assets/styles/*.less', lessStyle)
  watch('src/assets/scripts/*.js', script)
  watch('src/**/*.html', page)
  
  // 开发阶段不需要每一次修改都压缩文件,这些只是修改的时候重新加载即可
  watch([
    'src/assets/images/**',
    'src/assets/fonts/**',
    'public/**'
  ], bs.reload)
  
  
  bs.init({
    notify: false,
    port: 2080,
    files: 'dist/*',
    server: {
      // 网站根目录,多个的时候写成数组,如果路径找不到会依此去路径中寻找
      baseDir: ['dist', 'src', 'public'],
      routes: {
        '/node_modules': 'node_modules'
      }
    }
  })
}

当然更多的是这么用的,在任务后面手动调用更新,就可不用写init里面的files

// 创建sass任务
const style = () => {
  return src('src/assets/styles/*.scss', { base: 'src'})
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('dist'))
    // 以流的方式往浏览器推,每次任务执行完,都自动reload一下
    .pipe(bs.reload({ stream: true }))
}

PS: 这里要说一下gulp-swig,因为模板缓存机制可能会导致无法热更新,需要进行配置

const page = () => {
  return src('src/**/*.html', { base: 'src' })
    //swig因为模板缓存的关系无法热更新,所以需要默认设置里面关闭缓存
    .pipe(plugins.swig({defaults: { cache: false }}))
    .pipe(dest('dist'))
    .pipe(bs.reload({ stream: true }))
}

useref文件引用处理

如果项目中有引入dist里面没有的文件,上线会有问题,所以需要进行处理

  1. 下载模块npm i gulp-useref --save-dev
  2. gulpfile.js里面写
const { src, dest } = require('gulp')
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()

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

module.exports = {
  useref
}
  1. 命令行写gulp useref可以把对应注释文件进行了打包处理。并且在release文件夹中出现了vender.jsvender.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 -->

<!-- build:js assets/scripts/vendor.js -->
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script src="/node_modules/popper.js/dist/umd/popper.js"></script>
<script src="/node_modules/bootstrap/dist/js/bootstrap.js"></script>
<!-- endbuild -->

<!-- build:js assets/scripts/main.js -->
<script src="assets/scripts/main.js"></script>
<!-- endbuild -->

变成了

<link rel="stylesheet" href="assets/styles/vendor.css">
<link rel="stylesheet" href="assets/styles/main.css">
<script src="assets/scripts/vendor.js"></script>
<script src="assets/scripts/main.js"></script>

但是这些都是未压缩过的文件,还需要进行压缩操作

html/css/js文件压缩

上面生成的文件都是未压缩过的,所以下面要进行压缩

  1. 安装压缩插件压缩html/css/js 文件,npm i gulp-htmlmin gulp-uglify gulp-clean-css --save-dev
  2. 为了进行区分文件的不同,还需要安装npm i gulp-if --save-dev
  3. gulpfile.js里面写
const { src, dest } = require('gulp')
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()

const useref = () => {
  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'))
}

module.exports = {
  useref
}
  1. 命令行写gulp useref可以把所有的文件都压缩完成

对所有的插件和目录进行调整

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

下面是完整的代码

// gulpfile.js
// src 输入、dest 输出、parallel 并行、series 串行、watch 监听
const { src, dest, parallel, series, watch } = require('gulp')

// 引入清除文件模块
const del = require('del')
// if有less引入兼容适配
const LessAutoprefix = require('less-plugin-autoprefix')
// 引入浏览器监听服务模块
const browserSync = require('browser-sync')
// 引入gulp插件管理模块
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()

// 使用create方法会自动创建一个开发服务器
const bs = browserSync.create()
const autoprefix = new LessAutoprefix({browsers: ["last 2 versions"]});

// 创建清除文件任务
const clean = () => {
  // 每次执行的时候,先把之前的dist目录删除,再删除临时目录temp
  return del(['dist', 'temp'])
}

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

// 创建less任务
const lessStyle = () => {
  return src('src/assets/styles/*.less', { base: 'src'})
  // 进行sass向css转换,并且指定的样式是完全展开
  // (如果不设置完全展开,那么默认css样式的右花括号不折行)
  .pipe(plugins.less({
    plugins: [autoprefix]
  }))
  // 输出到temp临时文件夹
  .pipe(dest('temp'))
  // 以流的方式往浏览器推
  .pipe(bs.reload({ stream: true }))
}


// 创建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
  return src('src/**/*.html', { base: 'src' })
    //swig因为模板缓存的关系无法热更新,所以需要默认设置里面关闭缓存
    .pipe(plugins.swig({defaults: { cache: false }}))
    // 不需要进行临时文件夹中操作
    .pipe(dest('temp'))
    // 以流的方式往浏览器推
    .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: 'pubilc' })
    .pipe(dest('dist'))
}

// 创建服务任务
const serve = () => {
  // 监听文件变化并执行对应任务,写这个就不用写bs.reload()了
  watch('src/assets/styles/*.scss', style)
  watch('src/assets/styles/*.less', lessStyle)
  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,
    // 端口
    port: 2080,
    // 是否会自动打开浏览器:false是关闭
    // open: false,
    // 启动过后监听的文件,如果有修改就主动刷新,有reload就不需要这个files配置
    // files: 'temp/*',
    // 核心配置
    server: {
      // 网站根目录,多个的时候写成数组,如果路径找不到会依此去路径中寻找
      // 文件先从temp中寻找
      baseDir: ['temp', 'src', 'public'],
      // 优先于baseDir,会先匹配这个配置,没有就会去baseBir中获取
      routes: {
        '/node_modules': 'node_modules'
      }
    }
  })
}

// 根据文件注释打包外联文件并且产出代码进行压缩任务
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
     })))
    // 不要和dist一个目录,边读边写会有问题
    .pipe(dest('dist'))
}

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

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

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

package.json中注册一下命令

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

在命令行中直接运行npm run buildnpm run develop即可。然后在.gitignore中把disttemp目录加上提交忽略。