前端工程化之gulp

Posted 前端小菜的进阶之路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端工程化之gulp相关的知识,希望对你有一定的参考价值。


前端工程化是依据业务特点,将前端开发的规范、流程、技术、工具、经验等形成规范并建立成一种标准的体系。


构建工具的主要功能就是实现自动化处理,例如对代码进行检查、预编译、合并、压缩;生成雪碧图、sourceMap、版本管理;运行单元测试、监控等,当然有的工具还提供模块化、组件化的开发流程功能。


今天我们就来熟悉一款相对简单的构建工具——gulp。


一、gulp介绍

gulp的前身是grunt,它们都是基于任务运行的构建工具。与grunt相比,gulp显得更为高效更为简单了:

  • 更为高效:gulp利用 Node.js流的威力,可以快速构建项目并减少频繁的 IO操作;

  • 更为简单:通过代码优于配置的策略,Gulp让简单的任务简单,复杂的任务可管理;

  • 易于上手:通过最少的 API,掌握 Gulp毫不费力,构建工作尽在掌握:如同一系列流管道。

gulp比较简单,入门容易,适合小的工程项目的构建。


二、gulp的安装

我们使用任何软件的第一步就是安装。

第一步gulp是基于node,所以我们第一步进行node的监检测:

node -v

如果你没有安装node,请移步到,先进行node的安装;


第二步:正确安装node后,全局安装gulp,并检测是否安装成功

npm install -g gulp
....
....
gulp -v


第三步:切换到项目目录,局部安装gulp

npm init -y
...
npm install gulp -D
...


第四步gulp初体验

// 执行gulp命令
gulp
No gulpfile found
...


然后我们在项目根目录创建一个配置文件:gulpfile.js

// 执行gulp命令
var gulp = require('gulp');

gulp.task('default'function() {
  // 将你的默认的任务代码放在这
  console.log('我是默认任务')
});


至此,项目目录结构如下:

├── project_dir(项目根目录)
  ├── gulpfile.js(gulp配置文件)
  ├── node_modules
  │ └── gulp
  └── package.json

gulp的安装就结束了,这里为什么要全局和局部分别安装一次gulp呢?有人说是为了版本灵活,其实这不是主要的原因:

  • 全局安装:作为命令行工具,作为启动器,执行gulpfile.js中的任务

  • 本地安装:作为gulp的核心库,提供各种功能api,在gulpfile.js中使用


三、gulp的api

gulp简单易学,入门容易的一个原因得益于它的api够简单。满打满算它的api就那么十一二个,常用的api也就4个:

  • gulp.task():创建任务;

  • gulp.src():根据参数选择要处理的文件,并返回一个可以被pipe接收的虚拟文件对象流;

  • gulp.dest():能够接收pipe传出的流,并把流中的内容写入到文件中;

  • gulp.watch():监测文件,文件修改后,执行相关的任务操作;


在前面提到,gulpgrunt更高效,其中它们一个重要的因素在任务执行的时候的媒介不同:grunt是以文件为媒介进行任务的,它每进行一步就会把结果存入到一个临时文件中,来作为下一步的输入;gulp是以流为媒介进行额任务,它匹配到要处理的文件之后,生成相应的虚拟文件对象流,这个"流"流入到下一步任务,处理完之后继续生成相应的"流",直到任务结束,然后再输出到文件。


1、gulp.src():输入文件

用来匹配要处理的文件,并根据参数生成相应的流,其语法如下:

gulp.src(globs[, options])
  • globs:用来配文要处理的文件路径,包含文件名,采用的是node-glob语法,或者直接输入文件路径,语法有些类似正则,又有别于正则。可以是string或array

  • options:生成文件流时的可选配置,通常我们不需要关注

下面我们来看几个例子:

// 匹配src/js文件夹下的所有直接包含的js
gulp.src('src/js/*.js')

// 匹配src/js文件夹下的所有js,包含js后代目录中的js文件
gulp.src('src/js/**/*.js')

/**
 *匹配src/js文件夹下的所有js,包含js后代目录中的js文件,
 *以及lib/js下的直接包含的js
 */
 
gulp.src(['src/js/**/*.js','lib/js/*.js'])

更多相关信息,请阅读gulp官网api。


2、gulp.dest():输出文件

gulp.dest()方法是用来写文件的,其语法为:

gulp.dest(path[,options])
  • path:文件将被写入的路径(输出目录)。也可以传入一个函数,在函数中返回相应路径;

  • options:可选输出配置参数,通常不需要关注;

这里要注意:path是路径,不包含文件名哦,生成的最终文件名,由gulp.src匹配到的文件决定,或者通过gulp-rename修改

例如:

var gulp = require('gulp');
gulp.src('lib/js/jquery.js')
    .pipe(gulp.dest('dist/foo.js'));
//最终生成的文件路径为 dist/foo.js/js/jquery.js,而不是dist/foo.js


3、gulp.task()

我们说gulp是任务型的工程化工具,那么如何创建任务呢?

语法如下:

gulp.task(name[, deps]fn)
  • name:当前要定义的任务名称

  • deps:可选参数,当前任务所依赖的其他任务列表

  • fn:当前任务要执行的操作


在我们编译一个项目的过程中,我们可能创建了多个任务:buildbuild_jsbuild_cssbuild_lib等,它们直接可能存在依赖,也能不存在依赖。

build依赖其他任务时:

//定义一个有依赖的任务
gulp.task('build',['build_js','build_css','build_lib'],function()
  // build任务内容
});


当我们所依赖的任务是个异步任务时,我们需要异步操作完成才进行下一步,该怎么操作呢?有三种方式:

  • 回调的方式:给被依赖任务传入回调函数cb,异步操作完成后,调用cb

//被依赖任务:cb为任务函数提供的回调,用来通知任务已经
gulp.task('one',function(cb){ 完成
  //one是一个异步执行的任务
  setTimeout(function(){
    console.log('one is done');
    cb();  //执行回调,表示这个异步任务已经完成
  },5000);
});

//这时two任务会在one任务中的异步操作完成后再执行
gulp.task('two',['one'],function(){
  console.log('two is done');
});


  • 返回一个流:

gulp.task('somename'function() {
  var stream = gulp.src('client/**/*.js')
    .pipe(minify())
    .pipe(gulp.dest('build'));
  return stream;
});


  • 返回一个promise

var Q = require('q');

gulp.task('somename'function() {
  var deferred = Q.defer();
  // 执行异步的操作
  setTimeout(function() {
    deferred.resolve();
  }, 1);

  return deferred.promise;
});


4、gulp.watch()

用来监视文件的变化,当文件发生变化后,我们可以利用它来执行相应的任务,例如文件压缩等。其语法为:

gulp.watch(glob[, opts]tasks)
  • glob:匹配待监测的文件,同gulp.src参数

  • opts:可选的配置参数

  • tasks:任务列表,当文件变化时,需要执行的任务


四、常用插件

终于到了插件部分,插件才是王道,去gulp的官网扫了一眼,它的插件多达3772个,能满足你各种姿势各种需求。好了,我们先来上一个清单(package.json):

{
  "name""16",
  "version""1.0.0",
  "description""",
  "main""index.js",
  "scripts": {
    "test""echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author""",
  "license""ISC",
  "devDependencies": {
    "@babel/core""^7.1.6",
    "@babel/preset-env""^7.1.6",
    "gulp""^3.9.1",
    "gulp-babel""^8.0.0",
    "gulp-concat""^2.6.1",
    "gulp-cssmin""^0.2.0",
    "gulp-imagemin""^5.0.3",
    "gulp-less""^4.0.1",
    "gulp-livereload""^4.0.0",
    "gulp-rename""^1.4.0",
    "gulp-sourcemaps""^2.6.4",
    "gulp-uglify""^3.0.1"

  }
}


看到了吗,一部分金黄色的字,那就是我们常用的插件,它们清一色的以"gulp-"开头,并且各司其职。


1、JS压缩插件:gulp-uglify

安装:

npm install gulp-uglify -D

使用:

const gulp = require('gulp');
const uglify = require('gulp-uglify'); //js压缩
const rename = require('gulp-rename'); // 重命名

// JS压缩重命名
gulp.task('js/*.js',() => {
  return gulp.src(js_path)
  .pipe(uglify())
  .pipe(rename({suffix: '.min'}))
  .pipe(gulp.dest('./build/'))
});


压缩js目录下的js文件,并保存到build目录下。这里使用了rename插件,重命名了文件名(给压缩后的文件加了后缀.min),更多rename的使用请查阅官网


没使用es6语法?不可能的,所以当说使用了es6gulp-uglify就显得不够用了,就要需要babel转换了。


2、ES6语法转换:gulp-babel

安装:

npm i gulp-babel @babel/core @babel/preset-env -D

使用:

// 编译es6
gulp.task('es6/*.js',() => {
  return gulp.src(js_path)
  .pipe(babel({
    presets:['@babel/env']
  }))
  .pipe(uglify())
  .pipe(rename({suffix: '.min'}))
  .pipe(gulp.dest('./build/es6'))
});


插件gulp-uglifygulp-babel满足了我们对js的压缩,可是在开发的时候,如果js报错了就麻烦了,因为压缩后的代码谁也不认识,变得不可追溯了。怎么办,用gulp-sourcemaps


3、资源图:gulp-sourcemaps

安装:

npm i gulp-sourcemaps -D

使用:

// 编译es6并记录map可调式追溯
// 注意sourcemap的位置
gulp.task('es6map',() => {
  return gulp
  .src('es6/*.js')
  .pipe(sourcemaps.init())
  .pipe(babel({
    presets:['@babel/env']
  }))
  .pipe(uglify())
  .pipe(rename({suffix: '.min'}))
  .pipe(sourcemaps.write())
  .pipe(gulp.dest('./build/es6map'))
});

有兴趣的话,可以去看看生成的sourcemap位置,看看如何生成独立的sourcemap文件。


4、处理图片文件:gulp-imagemin

适用范围:只能处理三种文件jpg/png/gif

安装:

npm i gulp-imagemin -D

使用:

// 处理图片
gulp.task('image',()=>{
  return gulp
  .src(['./src/img/**/*.jpg''./src/img/**/*.png''./src/img/**/*.gif'])
  .pipe(imgmin([
    imgmin.gifsicle({interlacd:true}),
    imgmin.jpegtran({progressive:true}),
    imgmin.optipng({optimizationLevel:5})
  ]))
  .pipe(gulp.dest('./build/img'))
})

我这里src写的比较繁琐,大家可以尝试着用node-glob写法


5、压缩css:gulp-cssmin

安装:

npm i gulp-cssmin -D

使用:

// 处理css
gulp.task('style',() => {
  return gulp
  .src(['./src/css/**/*.css'])
  .pipe(concat('style.min.css'))
  .pipe(cssmin())
  .pipe(gulp.dest('./build/css'))
})


6、处理less:gulp-less

安装:

npm i gulp-less -D

使用:

gulp.task('less'()=>{
  return gulp
    .src(['./src/less/**/*.less'])
    .pipe(sourcemaps.init())
    .pipe(concat('main.less.min.css'))
    .pipe(less())
    .pipe(cssmin())
    .pipe(sourcemaps.write())
    .pipe(gulp.dest('./build/css/'));
});

sass的处理与less类似,只是插件不同而已,有需要可以去看看gulp-sass的使用。


你会发现在处理less的时候,使用了sourcemap。其实像这类插件gulp-renamegulp-concatgulp-sourcemaps,在很多地方都可以使用,它们跟语言没有多大关系。


7、html文件压缩gulp-minify-html

安装:

npm i gulp-minify-html -D

使用:

gulp.task('minify-html',()=> {
  return gulp.src('src/page/*.html'
  .pipe(minifyHtml()) //压缩
  .pipe(gulp.dest('build/page'));
});

当然gulp-minify-html有很多配置参数:

const htmlConfig = {
  removeComments: true, //清除HTML注释
  collapseWhitespace: true, //压缩HTML
  collapseBooleanAttributes: true, //省略布尔属性的值 <input checked="true"/> ==> <input />
  removeEmptyAttributes: true, //删除所有空格作属性值 <input id="" /> ==> <input />
  removeScriptTypeAttributes: true, //删除<script>的type="text/javascript"
  removeStyleLinkTypeAttributes: true, //删除<style>和<link>的type="text/css"
  minifyJS: false, //压缩页面JS
  minifyCSS: false //压缩页面CSS
};

以上是我以前使用过的,其他的大家去官网查阅


最后我们来上一个高大上的插件:自动刷新


8、自动刷新:gulp-livereload

这个插件是用来自动刷新的,代码修改保存后,浏览器自动刷新。当然该插件最好配合谷歌浏览器使用,需要安装一个浏览器端的插件:livereload_2_1_0_0.crx

安装:

npm i gulp-livereload -D

使用:

const js_path=['./src/js/**/*.js'];

gulp.task('js', ()=>{
  return gulp
    .src(js_path)
    .pipe(babel({
      presets: ['@babel/env']
    }))
    .pipe(concat('bundle.min.js'))
    .pipe(uglify())
    .pipe(gulp.dest('./build/js'))
    .pipe(livereload());
});

gulp.task('watch', ()=>{
  //开启livereload的服务
  livereload.listen();

  //重新编译JS
  gulp.watch(js_path, ['js']);

  //监听html;标记文件修改
  gulp.watch([
    './1.html',
    ...js_path
  ], file=>{
    livereload.changed(file.path);
  });
});

gulp.task('default', ['js''watch']);

你会发现default任务里面先执行了一遍js任务,是因为watch任务只有在文件变化的时候,它才会执行。


别忘记了在浏览器里面安装插件,并开启相应功能哦。


到此为止,我们也就生成了开始时候的清单了。


五、兼容性

当你在执行gulp默认任务的时候:

gulp

有可能你会发现,没法执行,还报了错,别急,这是版本之间的兼容性导致的。

进入官方文档查看,官方的意思是,4.0.0版本的,“default”右边两个参数放在gulp.series()的参数中,否则会报错。所以自己去改改吧


以上是我几次gulp使用后的一些总结,有不对的地方,请斧正,欢迎关注交流。

以上是关于前端工程化之gulp的主要内容,如果未能解决你的问题,请参考以下文章

前端开发自动化之gulp

gulp & webpack整合

前端面试题之前端工程化篇

Gulp & Webpack 整合,鱼与熊掌我都要!

gulp & webpack整合,鱼与熊掌我都要!

从0开始使用gulp升级项目。