vlambda博客
学习文章列表

Gulp 自动化构建实用配置


如今在三大主流框架的引领下,网页的开发方式越来越倾向于单页应用,依靠 webpack 的模块化打包机制,处理应用所需的所有资源。单页应用有它的优势,但也有不那么尽如人意的地方,某些时候,我们可能更希望回归到 HTML + CSS + JS 的纯粹开发方式,因为采用这种开发方式开发的应用具有更高的灵活性。

这篇文章就来讲一讲使用 Gulp 做自动化构建时的实用配置,对 Gulp 不是很熟悉的朋友可以先行查阅官方文档。

构建示例

和 webpack 类似,Gulp 也是通过各种插件来对文件进行处理的,本文中将使用到的所有插件如下所示:

{
  "name""gulp-config",
  "version""1.0.0",
  "dependencies": {},
  "devDependencies": {
    "@babel/core""^7.7.7", //Babel 相关
    "babel-preset-env""^1.7.0",//Babel 相关
    "gulp""^4.0.2",
    "gulp-autoprefixer""^7.0.1",//CSS前缀
    "gulp-babel""^8.0.0",//Babel
    "gulp-clean""^0.4.0",//清空文件夹
    "gulp-clean-css""^4.2.0",//压缩CSS
    "gulp-concat""^2.6.1",//合并文件
    "gulp-htmlmin""^5.0.1",//压缩HTML
    "gulp-imagemin""^6.2.0",//压缩图片
    "gulp-load-plugins""^2.0.2",//自动加载所有gulp插件
    "gulp-rev""^9.0.0",//为文件名添加hash
    "gulp-rev-collector""^1.3.1",//替换文件中的资源链接
    "gulp-sass""^4.0.2",//编译sass
    "gulp-uglify""^3.0.2"//压缩JS
  }
}

每个插件的用途会在后文一一展开,这里就不再赘述了。

准备工作

新建一个项目,结构大致如下:

Gulp 自动化构建实用配置
content

运行 npm install 安装所有插件。

根目录下的 gulpfile.js 文件,用于编写各种构建方法,当运行 gulp 命令时,Gulp 会自动读取此文件并用文件中的方法进行构建。打开此文件,写入下列代码:

const {src, dest, series} = require('gulp');
const plugins = require('gulp-load-plugins')();

console.log(plugins);

执行 gulp,控制台将打印出下面的内容:

autoprefixer: [Getter],
  babel: [Getter],
  clean: [Getter],
  cleanCss: [Getter],
  concat: [Getter],
  htmlmin: [Getter],
  imagemin: [Getter],
  rev: [Getter],
  revCollector: [Getter],
  sass: [Getter],
  uglify: [Getter] }

这是一个包含本文中使用到的所有 Gulp 插件的对象,是 gulp-load-plugins 插件生成的,后期可以通过 plugins 直接调用对应的插件,相对于手动引入每个插件来说,这种方法大大减少了工作量。

处理图片

对于图片,最主要的处理就是进行压缩,以减小其体积。

gulpfile.js 中添加处理图片的方法:

//...
function handleImage({
  return src('src/img/*')
    .pipe(plugins.imagemin({
      optimizationLevel3,
      progressivetrue,
      interlacedtrue,
      multipasstrue
    }))
    .pipe(plugins.rev())
    .pipe(dest('dist/img'))
    .pipe(plugins.rev.manifest('dist/rev-manifest.json', {
      base'dist',
      mergetrue
    }))
    .pipe(dest('dist'));
}

exports.default = series(handleImage);

首先使用 Gulp 的 src 方法读取 src/img/ 下的所有图片(用一个 * 可以匹配任意文件,用两个 ** 可以匹配任意目录及文件),这里并没有指定后缀名,因为图片有多种格式,方便扩展。

第一个 pipe中,使用 gulp-imagemin 对图片进行压缩。gulp-imagemin 内部使用了几个不同的插件对不同的图片格式进行处理,用户可以自定义需要使用的插件,详情可以点此查看,本文中使用的是标准模式。

第二个 pipe 中,使用 gulp-rev 对压缩后的图片进行改名。

第三个 pipe 中,使用 Gulp 的 dest 方法将改名后的图片输出到 dist 文件夹下的 img 文件夹中,输出时如果没有对应的文件夹,Gulp 会自动创建:

Gulp 自动化构建实用配置
imgs

第四个 pipe 中,使用 gulp-rev 插件的 manifest 方法输出一个 rev-manifest.json 文件(文件名可自定义),将原始图片的名称和更改后的名称进行映射,manifest 方法接收两个参数,第一个是文件的输出路径,第二个是配置对象,详情可以点此查看。

本文使用了两项配置,详见代码,重点提一下 merge 配置,每次使用 manifest 方法时都可以选择是输出一个新的映射文件还是在已有的映射文件上做增量输出。如果选择做增量输出,则需设置输出路径相同,且 merge 值为 true。当项目较大时,输出的单个映射文件体积可能会比较大,朋友们自行决定输出方式。

第五个 pipe 中,使用 Gulp 的 dest 方法将 rev-manifest.json 文件输出到 dist 文件夹中:

Gulp 自动化构建实用配置
rev_manifest

其内容如下:

{
  "bg.svg""bg-55b70eeb5f.svg",
  "logo.png""logo-d23239528e.png"
}

gulp-rev 在整个构建中会使用很多次,后文将不再赘述。

之所以首先讲解图片的处理,是因为图片会被 CSS、HTML 等文件引用,必须先拿到改名前后的映射关系。

处理字体图标

字体文件会被其他的 CSS 文件引用,因此也需要优先处理。

gulpfile.js 中添加处理字体图标的方法:

//···
function handleFont({
  return src('src/font/*')
    .pipe(plugins.cleanCss())
    .pipe(dest('dist/font'));
}
exports.default = series(handleImage, handleFont);

字体图标的处理比较简单,使用 gulp-clean-css 将 CSS 文件压缩一下即可:

Gulp 自动化构建实用配置
font

字体图标文件夹中不仅有 CSS 文件,因此不能指定后缀名,否则将会只读取后缀名相匹配的文件。

清空文件夹

在开发过程中,我们可能会因为某些情况需要调整各种文件的输出目录,如果不清除已输出的文件,会导致最终的包中出现很多重复文件,因此最好在输出前清空 dist 文件夹。

gulpfile.js 中添加清空文件夹的方法:

function clearFolder({
  return src('dist', {
    readfalse//此时不需要读取文件内容
    allowEmpty: true //允许为空,确保即使没有dist文件夹也不会出错
  })
    .pipe((plugins.clean({
      forcetrue //确保清除没有问题
    })));
}
exports.default = series(clearFolder, handleImage, handleFont);

如果不设置 allowEmpty:true,在初次运行时并没有 dist 目录会报错,因为不存在该文件夹。gulp-clean 的配置可以点此查看。

需要注意的是,必须保证清空文件夹的操作处于 series 方法第一位,否则它会清空在其之前的方法所输出的所有文件。

处理 CSS

gulpfile.js 中添加处理 CSS 的方法:

//···
function handleCss({
  return src(['dist/rev-manifest.json''src/css/*.scss'])
    .pipe(plugins.revCollector({
      replaceRevedtrue,//
    }))
    .pipe(plugins.sass())
    .pipe(plugins.autoprefixer())
    .pipe(plugins.cleanCss({compatibility'ie9'}))
    .pipe(plugins.rev())
    .pipe(dest('dist/css'))
    .pipe(plugins.rev.manifest('dist/rev-manifest.json', {
      base'dist'//修改rev-manifest.json文件的基础路径
      merge: true //是否合并已有的rev-manifest.json文件
    }))
    .pipe(dest('dist'));
}
exports.default = series(clearFolder, handleImage, handleFont, handleCss);

src 方法中读取 dist 文件夹下的映射文件 rev-manifest.json 和开发目录 src/css/ 下的所有需要输出的 .scss 文件。对于 variables.scssmixins.scss 等基础文件,并不需要输出,因此大家可以自行组织文件结构,忽略掉这些文件。

第一个 pipe 中,使用 gulp-rev-collector 插件替换 .scss 文件中的图片等资源的路径及名称,使用此插件时必须保证它处于第一位。

第二个 pipe 中,使用 gulp-sass 插件将 .scss 文件编译成 .css 文件。

第三个 pipe 中,使用 gulp-autoprefixer 插件为部分 CSS 属性添加浏览器前缀,提高兼容性。

第四个 pipe 中,使用 gulp-clean-css 插件对 CSS 文件进行压缩,该插件还可以接收一个配置对象,在压缩时使用额外的功能,详情可以点此查看。我们这里将 compatibility 设置为 ie9,表示最终的属性兼容到 IE9 以上。

其余的 pipe 功能前文已经讲过,就不再赘述了。

输出的结果如下:

Gulp 自动化构建实用配置
css

随便打开一个有引用图片的 CSS 文件,可以看到图片的路径和名称都被替换了:

Gulp 自动化构建实用配置
css_img

处理 JS

gulpfile.js 中添加处理 JS 的方法:

function handleJs({
    return src('src/js/*.js')
        .pipe(plugins.babel())
        .pipe(plugins.uglify())
        .pipe(plugins.rev())
        .pipe(dest('dist/js'))
        .pipe(plugins.rev.manifest('dist/rev-manifest.json', {
          base'dist'//修改rev-manifest.json文件的基础路径
          merge: true //是否合并已有的rev-manifest.json文件
        }))
        .pipe(dest('dist'));
}
exports.default = series(clearFolder, handleImage, handleFont, handleCss, handleJs);

src 方法中读取 src/js/ 下的所有需要输出的 .js 文件,对于 utils.js 等基础文件,并不一定需要输出,因此朋友们可自行组织文件结构,并选择是否忽略掉这些文件。

第一个 pipe 中,使用 gulp-babel 插件编译使用 ES6 及以后语法的代码,Babel 的相关配置可以点此查看。

第二个 pipe 中,使用 gulp-uglify 插件对 JS 文件进行压缩,该插件也可以接收一个配置对象,详情可以点此查看。

其余的 pipe 功能前文已经讲过,就不再赘述了。

输出的结果如下:

Gulp 自动化构建实用配置
js

处理三方资源

gulpfile.js 中添加处理三方资源的方法:

function handleVendor({
    return src('src/vendor/**')
        .pipe(dest('dist/vendor'));
}
exports.default = series(clearFolder, handleImage, handleFont, handleCss, handleJs, handleVendor);

三方资源一般是项目中使用到的外部资源,如富文本编辑器、可视化工具等,对于这些资源,多数情况下都可以使用 CDN,但也存在少数不能使用 CDN 的情况。

对于不能使用 CDN 的情况,我们可以下载相应的生产版代码放到自己的项目中,生产版代码都是已经经过优化、压缩过的,所以直接复制到生产版代码目录下即可:

vendor

处理 HTML

gulpfile.js 中添加处理 HTML 的方法:

function handleHtml({
    return src(['dist/*.json''src/*.html'])
        .pipe(plugins.revCollector({
            replaceRevedtrue,
        }))
        .pipe(plugins.htmlmin({
            removeCommentstrue,
            collapseWhitespacetrue,
            collapseBooleanAttributestrue,
            removeEmptyAttributestrue,
            removeScriptTypeAttributestrue,
            removeStyleLinkTypeAttributestrue,
            minifyJStrue,
            minifyCSStrue
        }))
        .pipe(dest('dist'));
}
exports.default = series(clearFolder, handleImage, handleFont, handleCss, handleJs, handleVendor, handleHtml);

第一个 pipe 中,使用 gulp-rev-collector 插件替换 .html 文件中的图片、CSS 文件、JS 文件等资源的路径及名称。

第二个 pipe 中,使用 gulp-htmlmin 插件对 HTML 文件进行压缩,该插件可以接收一个配置对象,详情可以点此查看。

输出的结果如下:

html

结语

单就本文的构建内容而言,webpack 也可以做到,只是配置写起来可能会稍微复杂一点。具体使用哪种工具,还是要根据实际需求决定。

文中的配置是一个比较实用的配置,能适用于多数场景,当有特殊需求时大家可以自行选择相应插件进行处理。值得一提的是,可以将各种文件的路径写成一个配置文件,后期调整架构时更便于修改。

最后,附上完整代码:

const {src, dest, series} = require('gulp');
const plugins = require('gulp-load-plugins')();
console.log(plugins);

function clearFolder({
    return src('dist', {readfalseallowEmptytrue}).pipe((plugins.clean({forcetrue})));
}

function handleImage({
    return src('src/img/*')
        .pipe(plugins.imagemin({
            optimizationLevel3,
            progressivetrue,
            interlacedtrue,
            multipasstrue
        }))
        .pipe(plugins.rev())
        .pipe(dest('dist/img'))
        .pipe(plugins.rev.manifest('dist/rev-manifest.json', {
            base'dist',
            mergetrue
        }))
        .pipe(dest('dist'));
}

function handleFont({
    return src('src/font/*')
        .pipe(plugins.cleanCss())
        .pipe(dest('dist/font'));
}

function handleCss({
    return src(['dist/*.json''src/css/*.scss'])
        .pipe(plugins.revCollector({
            replaceRevedtrue,
        }))
        .pipe(plugins.sass())
        .pipe(plugins.autoprefixer())
        .pipe(plugins.cleanCss({compatibility'ie9'}))
        .pipe(plugins.rev())
        .pipe(dest('dist/css'))
        .pipe(plugins.rev.manifest('dist/rev-manifest.json', {
            base'dist',
            mergetrue
        }))
        .pipe(dest('dist'));
}

function handleJs({
    return src('src/js/*.js')
        .pipe(plugins.babel())
        .pipe(plugins.uglyfly())
        .pipe(plugins.rev())
        .pipe(dest('dist/js'))
        .pipe(plugins.rev.manifest('dist/rev-manifest.json', {
            base'dist',
            mergetrue
        }))
        .pipe(dest('dist'));
}

function handleVendor({
    return src('src/vendor/**')
        .pipe(dest('dist/vendor'));
}

function handleHtml({
    return src(['dist/*.json''src/*.html'])
        .pipe(plugins.revCollector({
            replaceRevedtrue,
        }))
        .pipe(plugins.htmlmin({
            removeCommentstrue,
            collapseWhitespacetrue,
            collapseBooleanAttributestrue,
            removeEmptyAttributestrue,
            removeScriptTypeAttributestrue,
            removeStyleLinkTypeAttributestrue,
            minifyJStrue,
            minifyCSStrue
        }))
        .pipe(dest('dist'));
}

exports.default = series(clearFolder, handleImage, handleFont, handleCss, handleJs, handleVendor, handleHtml);

首图由Free-Photos在Pixabay上发布