微信小程序源码压缩探索

微信小程序主包具有2M的最大限制,因此压缩程序源码成为一个优化的可能。下面是一些探索的结论以及为解决问题而做的一些方案。

1. 一个结论

先说一个结论:压缩JS文件和WXSS文件对于缩小主包体积是没有作用的,jswxss文件的确是源码中最大的两个部分,但是小程序开发工具在打包上传的时候可以设置自动压缩这两部分,因此我们不必做多余的动作(实验发现做了也是无用的)。
在这里插入图片描述

2. 压缩WXML

WXML的压缩和单纯的HTML压缩有两个主要的不同点:

  1. WXML里面可能存在WXS脚本,而WXS的处理不同于WXML的处理。
  2. WXML的属性是区分大小写的,因此采用常见的gulp插件(如gulp-htmlmin)压缩WXML可能改变这种这种属性,同时它也不能处理wxs

对于第一种情况,首先的一点建议是总是将wxs脚本单独放在一个文件中,而不是内联在WXML中,这样做的好处是可以将wxs作为一个标签来处理,而wxs文件也可以做单独处理。

由于我们不可以避免的会引入别人的npm包,而别人可能不采用这种规范(如iview),所以在处理wxml压缩的时候仍然不可避免的要考虑这种情况。

对于第二种情况,既然压缩htmlgulp插件可能对wxml有副作用,那么可以考虑以下两个解决方案:

  1. 自己开发一个gulp插件。
  2. 自己开发一个内联插件(内联插件介绍)。

下面是我开发的一个压缩WXML的内联插件示例(全部的代码将会在后面展示):

function minifyWXML() {
  return through2.obj(function (file, _, cb) {
    if (file.isBuffer()) {
      let code = file.contents.toString();
      const result = code.match(/<wxs(.*?)>[\s\S]*(<\/wxs>)/g); // 提取wxs内容
      code = code.replace(/<wxs(.*?)>[\s\S]*(<\/wxs>)/g, '')
        .replace(/\n+|\t+/g, ' ') // 删除换行和缩进
        .replace(/\s+/g, ' ') // 删除多个空格
        .replace(/(>\s)/g, '>') // 删除标签前面和后面的空格
        .replace(/(\s<)/g, '<')
        .replace(/<!--[\w\W\r\n]*?-->/g, '') // 删除注释
      if (result) {
        code = result.join('') + '\n' + code; // 将wxs拼接到wxml的前面
      }
      // eslint-disable-next-line no-param-reassign
      file.contents = Buffer.from(code);
    }
    cb(null, file);
  });
}

对于wxml中wsx,这里只是将它提取了出来,然后放到文件的头部,有两个原因:

  1. 压缩这一个部分可能导致未知的问题,例如函数名被替换,不同人的编码习惯导致压缩后代码出错等。
  2. wxs中的内容在整个应用中占比是比较小的。

删除换行和缩进的时候一定要留一个空格,因为我们的组件通常是以下方式书写的
在这里插入图片描述
若不换行可能导致出现<viewwx:for....>这种无法识别的标签。

最后得到的结果是这样的(压缩iview的代码),对于一个比较大的项目来说,这种压缩大致可以减少100kb左右。
在这里插入图片描述

3. 压缩json

压缩json比较简单,直接使用gulp-jsonminify即可,这种压缩也可以减少几十kb。

4. 压缩图片

这里不做图片压缩,理由如下:

  1. 图片应尽量放在cdn里而不是本地。
  2. 对于少量的图片可以事先设计好,或者使用字体图标。

5. wxss的压缩

css压缩没什么用,这里还是简单介绍一下吧,由于wxss里面的@import和css的import语法不同,因此不能使用通常的css gulp插件压缩,下面是一个我实现的内联插件:

function minifyWXSS() {
  return through2.obj(function (file, _, cb) {
    if (file.isBuffer()) {
      let code = file.contents.toString();
      code = code.replace(/\/\*[\s\S]*\*\//g, '');
      const result = code.match(/@import\s+["'].*["'];?/g);
      code = code.replace(/@import\s+["'].*["'];?/g, '');
      code = cleanCSS.minify(code).styles;
      if (result) {
        code = result.join('\n') + '\n' + code;
      }
      // eslint-disable-next-line no-param-reassign
      file.contents = Buffer.from(code);
    }
    cb(null, file);
  });
}

6. 总结

总的来说就是, 压缩小程序代码就是将所有代码处理后放在一个新的目录下,相当于新建了一个应用,然后改变project.config.json下的miniprogramRoot为新目录即可。建议miniprogram_npm下的文件也一起压缩。这种压缩可以获得100-200多kb的空间。
在这里插入图片描述

下面是完整的代码gulpfile文件代码(gulp版本>=4.0.0):

/**
 * 微信小程序优化压缩工具
 */
const gulp = require('gulp');
const colors = require('colors');
const pump = require('pump');
const jsonminify = require('gulp-jsonminify');
const del = require('del');
const size = require('gulp-size');
const terser = require('gulp-terser');
const through2 = require('through2');
const CleanCSS = require('clean-css');

const JsSizeBefore = size({ title: 'js压缩前:' });
const JsSizeAfter = size({ title: 'js压缩后:' });
const WXMLSizeBefore = size({ title: 'WXML压缩前:' });
const WXMLSizeAfter = size({ title: 'WXML压缩后:' });
const WXSSSizeBefore = size({ title: 'WXSS压缩前:' });
const WXSSSizeAfter = size({ title: 'WXSS压缩后:' });
const JSONSizeBefore = size({ title: 'JSON压缩前:' });
const JSONSizeAfter = size({ title: 'JSON压缩后:' });
const OtherSize = size({ title: '其它文件:' });

function getSize(SizeObj) {
  return SizeObj.size || 0;
}

function getTotalSize() {
  const before = getSize(JsSizeBefore.size) + getSize(WXMLSizeBefore.size)
    + getSize(WXSSSizeBefore.size) + getSize(JSONSizeBefore.size) + getSize(OtherSize.size);
  const after = getSize(JsSizeAfter.size) + getSize(WXMLSizeAfter.size)
    + getSize(WXSSSizeAfter.size) + getSize(JSONSizeAfter.size) + getSize(OtherSize.size);

  return {
    beforeSize: `${(before / 1024).toFixed(3)}KB`,
    afterSize: `${(after / 1024).toFixed(3)}KB`,
  };
}

const cleanCSS = new CleanCSS({
  units: {
    rpx: true,
  },
});

function minifyWXML() {
  return through2.obj(function (file, _, cb) {
    if (file.isBuffer()) {
      let code = file.contents.toString();
      const result = code.match(/<wxs(.*?)>[\s\S]*(<\/wxs>)/g);
      code = code.replace(/<wxs(.*?)>[\s\S]*(<\/wxs>)/g, '')
        .replace(/\n+|\t+/g, ' ')
        .replace(/\s+/g, ' ')
        .replace(/(>\s)/g, '>')
        .replace(/(\s<)/g, '<')
        .replace(/<!--[\w\W\r\n]*?-->/g, '');
      if (result) {
        code = result.join('') + '\n' + code;
      }
      // eslint-disable-next-line no-param-reassign
      file.contents = Buffer.from(code);
    }
    cb(null, file);
  });
}

function minifyWXSS() {
  return through2.obj(function (file, _, cb) {
    if (file.isBuffer()) {
      let code = file.contents.toString();
      code = code.replace(/\/\*[\s\S]*\*\//g, '');
      const result = code.match(/@import\s+["'].*["'];?/g);
      code = code.replace(/@import\s+["'].*["'];?/g, '');
      code = cleanCSS.minify(code).styles;
      if (result) {
        code = result.join('\n') + '\n' + code;
      }
      // eslint-disable-next-line no-param-reassign
      file.contents = Buffer.from(code);
    }
    cb(null, file);
  });
}

// 配置项
const conf = {
  // 开发目录
  devPath: 'src',
  // 编译目录
  prodPath: 'dist',
  filesPath: {
    // js文件
    js: '/**/*.js',
    // wxss文件
    wxss: '/**/**.wxss',
    // wxml文件
    wxml: '/**/**.wxml',
    wxs: '/**/**.wxs',
    // json文件
    json: '/**/**.json',
    // 其它(jpg|jpeg|png|gif)
    other: '/**/{**.jpg,**.jpeg,**.png,**.gif,**.webp,**.eot,**.ttf,**.woff,**.woff2,**.svg}',
  },
  ignore: ['/node_modules/**'],
};
conf.ignore = conf.ignore.map(path => `!${conf.devPath}${path}`);

// 显示时间
const getTime = function () {
  return '[' + colors.white(new Date().getHours() + ':' + new Date().getMinutes() + ':' + new Date().getSeconds()) + '] ';
};

// 输出log
const _log = function (msg) {
  console.log(getTime() + msg);
};

/**
 * 优化js文件
 * @param filePath
 */
const _optJS = function (filePath) {
  _log(colors.white('对 js 文件进行优化...'));
  return pump([gulp.src(filePath, {
    base: conf.devPath,
  }),
  JsSizeBefore,
  terser({
    keep_classnames: true,
    keep_fnames: true,
  }).on('error', function (err) {
    _log('【错误】 '.red + '文件: ' + err.fileName + ', ' + err.cause);
  }),
  JsSizeAfter,
  gulp.dest(conf.prodPath)]);
};

/**
 * 优化wxss文件
 * @param filePath
 */
const _optWXSS = function (filePath) {
  _log(colors.white('对 wxss 文件进行优化...'));
  return gulp.src(filePath, {
    base: conf.devPath,
  })
    .pipe(WXSSSizeBefore)
    .pipe(minifyWXSS())
    .pipe(WXSSSizeAfter)
    .pipe(gulp.dest(conf.prodPath));
};

/**
 * 优化json文件
 * @param filePath
 * @param cb
 * @private
 */
const _optJSON = function (filePath) {
  _log(colors.white('对 json 文件进行优化...'));
  return gulp.src(filePath, {
    base: conf.devPath,
  })
    .pipe(JSONSizeBefore)
    .pipe(jsonminify())
    .pipe(JSONSizeAfter)
    .pipe(gulp.dest(conf.prodPath));
  // cb();
};

/**
 * 优化wxml文件
 * @param filePath
 * @private
 */
const _optWXML = function (filePath) {
  _log(colors.white('对 wxml 文件进行优化...'));
  return gulp.src(filePath, {
    base: conf.devPath,
  }).pipe(WXMLSizeBefore)
    .pipe(minifyWXML())
    .pipe(WXMLSizeAfter)
    .pipe(gulp.dest(conf.prodPath));
};

/**
 * 复制文件
 * @param filePath
 */
const _copyFiles = function (filePath) {
  return gulp.src(filePath, {
    base: conf.devPath,
  }).pipe(OtherSize).pipe(gulp.dest(conf.prodPath));
};

// 清理编译目录
gulp.task('clean', function () {
  _log(colors.white('开始清理 ' + conf.prodPath + ' 目录...'));
  return del([conf.prodPath] + '/**/*').then(function (paths) {
    _log(colors.white('清理完毕, 共清理 ' + colors.red(paths.length) + ' 个文件和目录。'));
  });
});

// 优化js
gulp.task('js', function () {
  const files = [
    conf.devPath + conf.filesPath.js,
    ...conf.ignore,
  ];
  return _optJS(files);
});

// 优化wxss
gulp.task('wxss', function () {
  const files = [
    conf.devPath + conf.filesPath.wxss,
    ...conf.ignore,
  ];
  return _optWXSS(files);
});

// 优化wxml
gulp.task('wxml', function () {
  const files = [
    conf.devPath + conf.filesPath.wxml,
    ...conf.ignore,
  ];
  return _optWXML(files);
});

// 优化json
gulp.task('json', function () {
  const files = [
    conf.devPath + conf.filesPath.json,
    ...conf.ignore,
  ];
  return _optJSON(files);
});

// 优化其它
gulp.task('other', function () {
  const files = [
    conf.devPath + conf.filesPath.other,
    conf.devPath + conf.filesPath.wxs,
    conf.devPath + conf.filesPath.wxss,
    conf.devPath + conf.filesPath.js,
    ...conf.ignore];
  // eslint-disable-next-line no-useless-concat
  _log(colors.white('对 ' + 'jpg/jpeg/png/gif/svg' + ' 文件进行优化...'));
  return _copyFiles(files);
});

// 计时
let startTime;
let endTime;
// 开始任务
gulp.task('startProd', function (cb) {
  startTime = new Date().getTime();
  _log(colors.green('开始优化, 请稍候...'));
  cb();
});

gulp.task('endProd', function (cb) {
  _log(colors.green('优化完毕, 请查看 ' + conf.prodPath + ' 目录。'));
  const totalSize = getTotalSize();
  _log(colors.green(`压缩前总尺寸${totalSize.beforeSize},压缩后总尺寸:${totalSize.afterSize}`));
  endTime = new Date().getTime();
  _log(colors.green('耗时: ' + (endTime - startTime) + 'ms'));
  cb();
});

// 编译任务
// gulp.task('default', gulp.series('startProd', 'clean', gulp.parallel('other', 'json', 'wxml', 'wxss', 'js'), 'endProd'));
gulp.task('default', gulp.series('startProd', 'clean', gulp.parallel('other', 'json', 'wxml'), 'endProd'));

参考资料:wxmini-optimize

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值