Gulp,1: 打包压缩小程序 小程序瘦身

在当今前端技术愈加成熟的环境下,小程序的压缩瘦身,相对变得容易多了。

webpack 中一切皆模块,基于其丰富的自由配置,会从入口处对所有依赖进行整合并重新分配,本是一个极大的优势,但在小程序中却也是一个弊端。

相比较而言,gulp 任务规划,功能明确,运行可控,对于小程序这样的对文件索引更严格的模式下,更显得得心应手。

1. 安装 gulp

选择一个目录,安装 gulp

npm i -D gulp

创建配置文件 gulpfile.js,输入:


function defaultTask(cb) {
  // task
  console.log('hello gulp')
  cb();
}
exports.default = defaultTask

在 package.json 中添加脚本命令 "dev": "gulp" , 然后命令行执行 npm run dev,就会执行默认任务,并打印 ' hello gulp '。

创建文件夹 demo,并在其中创建 test.js、common.css 等测试文件,填充基本内容。为后续操作做准备。

2. 基本配置

可以说,gulp是面向任务的工具,所有功能都由一个个任务组合而成,每个任务都是一个函数。

处理小程序中的文件,最主要的api 是 src()读取文件流、 dest()输出文件流、 pipe()处理数据流。

如下,先安装 gulp-babelgulp-uglify两个插件用于转义es6 并压缩js。

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

gulpfile.js 中改为:


const { src, dest } = require('gulp');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');

exports.default = function() {
  return src('demo/*.js')
    .pipe(babel())
    .pipe(uglify())
    .pipe(dest('dist/'));
}

src()dest()都是接受 glob 参数,传入地址目录,此处不再展开。

值得注意的是,gulp 不支持同步任务,意味着,所有的任务必须使用 callback 或 async函数,或返回 stream、promise、event emitter、child process、observable。若在使用中出现 "Did you forget to signal async completion?" 警告,表示未使用前面提到的返回方式。

执行 npm run dev即可看到,同级目录内出现 dist 文件夹,内部包含前面提到的测试文件的压缩版js。

随后引入 gulp-clean-css用于压缩 wxss 文件,gulp-htmlmin用于压缩 wxml,同时需要 gulp-iflazypipe 进行条件编译,对js、wxss、wxml 文件进行分别处理。

npm i -D gulp-clean-css gulp-htmlmin gulp-if lazypipe

gulp-if 第一个参数为判断条件,第二参数为判断为 true 时,执行的任务,第三参数为判断为 false 时,执行的任务,可不传。

对js的处理需要两步操作,首先进行 babel转换,然后 uglify压缩,在这里就需要用 lazypipe 把这两个操作转化为操作链。

注意,lazypipe 实例在调用 pipe() 时,pipe 中函数的参数,需紧跟 pipe 中函数的后面:

lazypipe().pipe(fn[, arg1[, arg2[, ...]]])

并且, pipe 中函数与参数,需分开传入 !! 例如:

lazypipe()
  .pipe( babel, { 
    presets: ["@babel/env"],
  })

Vinyl 是描述文件的元数据对象,同样存在于文件流中,可以使用 Vinyl api 获取文件流的文件类型,如:file.extname = '.js';


const { src, dest} = require('gulp');
const gulpif = require('gulp-if');
const lazypipe = require('lazypipe');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');
const cleanCss = require('gulp-clean-css');
const htmlmin = require('gulp-htmlmin');

const isJS = (file) => file.extname === '.js';
const isCSS = (file) => file.extname === '.wxss' || file.extname === '.css';
const isWXML = (file) => file.extname === '.wxml';

const jsChannel = lazypipe()
  .pipe( babel, { presets: ['@babel/env'] } )
  .pipe( uglify, {
    // 压缩配置
    compress: {
      drop_console: true,
    },
    // 输出配置
    output: {
      comments: false,    // 移除注释
    },
    toplevel: false,    // 混淆最高作用域中的变量和函数名
  })

async function fileHandle() {
  src('demo/**/*', {
    allowEmpty: true
  })
  // 分别处理 js、wxss
  .pipe(gulpif( isJS, jsChannel()))
  .pipe(gulpif( isCSS, cleanCss()))
  // 取消对 wxml 的处理,<input></input>等与 html 中存在冲突
  // .pipe(gulpif( isWXML, htmlmin({
  //   caseSensitive: true,    // 大小写敏感
  //   removeComments: true,   // 删除HTML注释
  //   keepClosingSlash: true, // 单标签上保留斜线
  // })))
  .pipe(dest('dist/'))
}
exports.default = fileHandle

引入 del 库,

npm i -D del

在编译前,清空输出目录,这里涉及到顺序执行,所以还需使用 gulp.series组合任务。

const { series} = require('gulp');
const del = require('del');

// 清理
async function clean(){
  await del(output);
}

exports.clean = clean
exports.default = series( clean, fileHandle)

另外,把输入输出目录 提取出来,统一配置

最后,gulpfile.js 源码如下:


const { src, dest, series} = require('gulp');
const gulpif = require('gulp-if');
const lazypipe = require('lazypipe');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');
const del = require('del');
const cleanCss = require('gulp-clean-css');
const htmlmin = require('gulp-htmlmin');

// const entry = '../wx'    // 小程序地址
const entry = './demo'      // 示例地址
const output = './dist'     // 输出目录


const isJS = (file) => file.extname === '.js';
const isCSS = (file) => file.extname === '.wxss' || file.extname === '.css';
const isWXML = (file) => file.extname === '.wxml';

const jsChannel = lazypipe()
  .pipe( babel, { presets: ['@babel/env'] } )
  .pipe( uglify,  )

// 清理
async function clean(){
  await del(output);
}
async function fileHandle() {
  src([ 
    `${entry}/**/*`,
    `!${entry}/**/.*`,
    `!${entry}/node_modules/*`,
    `!${entry}/**/*.md`,
  ], {
    allowEmpty: true
  })
  // 分别处理 js、wxss、wxml
  .pipe(gulpif( isJS, jsChannel()))
  .pipe(gulpif( isCSS, cleanCss()))
  // 取消对 wxml 的处理,<input></input>等与 html 中存在冲突
  // .pipe(gulpif( isWXML, htmlmin({
  //   caseSensitive: true,    //  大小写敏感
  //   removeComments: true,   //       删除HTML注释
  //   keepClosingSlash: true, // 单标签上保留斜线
  // })))
  .pipe(dest(output))
}

exports.clean = clean
exports.default = series( clean, fileHandle)

package.json

{
  "devDependencies": {
    "@babel/core": "^7.11.1",
    "@babel/preset-env": "^7.11.0",
    "del": "^5.1.0",
    "gulp": "^4.0.2",
    "gulp-babel": "^8.0.0",
    "gulp-clean-css": "^4.3.0",
    "gulp-htmlmin": "^5.0.1",
    "gulp-if": "^3.0.0",
    "gulp-uglify": "^3.0.2",
    "lazypipe": "^1.0.2"
  },
  "scripts": {
    "dev": "gulp",
    "clean": "gulp clean"
  }
}