自动化构建工具和Gulp

309 阅读10分钟

笔记来源:拉勾教育 - 大前端就业集训营

文章内容:学习过程中的笔记、感悟、和经验

Gulp - 自动化构建工具

中文官网:www.gulpjs.com.cn/

英文官网:www.gulpjs.com

常用自动化构建工具

  • grunt:最早,基于临时文件,较慢
  • Glup:基于内存,速度快,简单
  • FIS:百度发布,百度维护,不更新了

Gulp和npm scripts

  • 都能实现自动化构建
  • gulp语法更简单,使用的是js语法,npm使用的语法更接近shell脚本
  • gulp生态完善,构建效率更高

Gulp基础

步骤

  1. 全局安装gulp(npm i gulp-cli -g),这里安装的只是gulp的命令行工具
  2. 初始化项目(npm init --yes)
  3. 项目中安装gulp包(npm i gulp -D),这里在本地安装项目依赖
  4. 根目录创建gulpfile.js文件
  5. gulpfile.js中,创建gulp任务,类似之前的脚本
  6. 执行gulp任务(gulp 任务名
// gulpfile.js

// 注意,新版gulp要求所有任务必须是异步的,所以不可以写同步任务,只需要加上一个回调函数即可(cb)

// 直接引入gulp
// const GulpClient = require("gulp");


// 旧版写法
// 调用task方法添加任务
// 参数1为任务名,参数2是一个异步函数
// GulpClient.task('task1', (cb) => {
//   console.log('task1')
//   cb()
// })


// 新版写法,直接创建常量,值为一个异步函数
const task2 = (cb) => {
  console.log('task2')
  cb()
}
const task3 = (cb) => {
  console.log('task3')
  cb()
}
// 最后把所有新创建的函数导出出来即可
module.exports = {
  task2,
  // default表示默认执行,调用的时候不需要写任务名,直接gulp回车即可执行task3
  default: task3
}

组合任务

类似于npm scripts中的并行和串行执行的功能

任务执行

并行执行:gulp.parallel(任务,任务.....)

串行执行:gulp.series(任务,任务......)

案例:先执行任务1,之后任务二和任务三并行执行,之后执行任务4 => gulp.series(任务1,gulp.parallel(任务2,任务3),任务4)

yCXwyq.png

// 导入gulp
const GulpClient = require("gulp")


// 添加四个任务
const task1 = (cb) => {
  console.log('task1')
  cb()
}
const task2 = (cb) => {
  console.log('task2')
  cb()
}
const task3 = (cb) => {
  console.log('task3')
  cb()
}
const task4 = (cb) => {
  console.log('task4')
  cb()
}


// 常规写法,注意module可以省略,直接写exports即可
// exports = {
//   p: GulpClient.series(task1, GulpClient.parallel(task3, task2), task4)
// }
// exports实际上是一个对象,直接添加方法也可以,和上面的写法效果相同
exports.p = GulpClient.series(task1, GulpClient.parallel(task3, task2), task4)
// 任务2和任务3并行执行,和任务1、任务4串行执行

文件操作

gulp是基于流的构建系统,文件操作可以使用gulp的三个函数

输入-读取流-src() => 加工-转换流-pipe() => 输出-写入流-dest()

// 引入gulp
// 常规方法
// const gulp = require('gulp')

// 可以使用es6解构的方式获得两个方法,不过还是建议不要这么做,可能要用到其他方法,这样做反倒麻烦
const {
  src,
  dest
} = require('gulp')

// 创建任务,因为gulp的方法都是异步的,所以不需要在创建回调函数了
const go = () => {
  // 常规方法调用
  // return gulp.src('./src/a/index.js', {base: 'src'}).pipe(gulp.dest('./dist'))
  // 使用ES6就不需要写gulp了
  return src('./src/a/index.js', {base: 'src'}).pipe(dest('./dist'))
}

// 这里建议写全module.exports,因为刚刚我没写module导致报错了
module.exports = {
  go
}

案例:构建样式文件

需求:和之前npm scripts脚本一样需要把less文件转换成压缩后的css文件

思路:less转css再进行压缩再更改名字后缀为min.css

我们需要在gulp官网查找所需的插件

  • less转css插件:gulp-less
  • css压缩插件:gulp-clean-css
  • 重命名插件:gulp-rename
// 引入gulp
const gulp = require('gulp'),
  // 本地安装三个插件,按照插件文档要求引入3个插件
  less = require('gulp-less'),
  cleanCSS = require('gulp-clean-css'),
  rename = require("gulp-rename")

// 创建任务
const go = () => {
  // 读取流
  return gulp.src('./src/style/index.less', {
      // 参考src创建文档结构
      base: 'src'
    })
    // 之前说过,管子函数pipe可以反复调用多次
    // 按照顺序依次使用插件
    // 注意其实这些插件都可以配置参数,这里不需要所以没有配置参数,具体参数可以查询插件文档
    // 转css
    .pipe(less())
    // 压缩
    .pipe(cleanCSS())
    // 重命名,这里使用对象的方式,只更改后缀名
    .pipe(rename({
      extname: ".min.css"
    }))
    // 写入流
    .pipe(gulp.dest('./dist'))
}
// 导出
module.exports = {
  go
}

Css hack和autoprefixer兼容性

css hack

css代码也存在兼容性问题

针对不同浏览器写不同的css代码,这过程叫做css hack

属性前缀法

给我们所写的css代码加上特定的前缀名,可以在不同浏览器调用指定的属性

/* 例如:user-select属性可以控制用户是否可以选中文本(存在兼容性问题),如果使用前缀法就要写成 */
.code {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  -o-user-select: none;
  user-select: none;
}
前缀浏览器
-ms-IE
-webkit-Chrome、safari
-moz-火狐
-o-opera

autoprefixer自动前缀

自动为css样式添加兼容性前缀

Autoprefixer使用caniuse.com的数据来决定哪些属性需要加前缀

// 引入gulp
const gulp = require('gulp'),
  //引入自动前缀插件
  gulpAutoprefixer = require('gulp-autoprefixer')

// 创建任务
const go = () => {
  // 读取流
  return gulp.src('./src/style/index.less', {
      // 参考src创建文档结构
      base: 'src'
    })
  // 转css
    .pipe(less())
  // 调用管子方法调用自动添加前缀
    .pipe(gulpAutoprefixer())
    // 写入流
    .pipe(gulp.dest('./dist'))
}
// 导出
module.exports = {
  go
}
/* 转换前,less代码 */
@cl : red;

// 我是注释
body {
  width: 100px;
  height: 100px;
  background-color: @cl;
  user-select: none;
}



/* 转换后,发现前缀自动添加了 */
body {
  width: 100px;
  height: 100px;
  background-color: red;
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
}

构建脚本(js)文件

使用插件

  • 转ES5插件:gulp-babel,注意查看自己本地babel的版本安装对应的版本,另外文档里面的代码使用的是版本7的代码,如果是6版本不要用那个,使用'babel-preset-env'
  • 压缩js代码:gulp-uglify
  • 重命名:gulp-rename

构建HTML文件

使用插件:

  • 压缩HTML文件:gulp-htmlmin

构建图片文件

使用插件:

  • 压缩图片: gulp-imagemin - 默认无损压缩,有损压缩需要配置参数

文件清除

使用插件

  • 删除文件和目录:del - npm i dev -D
  • 在gulp没有发现这款插件,可能不属于gulp,后面调用也不需要写gulp前缀

Gulp服务发布 - 服务器

使用插件

npm i browser-sync gulp -D - gulp可省略

注意:

  • 发布的服务是默认指定目录下的index.html文件
  • 文件如果需要引入js或者css文件要参考目录结构,如果输出结构和构建之前的结构相同,可以在构建之前就更改引入文件名
    • 比如构建之前使用的是index.css
    • 构建后css文件为index.min.css
    • 那么就可以在构建之前更改css文件名为index.min.css,这样构建之后就会直接链接到构建后的index.min.css文件

组合案例:构建css+自动前缀、js、HTML、图片、文件清除、发布服务

这里展示完整的一整套css、js、html、图片构建、文件清除、发布服务

这里会演示上面所有构建服务的代码

// 引入gulp
const gulp = require('gulp'),
  // 引入gulp插件
  // less转css
  less = require('gulp-less'),
  // css自动前缀
  autoprefixer = require('gulp-autoprefixer'),
  // 压缩css文件
  cleanCss = require('gulp-clean-css'),
  // 重命名
  rename = require('gulp-rename'),
  // 压缩js代码
  uglify = require('gulp-uglify'),
  // ES6+转SES5
  babel = require('gulp-babel'),
  // 压缩html代码
  htmlmin = require('gulp-htmlmin'),
  // 压缩图片
  imagemin = require('gulp-imagemin'),
  // 文件清除插件
  del = require('del'),
  // 引入并创建服务器,.create()为创建服务器的方法
  browserSync = require('browser-sync').create()

// css文件构建
const css = () => {
  // 读取
  return gulp.src('./src/css/index.less', {
      // 以src文件结构为模版
      base: 'src'
    })
    // 转css
    .pipe(less())
    // 自动前缀
    .pipe(autoprefixer())
    //压缩
    .pipe(cleanCss())
    // 重命名
    .pipe(rename({
      // 重命名使用对象格式,设置后缀
      extname: '.min.css'
    }))
    // 写入
    .pipe(gulp.dest('./dist'))
}

// js构建
const js = () => {
  return gulp.src('./src/js/index.js', {
      base: 'src'
    })
    // 转ES5
    .pipe(babel({
      // babel文档的代码是babel7的代码,如果是babel6改成'babel-preset-env'即可,也可直接去
      presets: ['babel-preset-env']
    }))
    // 压缩
    .pipe(uglify())
    // 重命名
    .pipe(rename({
      extname: '.min.js'
    }))
    .pipe(gulp.dest('./dist'))
}

// html构建
const html = () => {
  return gulp.src('./src/index.html', {
      base: 'src'
    })
    // html压缩,这里设置参数
    .pipe(htmlmin({
      // 下面三个默认属性都是flase,所以需要改为true
      // 空白折叠 - 是
      collapseWhitespace: true,
      // 压缩css代码 - 是
      minifyCSS: true,
      // 压缩js代码 - 是
      minifyJS: true
    }))
    .pipe(gulp.dest('./dist'))
}

// 图片构建
const image = () => {
  // 这里使用通配符**通配怒文件夹下面的所有文件
  return gulp.src('./src/img/**', {
      base: 'src'
    })
    // 执行压缩,这里可以配置参数,可以查看文档
    .pipe(imagemin())
    .pipe(gulp.dest('./dist'))
}

// 清除文件任务
const clear = () => {
  // 直接调用del即可,del不属于gulp
  return del(['./dist'])
}

// 发布服务
const serve = () => {
  // init()为服务器初始化方法
  return browserSync.init({
    // 禁用每次刷新后出现在右上角提示框
    notify: false,
    server: {
      // basedir为参考的基础目录,会自动在目录下寻找index.html文件进行发布
      baseDir: "./dist"
    }
  });
}

// 创建串行执行任务先执行清除文件,再并行构建css、js、html、图片,最后直接发布服务
const go = gulp.series(clear, gulp.parallel(css, js, html, image), serve)

// 导出所有任务
module.exports = {
  // 这里只需要把go暴露出来即可,其他的都不需要单独执行
  go
}

使用bootstrap

使用插件

  • bootstrap:提供常用页面效果
  • jquery:bootstrap依赖

npm i jquery bootstrap@3.4.1 -S

注意:

  • 如果使用这两个插件是后面上线版本中需要保留的,所以使用的时候不要使用-D而是使用-S安装插件
  • 如果要使用指定版本包,可以在包后面加@版本号安装指定版本的包,例如bootstrap@3.4.1

引入路径和发布路由

  • 引入路径:这里的css和js文件都要去node_modules文件夹中去找,因为依赖安装在这里
jquery:
<script src="/node_modules/jquery/dist/jquery.min.js"></script>

bootstrap:
<script src="/node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css">
  • 发布路由:
// 发布服务
const serve = () => {
  // init()为服务器初始化方法
  return browserSync.init({
    // 禁用每次刷新后出现在右上角提示框
    notify: false,
    server: {
      // basedir为参考的基础目录,会自动在目录下寻找index.html文件进行发布
      baseDir: "./dist",
      
      
      // 在这里可以设置路由映射!!!!!!!
      routes: {
        // 把 /node_modules 映射到 node_modules 文件夹,实际上只要属性名对应上引入的链接前缀即可
        '/node_modules': 'node_modules'
      }
    }
  });
}
// 路由上只是一个映射地址,假如前面写的src地址是 /abc/.../..../.../... 映射只需要写上 '/abc': 'node_modules' 即可

browser-sync热更新 - 服务器监视文件变化

方法

  • 监视src目录变化,一旦变化自动构建 -

    • 使用gulp的watch()方法监视文件
    • 语法:gulp.watch('src/css/*.less'(监听文件,星号表示通配,两个星号表示监视全部文件), css(文件变化后执行任务))
  • 监视dist目录变化,一旦变化重新发布 - 使用browser-sync的files监视目录

  • // 发布服务
    const serve = () => {
      // 监视文件变化!!!!!!!
      gulp.watch('src/index.html', html)
      gulp.watch('src/css/*.less', css)
      gulp.watch('src/js/*.js', js)
      gulp.watch('src/img/**', image)
    
      // init()为服务器初始化方法
      browserSync.init({
        
        // files表示文件,这里表示一旦'./dist/**'下面的任何文件发生变化都要更新!!!!!!!!
        files: './dist/**',
        
        // 禁用每次刷新后出现在右上角提示框
        notify: false,
        server: {
          // basedir为参考的基础目录,会自动在目录下寻找index.html文件进行发布
          baseDir: "./dist",
          // 在这里可以设置路由映射
          routes: {
            // 把 /node_modules 映射到 node_modules 文件夹,实际上只要属性名对应上引入的链接前缀即可
            '/node_modules': 'node_modules'
          }
        }
      });
    }
    

yk95XF.png

gulp和yeoman

以gulp在yeoman中的使用为例,运行过程

  1. 命令行执行npm run start =>
  2. 执行npm scripts中的脚本start =>
  3. 脚本内容为gulp serve =>
  4. 执行gulp serve =>
  5. 执行构建和服务器发布

ykAVeO.png

根据以上分析,我们可以发现,gulp在脚手架中使用起来没有任何问题,因为脚手架中继承了gulp,我们也学习了gulp,在日后工作中我们就可以在自行定制脚手架中gulp的js代码

安装依赖报错解决方案

报错:无法加载默认插件xxxxx / Couldn't load default plugin xxxxx

解决方案:配置hosts

  1. 通过vscode打开hosts文件(C:\Windows\System32\Drivers\etc)
  2. 添加Github hosts内容(请查看课堂笔记)
  3. 保存(如无权限,以管理员身份新打开文件)
  4. 然后重新安装插件

如果上面以来问题仍然没有解决

删除所有依赖文件,命令行输入npm i会自动安装所有这个项目的依赖(package.json下面的所有依赖),可以解决大部分问题

淘宝cnpm包下载

npm install -g cnpm --registry=https://registry.npm.taobao.org

使用cnpm安装插件(更快,有一些问题可以解决,就比如我安装gulp-imagemin运行之后总是报错,使用cnpm解决了)