对于gulp,我还是想学习一下

lxf2023-12-18 03:00:02

本文正在参加「」

现在构建工具盛行,但是gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp处理,比如单独打包css文件等等。gulp是基于任务流的。类似于jquery,找到一个或者一类文件,对其进行链式操作,更新流上的数据,整一条链条构成一个任务,多个任务就构成web的构建流程。

webpack需要开发者找到入口,并且需要清楚对于不同的资源应该使用什么loader做何种解释和加工。

glup官网

对于gulp,我还是想学习一下

Gulp vs Webpack/其他构建工具

  • Gulp 不具备任何具体功能,完全自主,自定义性强。gulp需要开发者将整个前端流程构建拆分为多个任务,并合理控制所有任务的调用关系。
    • 需要开发者自己实现各种功能
    • 对 Node.js 储备要求高
    • 强调任务的概念,Gulp 本身实际上是一个任务调度工具(tasks runner)
  • Webpack 从模块打包出发,通过插件实现一部分 Web 项目的自动化任务。webpack是基于入口的。webpack会自动递归解析入口所需要的加载的所有资源文件,然后用不同的loader去处理不同的文件,用plugin来扩展webpack的功能。
    • 开箱即用,门槛更低
    • 主要应对 SPA 类应用的模块打包

以往我们使用 Gulp 去实现的常用自动化工作现在都可以使用 Webpack 实现。

Gulp 常见场景

  • 如果只是传统的静态页面开发(多页应用MPA),注重的是页面结构与样式,建议采用 Gulp。

  • 小程序项目中使用 Sass / Less。

  • 再者就是日常的综合事务:文件重命名 / 前后缀。

最佳实践

  • 工具层面没有唯一标准答案

  • 充分掌握 Gulp 与 Webpack,因地制宜

  • SPA 类使用 Webpack

  • MPA 类使用 Gulp

  • 如果只是个别的需求直接使用 npm scripts 配合个别工具就好

    • 例如 只需要校验代码,单独使用 ESLint
    • npm scripts 就是小型 tasks runner
    • start prestart

gulp的任务函数

在gulpfile.js文件中导出的函数都是一个gulp函数。并且每个任务都是异步任务,需要指定结束时机。

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

未指定结束时机,那么就会抛出错误。 对于gulp,我还是想学习一下

串行和并行任务

gulp提供seriesparallel来帮助我们一次性执行多个任务函数。这在构建我们的代码时非常有用,当我们编译两个不相干的任务时,就可以使用parallel来帮助我们编译。比如js和css编译。

执行串行任务

    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.seriesTask = series(task1, task2, task3)

对于gulp,我还是想学习一下

执行并行任务

    exports.parallelTask = parallel(task1, task2, task3)

对于gulp,我还是想学习一下

如何结束异步任务

通过gulp的done回调

exports.fn1 = done => {
  console.log("fn1")
  done() // 可以传入错误对象,来阻止后续任务执行
}

返回一个promise

// 可以返回一个失败的promise对象来阻止后续任务的执行
exports.fn2 = () => {
  console.log("fn2")
  return Promise.resolve()
}

exports.fn2Err = () => {
  console.log("fn2")
  return Promise.reject(new Error("err"))
}

exports.fn3 = async () => {
  await new Promise(resolve => {
    setTimeout(resolve, 1000)
  })
  console.log("fn3")
}

返回一个Stream对象,当stream调用end事件时该任务将结束。

// 返回一个stream对象
exports.fn4 = () => {
  const rs = fs.createReadStream("package.json")
  const ws = fs.createWriteStream("temp.json")
  rs.pipe(ws)
  return rs
}

gulp的工作流程

基于流的构建系统。 对于gulp,我还是想学习一下 下面一个例子是将css压缩并输出。

const fs = require('fs')
const { Transform } = require('stream')

exports.default = () => {
  // 文件读取流
  const readStream = fs.createReadStream('normalize.css')

  // 文件写入流
  const writeStream = fs.createWriteStream('normalize.min.css')

  // 文件转换流
  const transformStream = new Transform({
    // 核心转换过程
    transform: (chunk, encoding, callback) => {
      const input = chunk.toString()
      const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '')
      callback(null, output)
    }
  })

  return readStream
    .pipe(transformStream) // 转换
    .pipe(writeStream) // 写入
}

文件操作

gulp提供了src,destapi来供我们读取和写入文件。并返回Stream对象。

const { src, dest } = require('gulp')
// 压缩css
const cleanCSS = require('gulp-clean-css')
// 重命名文件
const rename = require('gulp-rename')

exports.default = () => {
  return src('src/*.css')
    .pipe(cleanCSS())
    .pipe(rename({ extname: '.min.css' }))
    .pipe(dest('dist'))
} 

gulp构建项目案例

一个案例

处理sass文件

需要安装gulp-sass, sass

// 安装sass,gulp-sass
const sass = require('gulp-sass')(require('sass'));

const style = () => {
  // 需要指定打包的base,来达到输出同样的目录
  return src("src/assets/styles/*.scss", {base: "src"}).pipe(sass()).pipe(dest("dist"))
}

处理js文件

处理js语法,做降级处理。

// babel只是一个转换平台,我们还需要安装对应的插件来时间对应语法的转化功能。
// @babel/core @babel/preset-env
const babel = require("gulp-babel")

const script = () => {
  return src("src/assets/scripts/*.js", {base: "src"}).pipe(babel({presets: ["@babel/preset-env"]})).pipe(dest("dist"))
}

处理html文件

需要更具不同的模板安装对应的插件处理,这里是swig模板。

// gulp-swig处理swig模板语法
const swig = require("gulp-swig")


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 page = () => {
  // 匹配到src下面的所有html文件
  return src("src/**/*.html", {base: "src"}).pipe(swig({data})).pipe(dest("dist"))
}

处理图片字体等文件

注意我们安装时,需要安装指定版本gulp-imagemin, del,因为高版本的包不支持require引入。

const {src, dest, parallel, series} = require("gulp")
// gulp-swig处理swig模板语法
const swig = require("gulp-swig")
// gulp-imagemin处理图片,我们需要安装6.1.0版本,否则模块化不兼容
const imagemin = require("gulp-imagemin")
// 删除上一次打的包 ,需要安装5.1.0版本
const del = require("del")

// 无损压缩
const image = () => {
  return src("src/assets/images/**", {base: "src"}).pipe(imagemin()).pipe(dest("dist"))
}
// 无损压缩
const font = () => {
  return src("src/assets/fonts/**", {base: "src"}).pipe(imagemin()).pipe(dest("dist"))
}

const extra = () => {
  return src("public/**", {base: "public"}).pipe(dest("dist"))
}
// 删除上次打包文件
const clean = () => {
  return del(["dist"])
}
// 并行处理js,css,html文件
const compile = parallel(style, script, page, image, font, extra)

const build = series(clean, compile)

module.exports = {
  build,
  compile
}

热更新开发服务器

我们通过browser-sync来实现。

并且我们可以通过watch来做监听文件变化,然后进行相应的任务重新打包。由于图片等资源我们在开发环境无需处理,可以直接监听其变化,来刷新浏览器,增加构建效率。

const {src, dest, parallel, series, watch} = require("gulp")
// 开发服务器
const browserSync = require("browser-sync")

const bs = browserSync.create()

const server = () => {
  // 监听src下的文件变化
  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,
    // 那些文件改变,可以热更新
    // files: "dist/**",
    server: {
      // 一些静态资源,图片等在开发过程中我们不需要编译打包,所以在src,public中直接获取,提高构建效率
      baseDir: ["dist", "src", "public"],
      // 优先级高于baseDir
      routes: {
        "/node_modules": "node_modules"
      }
    }
  })
}

处理html依赖引入问题和代码压缩

我们可以通过定义构建注释,来加载引入的依赖。通过useref来解决。useref会读取类似于下面这种注释,然后将依赖替换成注释中指定的路径。

 <!-- 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:css assets/styles/main.css -->
//   <link rel="stylesheet" href="assets/styles/main.css">
//   <!-- endbuild -->
const useref = () => {
  return src("temp/*.html", {base: "temp"})
  .pipe(
    plugins.useref({
      searchPath: ["temp", "."]
    })
  )
  // 压缩js,css,html
  .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 {src, dest, parallel, series, watch} = require("gulp")
// 安装sass,gulp-sass
const sass = require('gulp-sass')(require('sass'));
// // babel只是一个转换平台,我们还需要安装对应的插件来时间对应语法的转化功能。
// // @babel/core @babel/preset-env
// const babel = require("gulp-babel")
// // gulp-swig处理swig模板语法
// const swig = require("gulp-swig")
// // gulp-imagemin处理图片,我们需要安装6.1.0版本,否则模块化不兼容
// const imagemin = require("gulp-imagemin")
// 删除上一次打的包 ,需要安装5.1.0版本
const del = require("del")

// gulp-load-plugins该插件将所有gulp相关插件都挂载到该对象上,方便了我们使用
const plugins = require("gulp-load-plugins")()
// 开发服务器
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: 'About',
          link: 'https:///user/2225067267204935/columns'
        },
        {
          name: 'About',
          link: 'https://github.com/zhang-glitch'
        }
      ]
    }
  ],
  pkg: require('./package.json'),
  date: new Date()
}

const style = () => {
  // 需要指定打包的base,来达到输出同样的目录
  return src("src/assets/styles/*.scss", {base: "src"}).pipe(sass()).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 = () => {
  // 匹配到src下面的所有html文件 
  // cache: false防止模板缓存导致页面不能及时更新
  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 clean = () => {
  return del(["dist", "temp"])
}

const server = () => {
  // 监听src下的文件变化
  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,
    // 那些文件改变,可以热更新
    // files: "dist/**",
    server: {
      // 一些静态资源,图片等在开发过程中我们不需要编译打包,所以在src,public中直接获取,提高构建效率
      baseDir: ["temp", "src", "public"],
      // 优先级高于baseDir
      routes: {
        "/node_modules": "node_modules"
      }
    }
  })
}

// 通过构建注释,来加载引入的依赖
// <!-- build:css assets/styles/main.css -->
//   <link rel="stylesheet" href="assets/styles/main.css">
//   <!-- endbuild -->
const useref = () => {
  return src("temp/*.html", {base: "temp"})
  .pipe(
    plugins.useref({
      searchPath: ["temp", "."]
    })
  )
  // 压缩js,css,html
  .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"))
}
// 并行处理js,css,html文件
const compile = parallel(style, script, page)
// 构建,【删除,编译html,js,css,处理依赖引入,压缩代码】【其他资源处理】
const build = series(clean, parallel(series(compile, useref), image, font, extra))

const dev = series(clean, compile, server)

module.exports = {
  build,
  compile,
  dev,
  useref
}

该项目的演示在这里,如有需要自取

为了更好地以后使用,通过gulp封装了一个构建工具,已发布,hm-mpa-build

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!