vlambda博客
学习文章列表

对比webpack,你更应该先掌握gulp【10分钟教你彻底掌握gulp】

前言

可能很多人对于gulp都相对陌生,特别是vuereact出现以后,渐渐淡出了做业务前端人员的视野,14到16年的时候应该是它最巅峰的时候,真正的是出道即巅峰,取代了当时最火的grunt成为了前端构建的主流工具,就连某度都忍不住来瓜分一下流量,出了个fis(不过按照烂尾的惯例来看,基本会属于后继无人的状态,所以没有真正去用在生产项目中过),而且当时webpack虽然已经出现,但完全不能跟gulp抗衡,直到vuereactspa项目出现,才让webpack取而代之,gulp也逐渐退出幕前,转战幕后,去做了它更擅长的事情:前端开发流程规范管理。

现在我们在各种组件库,像antdelement-uivant等比较人们的组件库,或者其他一些前端工程中都能看到它的身影,只不过它不再介入到业务的实际生产开发中了,所以对业务开发人员来说是不太能感知到它的存在了。

gulp和webpack的区别

首先,可能很多人面试过程中都会被问到这个问题。我说一说自己的理解:

gulp webpack
强调的是规范前端开发的流程 是一个前端模块化方案
是一个基于流的自动化构建工具,不包括模块化的功能,通过配置一系列的task,例如文件压缩合并、雪碧图、启动server、版本控制等,然后定义执行顺序来让gulp执行task,从而构建前端项目的流程 是一个自动化模块打包工具,把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,划分成不同的模块,需要哪个加载哪个,实现按需加载的功能,入口引入的更多是js文件

webpack刚面世的时候,webpackgulp中也有一个插件(gulp-webpack)作为使其可以作为gulp️一个子任务来执行。只不过当时还是JQuery的时代,功能基本重复,真正使用webpack的还是很少,所以reactspa框架的出现让webpack迅速蹿红。

gulp的核心api

task, series, parallel, src, pipe, dest, on, watch

  • task: 创建一个任务
  • series:顺序执行多个任务
  • prallel:并行执行多个任务
  • src:读取数据源转换成stream
  • pipe:管道-可以在中间对数据流进行处理
  • dest:输出数据流到目标路径
  • on:事件监听
  • watch:数据源监听

这些api在demo中都有用一个例子串起来讲解使用

其他的基本很少会用到了,这里就不多复述,网上的很多文章,还有官方的api都有详细的,但在实际的开发中我基本很少用到,可能是使用的场景过于简单吧

本文就用一个实际的例子把这几个api全部串联起来,我将实现一个这样的功能:

对比webpack,你更应该先掌握gulp【10分钟教你彻底掌握gulp】
流程管理

全局安装gulp

$ npm i gulp -g

项目根目录新建gulpfile.js文件

文件头引入模块

// gulpfile.js
const gulp = require("gulp");
/**
 * 合并文件插件
 * gulp的插件很多,有4000多个,足够满足大家日常的各种需求,而且插件写起来也超级简单
 */

const concat = require("gulp-concat");
const through2 = require("through2");

创建合并文件任务

新建合并任务,读取20201108目录下所有txt文件,合并为20201108.txt文件并存储在demo文件夹下

// task 为创建gulp子任务
gulp.task('concat', () => {
  return gulp.src('./20201108/*.txt'// src: 读取文件转化为可读流,参数可以是文件通配符匹配
       .pipe(gulpConcat('20201108.txt')) // pipe:管道,把gulp的执行步骤一步步串联起来,也是gulp的核心
       .pipe(dest('./demo/')) // dest:存放文件
    .on('end', () => { // 事件监听
    console.log('concat: 文件合并完成');
    })
})

创建文件去除空行任务

因为是需要顺序执行子任务,所以用的series,如果是需要并行执行的话用parallel

代码中的through2主要是用来做文件流转换过滤,写gulp插件必备,下一节会大概的介绍一下

gulp.task('format', gulp.series('concat', () => {
  return gulp.src('./demo/20201108.txt')
    .pipe(through2.obj(function (file, encoding, cb// through2:文件流转换,写gulp插件必备,下面会大概的介绍一下
    let contents = file.contents.toString();
    contents = contents
     .replace(/(\n[\s\t]*\r*\n)/g"\n")
     .replace(/^[\n\r\n\t]*|[\n\r\n\t]*$/g""); // 去除空行
    let lines = contents.split(/\n/g);
    totalLine = lines.length;
    contents = lines.join("\n");
    file.contents = Buffer.from(contents);
    this.push(file);
    cb();
   }))
   .pipe(dest('./demo/'))
   .on('end', () => {
    console.log('format: 去除空行完成');
   })
}))

创建监听任务

20201108文件夹下的文件有写入操作时,去执行format任务,format任务又依赖concat任务执行

gulp.task('watch', () => {
  // 因为是需要顺序执行子任务,所以用的concat,如果是需要并行执行的话用parallel
  gulp.watch('./20201108/*.txt', gulp.series('format', (cb) => {
  cb();
  })).on('change', () => { // 更多事件监听可以查看官方文档
  console.log('watch: 文件被改变');
 })
})

在项目目录下执行

以上几步的代码合并到一个gulpfile.js文件中即可运行

# 监控20201108文件夹下所有文件变化,则执行format子任务
$ gulp watch

下图为命令行中输入日志

对比webpack,你更应该先掌握gulp【10分钟教你彻底掌握gulp】
图片

看了上面的demo可能大家会对through2比较好奇吧,接下来会大概介绍一下

gulp插件机制

我们先提一提gulp的机制,gulp内部的实现很简单,用了三个sdk实现undertakervinyl-fs, glob-watcher

  • undertaker: 主要用来实现gulp的子任务流程管理

  • vinyl-fs: .src 接口可以匹配一个文件通配符,将匹配到的文件转为Vinyl Stream(流),gulp理念就是万物皆可流

  • glob-watcher: 也就是去实现gulp.watch功能,监控文件流变化

核心就是把文件转换成Stream流,然后对Stream进行操作。

所以gulp采用pipe(管道)的概念,意味着顺着管道流淌,然后我们对于gulp的插件,也很好理解了,就是在管道中间有个过滤站,对流进行过滤处理,这就用到了上面提到的through2,这个插件主要的作用也是对流文件进行处理,类似的插件还有map-stream等,不过gulp的主流的插件都是基于through2编写的.

例如上面的例子(文件去除空行任务),单独封装一下,使用的时候就是一个简单的插件

// gulp-file-format.js

module.exports = () => {
  return through2.obj(function (file, encoding, cb{
 let contents = file.contents.toString();
 contents = contents
  .replace(/(\n[\s\t]*\r*\n)/g"\n")
  .replace(/^[\n\r\n\t]*|[\n\r\n\t]*$/g""); // 去除空行
 let lines = contents.split(/\n/g);
 totalLine = lines.length;
 contents = lines.join("\n");
 file.contents = Buffer.from(contents);
 this.push(file);
 cb();
  })
}

替换文件去除空行任务

const gulpFormact = require('gulp-file-format.js');

gulp.task('format', gulp.series('concat', () => {
  return gulp.src('./demo/20201108.txt')
    .pipe(gulpFormact())
    .pipe(dest('./demo/'))
    .on('end', () => {
    console.log('format: 去除空行完成');
    })
}))

这就是一个很简单的gulp插件了,是不是很简单,比webpack的插件简单多了

下面讲一个日常中对于重复工作提效写的一个脚本,讲讲思路,让大家对gulp的使用场景有个更深的理解。

实际应用案例思路拆解-支付中间页改版后数据统计

由于实际的代码涉及到一些敏感数据,所以这个段落只是讲一下解决这个实际问题的思路拆解,怎么去用gulp完成想要的结果,不贴详细的代码了。

例如,作者最近做了一个支付中间页的改版

我需要统计从这个支付中间页转化的用户产生了多少收入,人工流程如下:

对比webpack,你更应该先掌握gulp【10分钟教你彻底掌握gulp】
统计流程

把以上几个步骤拆解成gulp的任务,用gulp的任务机制管理起来,每一个任务可以单独执行,又可以统一执行

  1. export:下载用户uid
// 导出uid表
gulp.task('export', () => {})
  1. concat:合并文件并去重
// 对excel文件进行合并去重
gulp.task('cocat', () => {})
  1. money:循环uid,远程请求接口,拿到支付金额
// 获取每一个uid的支付金额
gulp.task('money', () => {})
  1. total: 汇总数据,生成汇总excel表格并输出
// 数据汇总
gulp.task('total', gulp.series('export''concat''money', (cb) => {
 //...
}))
  1. 执行命令
$ gulp total

以上任务都可以独立执行,也可以合并执行

更复杂的应用场景-转转sdk生成命令工具

更复杂的应用场景可以查看我们之前产出的一套sdk命令生成工具:commander-tools,现已在github开源,在转转支撑团队的维护下功能越来强大,主要实现以下命令:

{
  "scripts": {
    "lint""commander-tools run lint"// 校验
    "fix""commander-tools run lint --fix"// 修复
    "staged""commander-tools run lint --staged",
    "staged-fix""commander-tools run lint --staged --fix",
    "dev""commander-tools run dev"// 启动本地调试服务
    "compile""commander-tools run compile"// 编译
    "dist""commander-tools run dist"// 外链打包
    "analyz""commander-tools run dist --analyz"// 代码分析
    "build""commander-tools run build",
    "pub""commander-tools run pub"// 发布正式版
    "pub-beta""commander-tools run pub-beta"// 发布beta版本
    "unpub""commander-tools run unpub"// 卸载版本
    "doc""commander-tools run doc"// 预览文档
    "build-doc""commander-tools run build-doc"// 生成文档
    "doc-upload""commander-tools run doc-upload" // 文档上传ftp
  }
}

例如:一个上传注释文档的功能

$ npm run doc-upload
/**
* 上传文档
**/

const chalk = require('chalk')
const ftp = require('vinyl-ftp')
gulp.task('doc-upload', gulp.series('build-doc', done => {
console.log(chalk.green('running doc-upload'))

if (!ftpConfig) {
  console.log(chalk.red('请配置 ftp.config.js'))
  process.exit(1)
else {
  const businessLine = getBusinessLine(program)
  const { name } = packageJson
  const conn = ftp.create({
    parallel10,
    log: fancyLog,
    ...ftpConfig
  })

  const pipe = gulp
    .src(`${cwd}/${program.docsDirName || 'docs'}/**/*`)
    .pipe(conn.dest(`/${businessLine}/${name}/`))
    .on('end', () => {
      console.log(chalk.green('Success: 文档上传成功'))
      ftp.docUrl && open(ftp.docUrl)
    })

  return pipe
}
}))

以上命令保证转转的所有sdk都能实现按需加载,并且规范化输出

结语

如果只是想用一个很简单的小功能,不用写繁琐的node脚本,不用去配置复杂的webpackgulp不超过10行代码就能帮你搞定,它丰富的插件生态基本能满足你所有的功能需求,简直就是提升开发效率的利器。

参考资料

  • gulp官网
  • gulp插件集合
  • commander-tools
  • 文件通配符

文末福利