Gulp基础入门

852 阅读7分钟

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

Gulp是一个工具包,可以帮你自动化和增加你的工作流,

Gulp可以认为是一个基于任务的自动化构建工具

Gulp vs Webpack

gulp的核心理念是task runner

  • 可以定义自己的一系列任务,等待任务被执行
  • 基于文件Stream的构建流
  • 我们可以使用gulp的插件体系来完成某些任务

webpack的核心理念是module bundler

  • webpack是一个模块化的打包工具
  • 可以使用各种各样的loader来加载不同的模块
  • 可以使用各种各样的插件在webpack打包的生命周期完成其他的任务

总结:

  • gulp相对于webpack思想更加的简单、易用,更适合编写一些自动化的任务

  • 对于大型项目(Vue、React、Angular)并不会使用gulp来构建,比如默认gulp是不支持模块化的

  • 每个gulp任务都是一个异步的JavaScript函数

    • 此函数可以接受一个callback作为参数,调用callback函数那么任务会结束
    • 或者是一个返回stream、promise、event emitter、child process或observable类型的函数

基本使用

安装

# 全局安装
npm install gulp -g

# 局部安装
npm install gulp

使用

单个任务
  1. 在项目根目录下定义一个名为gulpFile.js的文件,我们可以在这个文件中定义我们需要执行的任务

gulpV4版本之前

// gulp也是运行于node环境下
const gulp = require('gulp')

// 定义一个任务 --> 这是Gulp V4.x版本之前定义任务的方式
gulp.task('foo', cb => {
  console.log('foo task running')
  // 每个gulp任务都是一个异步的JavaScript函数
  // 所以在执行的时候,会传入callback作为参数
  // 在调用callback回调后,gulp才会认为当前任务执行完毕
  cb()
})

Gulpv4版本开始

// Gulp V4.x 版本开始定义任务的方式
const foo = cb => {
  console.log('foo task running')
  cb()
}

module.exports = {
  foo
}

// 这是一个特殊的task,这是gulp的默认task
// 在执行的时候,不需要传递task的名称
// 直接调用 gulp --> 就会自动执行该默认任务
module.exports.default = cb => {
  console.log('default task running')
  cb
}
  1. 执行
# npx gulp <任务名>
npx gulp foo
任务组合

gulp提供了两个强大的组合方法:

  • series():串行任务组合 --- 一个任务执行完再执行另一个任务
  • parallel():并行任务组合 --- 所有任务一起执行
  • 他们都可以接受任意数量的任务函数或者已经组合的操作;
const { series } = require('gulp')

const foo = cb => {
  console.log('foo')
  cb()
}

const bar = cb => {
  console.log('bar')
  cb()
}

const baz = cb => {
  console.log('baz')
  cb()
}

// 此时seriesTask就被称之为公开任务(Public tasks)
// foo,bar,baz 这些没有被实际导出的task就被称之为私有任务(Private tasks)
exports.seriesTask = series(foo, bar, baz)
const { parallel } = require('gulp')

const foo = cb => {
  console.log('foo')
  cb()
}

const bar = cb => {
  console.log('bar')
  cb()
}

const baz = cb => {
  console.log('baz')
  cb()
}

exports.parallelTask = parallel(foo, bar, baz)
读写文件

gulp 暴露了 src() 和 dest() 方法用于处理计算机上存放的文件

  1. src() 接受参数,并从文件系统中读取文件然后生成一个Node流(Stream),它将所有匹配的文件读取到内存中并通过流(Stream)进行处理; 由 src() 产生的流(stream)应当从任务(task函数)中返回并发出异步完成的信号

  2. dest() 接受一个输出目录作为参数,并且它还会产生一个 Node流(stream),通过该流将内容输出到文件中

  3. pipe方法接受一个 转换流(Transform streams)或可写流(Writable streams); 拿到数据之后可以对数据进行处理,再次传递给下一个转换流或者可写, 如果在这个过程中,我们希望对文件进行某些处理,可以使用社区给我们提供的插件

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

const jsTask = () => {
 return src('./main.js')
  .pipe(babel({ presets: [ '@babel/preset-env' ] }))
  .pipe(terser({ mangle: { toplevel: true } }))
  .pipe(dest('./dist/'))
}


module.exports = {
  jsTask
}
glob文件匹配

src() 方法接受一个 glob 字符串或由多个 glob 字符串组成的数组作为参数,用于确定哪些文件需要被操作

  • glob 或 glob 数组必须至少匹配到一个匹配项,否则 src() 将报错

匹配规则如下:

  • (一个星号*):在一个字符串中,匹配任意数量的字符,包括零个匹配
  • (两个星号**):在多个字符串匹配中匹配任意数量的字符串,通常用在匹配目录下的文件
  • (取反!)
  • 由于 glob 匹配时是按照每个 glob 在数组中的位置依次进行匹配操作的;
  • 所以 glob 数组中的取反(negative)glob 必须跟在一个非取反(non-negative)的 glob 后面;
  • 第一个 glob 匹配到一组匹配项,然后后面的取反 glob 删除这些匹配项中的一部分
  • 例如[ 'script/**/*.js', '!script/vendor' ]
const { src, dest, watch } = require('gulp')
const terser = require('gulp-terser')
const babel = require('gulp-babel')

const jsTask = () => {
 return src('./js/**/*.js')
  .pipe(babel({ presets: [ '@babel/preset-env' ] }))
  .pipe(terser({ mangle: { toplevel: true } }))
  .pipe(dest('./dist/'))
}

// 开启对某些文件的监听操作,当对应文件中的内容发生改变的时候执行对应的task
watch('./js/**/*.js', jsTask)

module.exports = {
  jsTask
}

综合案例

打包压缩html文件

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

const htmlmin = require('gulp-htmlmin')

const htmlTask = () => {
  return src('./index.html')
    // 转换流
    .pipe(htmlmin({
      // 压缩html中的空格
      collapseWhitespace: true
    }))
    // dest函数返回的是一个输出流,所以也需要使用pipe进行接收
    .pipe(dest('./dist'))
}

module.exports = {
  htmlTask
}

处理JS

const terser = require('gulp-terser')
const babel = require('gulp-babel')

const jsTask = () => {
  // src的第二个参数为配置项
  // base选项代表的意思是以src为基准目录保留对应的对应的文件结构
  // 也就是输出后的js文件的文件目录结构和输出前的js文件的目录结构保持一致
  return src('./src/js/**/*.js', { base: './src' })
  .pipe(babel({ presets: ['@babel/preset-env'] }))
  .pipe(terser({ mangle: { toplevel: true } }))
  .pipe(dest('./dist'))
}

处理LESS

const less = require('gulp-less') // less会作为依赖一并下载
const postcss = require('gulp-postcss') // 这个插件不会附带安装postcss 需要自行手动安装
const postcssPresetEnv = require('postcss-preset-env')

const lessTask = () => {
  return src('./src/css/**/*.less', {base: './src'})
  .pipe(less())
  .pipe(postcss([postcssPresetEnv]))
  .pipe(dest('./dist'))
}

html资源注入

index.html

在gulp中,需要手动声明注入js和css的位置

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Gulp Demo</title>
  <!-- inject:css -->
  <!-- endinject -->
</head>
<body>
  <!-- inject:js -->
  <!-- endinject -->
</body>
</html>

gulpFile.js

const inject = require('gulp-inject')

const injectHtml = () => {
  return src('./dist/*.html')
  // 默认注入路径是以项目根路径为基准路径的绝对路径 ---> /dist/index.js
  // 设置relative为true之后,就可以将路径转换为相对路径 ---> index.js
  // 以方便我们在本地进行调试
  .pipe(inject(src(['./dist/js/**/*.js', './dist/css/**/*.css']), {relative: true}))
  .pipe(dest('./dist/'))
}

开启本地服务

const browserSync = require('browser-sync')

// server需要在task之外就已经被创建
const bs = browserSync.create()
const serve = () => {
  // gulp很难进行HMR,这里模拟监听文件的改变进行实时刷新
  watch('./src/index.html', series(htmlTask, injectHtml))
  watch('./src/js/**/*.js', series(jsTask, injectHtml))
  watch('./src/css/**/*.less', series(lessTask, injectHtml))

  // 开启serve
  bs.init({
    port: 3000, // 端口
    open: false, // 是否主动打开浏览器 --- 默认值为true
    // 监听那些文件改变刷新浏览器 --> 这里需要监听dist文件夹下文件的改变
    // 直接监听src目录下改变的时候,会刷新浏览器,但是dist文件夹下内容没有发生任何的改变,所以并不会有任何的效果
    files: './src/*',
    server: { // 服务器的配置项
      baseDir: './dist' // => 以哪个作为项目的基准根目录来开启本地服务 --- 必填项
    }
  })
}

清除上次打包后的文件

const del = require('del')

const clean = () => del(['dist'])

区分build和serve

gulpFile.js

const buildTask = series(clean, parallel(htmlTask, jsTask, lessTask), injectHtml)
const serveTask = series(buildTask, serve)

module.exports = {
  buildTask,
  serveTask
}

package.json

"scripts": {
    "serve": "gulp serveTask",
    "build": "gulp buildTask"
  }

全部的代码

gulpFile.js

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

const htmlmin = require('gulp-htmlmin')

const terser = require('gulp-terser')
const babel = require('gulp-babel')

const less = require('gulp-less')
const postcss = require('gulp-postcss')
const postcssPresetEnv = require('postcss-preset-env')

const inject = require('gulp-inject')

const browserSync = require('browser-sync')

const del = require('del')

const htmlTask = () => {
  return src('./src/index.html')
    .pipe(htmlmin({
      collapseWhitespace: true
    }))
    .pipe(dest('./dist'))
}

const jsTask = () => {
  return src('./src/js/**/*.js', { base: './src' })
  .pipe(babel({ presets: ['@babel/preset-env'] }))
  .pipe(terser({ mangle: { toplevel: true } }))
  .pipe(dest('./dist'))
}

const lessTask = () => {
  return src('./src/css/**/*.less', {base: './src'})
  .pipe(less())
  .pipe(postcss([postcssPresetEnv]))
  .pipe(dest('./dist'))
}

const injectHtml = () => {
  return src('./dist/*.html')
  .pipe(inject(src(['./dist/js/**/*.js', './dist/css/**/*.css']), {relative: true}))
  .pipe(dest('./dist/'))
}

const bs = browserSync.create()
const serve = () => {
  watch('./src/index.html', series(htmlTask, injectHtml))
  watch('./src/js/**/*.js', series(jsTask, injectHtml))
  watch('./src/css/**/*.less', series(lessTask, injectHtml))

  bs.init({
    port: 3000,
    open: true,
    files: './src/*',
    server: {
      baseDir: './dist'
    }
  })
}

const clean = () => del(['dist'])

const buildTask = series(clean, parallel(htmlTask, jsTask, lessTask), injectHtml)
const serveTask = series(buildTask, serve)

module.exports = {
  buildTask,
  serveTask
}