1. gulp 食用方法

gulpjs 是一个前端构建工具,与 gruntjs 相比,gulpjs 无需写一大堆繁杂的配置参数,API 也非常简单,学习起来很容易.不同于 grunt,gulpjs 使用的是 nodejs 中 stream 来读取和操作数据,其速度更快。

1.1. 安装

npm install -g gulp

全局安装 gulp 后,还需要在每个要使用 gulp 的项目中都单独安装一次。把目录切换到你的项目文件夹中,然后在命令行中执行:

npm install gulp

如果想在安装的时候把 gulp 写进项目 package.json 文件的依赖中,则可以加上--save-dev:

npm install --save-dev gulp

gulp 入口文件 gulpfile.js

需要注意的是 gulp4.0 变动较大,如

  • gulp4.0 以前需要使用 task 定义任务,4.0 之后导出函数即可作为任务

  • gulp4.0 不再支持同步任务,需要调用回调函数或其他方式标记任务完成

exports.foo = (done) => {
  console.log(start);
  done();
};

此时输入gulp foo即可执行任务

使用exports.default可以定义默认认为,运行时不需要指定任务名称

1.2. gulp 的组合任务

使用 series, parallel 分别开启串行和并行任务,可以相互嵌套的使用

const { series, parallel }

const task1 = () => { /* do something */ }
const task2 = () => { /* do something */ }
const task3 = () => { /* do something */ }

exports.foo = series(task1, task2, task3)   // 所有任务会按顺序执行
exports.bar = parallel(task1, task2, task3)   // 所有顺序会同时执行

上文说到 gulp4.0 以后不再支持同步模式,gulp 中有三种方式指定任务完成

  • 通过调用任务函数中的回调函数参数,也可以在函数参数中传递错误
exports.foo = (done) => {
  done(new Error('task failed))
}
  • 返回 Promise,也可以采用 async await 语法糖
exports.promise = () => {
  return Promise.resolev(); // 不需要返回任何值,gulp会忽略
  // 也可以抛出错误
  return new Promise.reject(new Error("task failed"));
};
  • 返回文件流
exports.stream = (done) => {
  const readStream = fs.createReadStream("package.json"); // 创建文件读取流
  const writeStream = fs.createWriteStream("tmp.txt"); // 创建文件写入流
  readStream.pipe(writeStream);
  return readStream;

  // 其实gulp内部会监听文件流的end事件,一旦写入成功,调用done标记任务结束
  readStream.on("end", () => {
    done();
  });
};

1.3. gulp 工作原理

gulp 是基于 node.js 中的 stream 完成数据读写与转换工作,核心原理就是创建文件读取流,文件转换流,文件写入流

通过一系列的插件,将读取到的文件流通过插件,pipe(导入,类似于数据从管道中加工一样)到插件的转换流中,最终将转换过的流 pipe 进写入流中

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

exports.default = () => {
  const readStream = fs.createReadStream('index.css')    // 创建文件读取流
  const writeStream = fs.createWriteStream('index.min.css')   // 创建文件写入流
  const trasnform = new Transform({   // 创建文件转换流
    trasnform: (chunk, encoding, callback) {
      // chunk: 读取流中的内容(Buffer)
      // do something, such as minify css
    }
  })
  readStream
    .pipe(trasnform)
    .pipe(writeStream)
  return readStream

  // 本示例中,这样通过gulp的自动化处理,就能将css文件进行压缩
}

gulp 中已经提供了包括文件读取流 gulp.src,写入流 gulp.dest,转换流(gulp 插件)的功能,基于 gulp 可以很轻松的完成前端构建过程中的任何操作

如,使用 gulp 插件完成压缩 css 的任务

const { src, dest } = require("gulp");
const cleanCss = require("gulp-clean-css");

exports.default = () => {
  return src("src/*.css").pipe(cleanCss()).pipe(dest("dist"));
};

命令行中输入 gulp 即可将 src 目录下的所有 css 压缩到 dist 目录下

其中gulp.src(globs[, options])需要特殊说明一下,内部使用node-glob匹配文件(类似正则), options为可选参数,通常较少配置

*匹配文件路径中的0个或多个字符,但不会匹配路径分隔符,除非路径分隔符出现在末尾

**匹配路径中的0个或多个目录及其子目录,需要单独出现,即它左右不能有其他东西了。如果出现在末尾,也能匹配文件。

?匹配文件路径中的一个字符(不会匹配路径分隔符)

[...]匹配方括号中出现的字符中的任意一个,当方括号中第一个字符为^或!时,则表示不匹配方括号中出现的其他字符中的任意一个,类似js正则表达式中的用法

1.4. gulp 插件

gulp 插件为以gulp-开头的npm 模块

1.4.1. 样式编译

scss 使用 gulp-sass,less 使用 gulp-less

需要注意的是安装 gulp-sass 依赖于 node-sass,node-oss 是一个 c++模块,由于网络原因很难下载下来,需要单独配置镜像源

vim ~/.npmrc

## 添加镜像源
sass_binary_site=https://npm.taobao.org/mirrors/node-sass
const gulp = require("gulp"),
  sass = require("gulp-sass");

const compileSass = () => {
  // 提供src的第二个参数,指定base,可以将输出的文件与源文件保持相同的目录结构;另外一个cwd也比较常用,表示src寻找路径时,从哪个路径下开始寻找
  // 另外,gulp-sass不会去转换以_开头的scss文件,会被认为这是其他scss所依赖的文件
  return gulp
    .src("src/assets/styles/*.sass", (base: "src"))
    .pipe(sass({ outputStyle: "expanded" })) // 默认右括号不会折行
    .pipe(gulp.dest("dist/css"));
};

执行yarn gulp compileSass,src/assets/style下的所有非_开头的sass文件会被编译成css文件到dist目录下

1.4.2. 脚本编译

使用插件gulp-babel

需要同时下载gulp-babel, @babel/core,相应的 presets 如@babel/preset-env,此 preset 会将所有最新的 ECMAScript 新特性做出转换

因为 babel 本身只是一个负责转换 ECMAScript 的平台,具体的实施是通过插件去实现的,不清楚 presets 使用的见这里

var gulp = require('gulp'),

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

同理,执行yarn gulp compileJs,此时src/assets/scripts目录下所有的js文本会经过babel转换,将最新的ECMAScript特性转化为ES5代码到dist目录下

1.4.3. 模板文件编译

根据模板引擎的不同,选择对应的插件,如 gulp-swig,gulp-front-matter,gulp-template 等

var gulp = require('gulp'),
const swig = require("gulp-swig");

const data = {}; // 模板需要的数据

const compileTemplate = () => {
  return gulp
    .src("src/*.html", { base: "src" })
    .pipe(swig({ data }))
    .pipe(dest("dist"));
};

1.4.4. 重命名

使用 gulp-rename

用来重命名文件流中的文件。用 gulp.dest()方法写入文件时,文件名使用的是文件流中的文件名,如果要想改变文件名,那可以在之前用 gulp-rename 插件来改变文件流中的文件名。

var gulp = require('gulp'),
    rename = require('gulp-rename'),
    uglify = require("gulp-uglify");

gulp.task('rename', function () {
    gulp.src('js/jquery.js')
    .pipe(uglify())  //压缩
    .pipe(rename('jquery.min.js')) //会将jquery.js重命名为jquery.min.js
    .pipe(gulp.dest('js'));

    //关于gulp-rename的更多强大的用法请参考https://www.npmjs.com/package/gulp-rename

1.4.5. 图片和字体文件转换

可以使用gulp-imagemin插件来压缩 jpg、png、gif 等图片。需要注意此模块内部也依赖于一些c++模块,下载比较困难

图片是无损的压缩,只是删除了一些元数据的信息,只有svg的字体文件可以通过gulp-imagemin进行压缩

var gulp = require('gulp');
var imagemin = require('gulp-imagemin');
var pngquant = require('imagemin-pngquant'); //png图片压缩插件

const image = () => {
  return gulp.src('src/images/*')
      .pipe(imagemin({
          // 可以传入参数指定压缩图片的插件或者不传
          progressive: true,
          use: [pngquant()] // 使用pngquant来压缩png图片
      }))
      .pipe(gulp.dest('dist'));

}

gulp-imagemin的使用比较复杂一点,而且它本身也有很多插件,建议去它的项目主页看看文档

1.4.6. 清理文件/文件夹

可以使用gulp插件gulp-clean或者非gulp插件del执行自定义操作

需要注意如果使用gulp-clean,需要配置src的allowEmpty为true,以防止需要清除的目录不存在而报错终止

const gulp = require('gulp')
const del = require('del')

const clean = () => {
  return del(['dist'])    // del方法返回Promise,可以作为任务结束标志
}

1.4.7. 自动加载插件

gulp-load-plugins这个插件能自动帮你加载package.json文件里的gulp插件,并且自动转换为驼峰命名,不用显式的去require每一个gulp插件

const gulp = require('gulp');
//加载gulp-load-plugins插件,并马上运行它
const plugins = require('gulp-load-plugins')();

然后我们要使用gulp-rename和gulp-ruby-sass这两个插件的时候,就可以使用plugins.rename和plugins.rubySass来代替了,也就是原始插件名去掉gulp-前缀,之后再转换为驼峰命名。

1.4.8. 其他插件

代码复用: gulp-file-include, 自动升级版本号: gulp-bump

1.5. 开发服务器

使用模块browser-sync,提供一个web服务器,支持热更新

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

 const server = () => {
  br.init({
    notify: false,    // 是否打开提示窗
    port: 8999,
    open: false,    // 是否默认打开浏览器
    files: 'dist/**',      // 监听的路径通配符,文件发生改变后,自动刷新浏览器;也可以使用br实例的reload方法
    server: {
      baseDir: 'dist',
      // 优于baseDir配置,假如html中有引入node_modules下的样式或脚本,需要根据routes映射路由;但是,只是在开发环境中有效,打包过后,dist目录下仍没有node_modules中的文件,这个后续再说
      routes: {
        '/node_modules': 'node_modules'
      }
    }
  })
}

1.6. gulp.watch

gulp.watch()用来监视文件的变化,当文件变化后,可以利用它执行相应的任务

gulp.watch(glob[, opts], tasks)

glob为要监视的文件匹配模式,可以传递字符串或者数组 opts为一个可选的配置对象 tasks为文件变化后要执行的任务,数组形式

此外,gulp.watch还有另外一种用法

gulp.watch(glob[, opts, cb])

cb为函数,表示监视的文件发生变化时,会调用这个函数;函数接收一个对象参数,该对象包含了文件变化的一些信息

type为属性变化的类型,可以是added, changed, deleted, path

需要注意的是,如果你使用了swig模板引擎,当你修改到模板源代码时,swig模板引擎的缓存机制会导致页面修改不生效 此时需要将swig选项中cache设置为false

另外,在开发过程中,有些文件不需要做出更改后的反馈,如图片,纯静态资源等,这些会加大开销,为优化 我们可以指定br.init()中server的baseDir参数,可以传递一个数组,当前一个元素找不动文件时,会去后后续路径寻找

1.7. useref文件处理

通过构建注释可以完成引用文件的打包,合并,压缩,针对的是dist中文件而不是src中的文件

  <!-- build:css assets/styles/vendor.css -->
  <link rel="stylesheet" href="/node_modules/..">
  <!-- endbuild -->
  <!-- build:css assets/styles/main.css -->
  <link rel="stylesheet" href="assets/styles/main.css">
  <!-- endbuild -->

配置useref

const useref = () => {
  return src('dist/*.html', { base: 'dist' })
    .pipe(plugins.useref({ searchPath: ['dist', '.'] }))
    .pipe(dest('dist'))
}

其中searchPath中配置的选项表示模板文件中的引用路径首先会在dist目录下去寻找,其次在项目根目录下寻找

编译后的结果为模板文件中的引入路径会被替换为相应路径,且注释会被移除掉,如

  <link rel="stylesheet" href="asset/styles/vendor.css">

编译后的css或js文件,是合并过的文件,但是未经压缩,我们可以这样

经过上述useref处理,未经压缩的文件为html,css,js,因此对应着三种文件的压缩

yarn add gulp-htmlmin gulp-uglify gulp-clean-css --dev

此处面临的问题是我们的读取流为三种格式的文件,需要分别进行不同的操作,可以借助gulp-if插件

yarn add gulp-if --dev
const useref = () => {
  return src('dist/*.html', { base: 'dist' })
    .pipe(plugins.useref({ searchPath: ['dist', '.'] }))
    .pipe(plugins.if(/\.js$/), uglify())
    .pipe(plugins.if(/\.css$/), cleanCss())
    .pipe(plugins.if(/\.html$/), htmlmin())
    .pipe(dest('dist'))
}

这里要注意必须先执行一次编译命令再执行useref,因为此前构建时构建注释已经被删除

执行以上任务时,你会发现有些文件没有写入成功,这是因为这里的读取流和写入流都是dist目录下的,同时读写,产生冲突,可能导致有些内容写不进去的情况 我们需要将开发环境过程中的构建后的文件写入另外的路径,只有build后的文件才放入dist目录

1.8. 封装工作流

如何提取一个可复用的自动化构建工作流

Jason Huang all right reserved,powered by Gitbook该文件最后修改时间: 2021-01-15 16:36:54

results matching ""

    No results matching ""