使用 Gulp 完成项目的自动化构建

1,347 阅读7分钟

在每次开发前,先把要做的事项一一列出来,或者把流程图画一下,有助于梳理思路

image.png

Gulp简介

Gulp 是目前最流行的前端构建系统,高效易用,是一个基于流的构建系统。它将开发流程中让人痛苦或耗时的任务自动化,从而减少浪费的时间,创造更大的价值。

Gulp官网给出了它的三大特性,如下图:

image.png

安装使用

node.js版本需要 >=12 || 14.13.1 || >= 16

使用步骤如下:

  1. 把gulp安装到全局环境

    安装 gulp 命令行工具npm install --global gulp-cli
    安装 gulp,作为开发时依赖项 npm install --save-dev gulp

  2. 建立gulpfile.js配置文件,编写构建任务,在运行gulp命令时会被自动加载

    打开需要构建的项目代码,使用包管理工具初始化一下项目环境,生成package.json文件,在同级目录新建一个gulpfile.js 的文件,作为gulp自动化构建的配置文件。Vscode里可以在终端运行code gulpfile.js 快速生成并打开这个文件。

  3. 在命令行终端运行gulp任务

    在项目根目录下运行 gulp命令测试,看到以下提示证明gulp安装成功,只是我们还没有配置任务。

image.png

gulp自动化构建的主要过程

读取文件 ---》 压缩文件 ---》 写入文件 (都是以流的方式进行)

gulp 暴露了 src() 和 dest() 方法用于处理计算机上存放的文件。
src( ): 读取文件生成一个Node流
dest( ):生成一个Node流,通常作为终止流

流(stream)所提供的主要的 API 是 .pipe() 方法,用于连接转换流或可写流。

创建任务前,需要把 src 和 dest 引入进来

// 使用Common JS 规范引入
const { src, dest } = require('gulp')

创建任务

创建任务主要使用gulp提供的一些插件实现,可以根据实际需要通过glup官网插件库进行搜索,基本上,每个插件提供的都是一个函数,函数调用结果会返回一个转换流,这样就可以实现一个文件里的转换。

创建一个压缩css的任务,这里用到插件gulp-clean-css
全局安装开发依赖 yarn gulp-clean-css --dev

const { src, dest } = require('gulp');
// 压缩css
const cleanCss = require('gulp-clean-css');

function streamTask() {
  return src('src/*.css', { base: 'src' })  // base:基准路径,指定按src原有目录输出
  .pipe(cleanCss())
  .pipe(dest('dist'))
}

exports.default = streamTask;  // 导出默认任务
// 或
module.export = { streamTask } // 导出命名任务

执行命令行指令yarn gulp执行gulpfile.js的默认任务(yarn gulp streamTask执行指定任务),下图可见src目录下的index.css文件被压缩成功。

image.png

通配符 *
src/*.css : src根目录下的css
src/**/*css: 子文件的通配方式,src任意子目录下的css
src/**: src任意子目录下所有文件

组合任务

Gulp 提供了两个强大的组合方法: series()parallel(),允许将多个独立的任务组合为一个更大的操作

series() :串联,按先后顺序执行任务
parallel(): 并行,同时执行多个任务

const { src, dest, series, parallel } = require('gulp');
function css() {...}
function html() {...}
function js() {...}
// css、html、js 互不影响,可以同时执行,创建一个组合任务并行执行
const complicate = parallel(cssStyle, sassStyle, js, page)

module.exports = {
  complicate
};

image.png

配置热更新开发服务器

使用 browser-sync

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

function serve() {
    bs.init({
        server: {
            baseDir: 'dir'
        }
    })
}

一个基础的自动化构建完整配置如下:

主要实现功能:

  • 转换SCSS、html模板、ES6
  • 压缩css、html、js、图片、字体文件
  • 输出静态文件
  • 清除文件
  • 文件引用处理(构建注释转换)
  • 监听文件变化
  • 配置热更新开发服务器
  • 对外暴露公有任务(clean、dev、build)
/**
 * src 读取流,dest 终止/输出流;
 * 组合任务:series 串联; parallel 并行
 * watch  监听文件,发生变化了就执行对应任务,把dist目录对应的文件覆盖
**/

const { src, dest, series, parallel, watch } = require('gulp');
// // 清除文件,串联操作,需要先删除后执行构建任务
const del = require('del')
// 压缩css
const cleanCss = require('gulp-clean-css');
// 重命名
const rename = require('gulp-rename');
// gulp-sass sass:把sass转换成css
const sass = require('gulp-sass')(require('sass'));
// gulp-babel @babel/core @babel/preset-env :转换脚本,转换ES6及以上的新特性
const babel = require('gulp-babel')
// html模板文件转换
const swig = require('gulp-swig')
// 图片文件转换,imagemin通过C++完成的模块,需要同时下载二进制的程序集(github上下载)
/**
 * gulp-imagemin安装最新版可能发生一下错误信息,此时重装一个低版本的就可以
    node_modules\.bin\gulp image
    Error [ERR_REQUIRE_ESM]: require() of ES Module 
    gulpfile.js not supported.
    Instead change the require of index.js
 * */
const imagemin = require('gulp-imagemin')
// 文件引用处理、压缩合并
const useref = require('gulp-useref')
// 条件插件
const ifPlugin = require('gulp-if')
// 文件压缩
const htmlmin = require('gulp-htmlmin')
const uglify = require('gulp-uglify')

// 创建开发服务器
const browserSync = require('browser-sync')
const bs = browserSync.create()

function serve() {
    watch('./src/assets/styles/*.css', cssStyle)
    watch('./src/assets/styles/*.scss', sassStyle)
    watch('./src/**/*.js', js)
    watch('./src/**/*.html', page)
    // watch('./src/assets/images/**', image) 
    // watch('./src/assets/fonts/**', font)
    // watch('public/**', extra)

    // 尽量减少构建次数,此处设置监听images、fonts、public文件,发生变化时通知浏览器,触发bs.reload重新获取最新文件
    watch([
        'src/assets/images/**',
        'src/assets/fonts/**',
        'public/**'
    ], bs.reload)

    bs.init({
        port: 2000,  // 端口
        files: 'dist/**',  // dist文件发生变化时,自动通知浏览器,实现源代码修改时会自动同步到浏览器
        server: {
            // 提高构建效率:启动开发服务器需要先从temp里找,temp里面才是打包好的html、css、js
            baseDir: ['temp', 'src', 'public'],
            routes: {
                '/node_modules': 'node_modules'
            }
        }
    })
}

// 清空正式环境目录dist,临时目录/语法编译的中间目录temp(开发环境构建文件放在临时目录temp)
function clean () {
    return del(['dist', 'temp'])
}

function cssStyle() {
  return src('./src/assets/styles/*.css', { base: 'src' })
  .pipe(cleanCss())
  .pipe(rename({ extname: ".min.css" }))
  .pipe(dest('temp'))
}
// _. 开头的scss文件,会被默认为是mian.css的依赖文件,被打包在mian.css文件内
function sassStyle() {
  return src('./src/assets/styles/*.scss', { base: 'src' })
  .pipe(sass({ outputStyle: 'expanded' })) // 设置输出样式完全展开
  .pipe(cleanCss())
  .pipe(rename({ extname: ".min.css" }))
  .pipe(dest('temp'))
}

function js() {
  return src('./src/**/*.js', { base: 'src' })
  .pipe(babel({ presets: ['@babel/preset-env'] })) // babel 只是一个ES的转换平台,本身不做转换,只是提供一个环境,实现转换的是内部的一些插件,preset是插件的集合
  .pipe(dest('temp'))
}

// html模板内部使用的数据,通过swig({ data })传入
const data = {
    pkg: {
        name: 'lili',
        description: '第一次自动化构建'
    }
}
function page() {
  return src('./src/**/*.html', { base: 'src' })
  .pipe(swig({data}))
  .pipe(dest('temp'))
}

// 无损压缩,只是删减一些图片信息
function image() {
  return src('./src/assets/images/**', { base: 'src' })
  .pipe(imagemin())
  .pipe(dest('dist'))
}

// 字体文件同样采用imagemin插件,可以压缩里面的svg图片,其他字体文件默认不处理直接输出
function font() {
  return src('./src/assets/fonts/**', { base: 'src' })
  .pipe(imagemin())
  .pipe(dest('dist'))
}

// 对额外的静态文件做拷贝--只在部署正式环境打包的时候需要
function extra() {
  return src('public/**', { base: 'public' })
  .pipe(dest('dist/public'))
}

function userefTask() {
    return src('temp/*.html', { base: 'temp' })
    .pipe(useref({ searchPath: ['temp', '.'] })) // 对代码中的构建注释做转换
    .pipe(ifPlugin(/\.js$/, uglify()))
    .pipe(ifPlugin(/\.css$/, cleanCss()))
    .pipe(ifPlugin(/\.html$/, htmlmin(
        {
        // 对html内联js、css压缩
        collapseWhitespace: true,
        minifyCSS: true,
        minifyJS: true
    }
    )))
    .pipe(dest('temp'))
}

const complicate = parallel(sassStyle, cssStyle, js, page)

const build = series(clean, parallel(series(complicate, userefTask), image, font, extra))
const dev = series(complicate, serve)

module.exports = {
  clean,
  build,
  dev
};

自动加载所有gulp插件

gulpfile.js文件中,我们避免不了引入各种各样的gulp插件,因此需要编写很多重复性的引入代码,这里可以使用gulp-load-plugins 一次性自动加载所有gulp插件。同样在终端运行命令行安装这个开发依赖。

const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()

上文中所有使用到gulp插件的地方,可以做如下示例优化:
cleanCss() 改为 plugins.cleanCss(),这样可以省略单独引入插件的代码

function cssStyle() {
  return src('./src/assets/styles/*.css', { base: 'src' })
  .pipe(plugins.cleanCss())
  .pipe(dest('dist'))
}

配置package.json,便于协作开发和使用

把暴露出来的任务,在package.jsonscripts选项进行配置。这样我们终端就可以使用快捷方式执行gulp这些任务。如yarn cleanyarn buildyarn start

  "scripts": {
    "clean": "gulp clean",
    "build": "gulp build",
    "start": "gulp dev"
  },

封装工作流

提取数据

  • 上文data数据不应该放在gulpfile.js文件中,需要把这个数据提取出来放在一个文件中,再使用require引入
  • 提供可配置的能力,配置中写死的绝对路径如'./src/assets/images/**'抽象成可配置

image.png

创作不易,麻烦动动手指头点个赞,欢迎给我留言~~~