我真的很想分享这个风骚的「首屏优化」方案

11,767 阅读6分钟

事情的起因是这样的。笔者在摸鱼期间突然发现首屏加载的几个js库的gzip大小只有10几k!!!10几k,这就离谱,是gz压缩的功劳吗?再细看一下,卧槽,不得了,原文件大小也就20来k!好,爱折腾搞事的我,在此就萌生了一个优化的想法了......

本文方案是笔者去年自己琢磨出来的一个优化思路,并不是业界常用的首屏优化方案,适用性方面可能有所限制~主要出发点就是为了提升 gzip 的压缩率,所以才有了本文的优化实现方案的思路。因为在面试中有面试官也认可这种方式,所以打算写成文章把思路分享出来~反正大🔥各取所需!

接下来,让我们一起开始折腾,再提升首屏性能亿点点!

一、优化想法萌生

1. 场景介绍

简单介绍下,笔者负责的项目通过找运维进行黑盒操作,开启服务器 gzip 的资源加载方式,所有资源都进行 gzip 压缩了。其中,除了项目的业务代码,还包括 vuevue-routeraxiosvuexelementui等等这些在打包阶段被我 externals 掉的第三方库

来看看是不是你熟悉的webpack externals 的配置!

configureWebpack: config => {
...
  config.externals = {
    "vue": "Vue",
    "vue-router": "VueRouter",
    "vuex": "Vuex",
    "element-ui": "ELEMENT",
    "axios": "axios"
  };
...    
}

好了,那 externals 的库,你是通过什么方式让页面加载的呢?笔者所在的公司有一个专门放第三库的cdn,所以是直接通过 5 个 script 标签对这几个第三库的 cdn 进行加载~如果你的项目也是这样,那ok,可以接着往下看,如果不是这样的话,那场景可能就不适用了,可以不用再往下看了!

2. gzip的压缩率低

不知道大家有没有注意到这么一点,有一些第三库的包体积,本身就很小!笔者去 node_modules 下找几个库为例:

  1. vue-router.min.js。28k 不到 image.png

  2. vuex.min.js。13k 不到 image.png

  3. axios。18k 不到 image.png

从这里引发一个思考的点,这个包大小本来就不大,那 gzip 压缩完的效果能减少多少体积呢❓这个时候你是不是也跟我想到一起了?有些包压缩完基本没有太大优化空间,比如 vuex 这种,全压完也就只能优化10来k~有没有办法更加优化压缩率呢!接着往下看

二、实践出真知

1. gzip压缩原理

关键的两个: LZ77哈夫曼编码。网络传输的内容都会通过文本的形式传输嘛,所以可以简单理解成对字符串进行压缩处理。这里只会简单描述一下,如果需要深入的话,大家自行去找一下相应的资料看看就好了~

  1. LZ77。只需要知道:第一个字符串的内容 和 重复的字符串 相对于首串的:距离 + 长度即可

    • 比如:0111111001111110 可以换成 01111110(8,8)
  2. 哈夫曼编码。字符重新编码,保证出现频率越高的字符占用的字节越少。

根据以上两点,我们不难发现,压缩的两个关键都是针对 重复 的字符串做手脚!那如果我们的一个文件比较小,那能重复的内容可能就偏少,如果是一个大文件,那么能重复的内容就更多,基于这么一个出发点,我们把小文件都合并起来,看看效果,实践一下!

2. 提升 gzip 的压缩率

不知道读到这里的你会不会跟我一样,想到当年有个前端优化方案叫雪碧图的东西。它的做法就是把多个小图片合成一个大图片,以减少资源的加载次数,并且合并后的图片可能还会比所有原片加起来的总和小!笔者就是这么想,且是这么干的,就是通过把文件合并起来,组成一个更大点的文件,以此来提高文件内容的重复度,提升 gzip 的压缩率!

看下这段 node 的代码,很简单:

const fs = require("fs");
const path = require("path");
const zlib = require("zlib"); // 用了 node 提供的 zlib 模块实现 gz 压缩

// 获取命令参数,如输入命令:node 执行文件.js axios.min.js
const [fileName] = process.argv.slice(2)
// 这里的fileName拿到的就是 axios.min.js 

//调用压缩函数
gzipFn(fileName)

function gzipFn(fileName) {
  const sourcePath = path.resolve(__dirname, fileName);
  // 输出的文件名,gz后缀
  const gzipPath = `${sourcePath}.gz`;

  const gzip = zlib.createGzip();
  // 读原文件 xx.min.js
  const rs = fs.createReadStream(sourcePath);
  // 写入后缀 gz 结尾的新文件
  const ws = fs.createWriteStream(gzipPath);

  rs.on("err", function (err) {
    console.log("gzip run err >>>", err);
  });
  // 使用 gzip 压缩文件
  rs.pipe(gzip).pipe(ws);
}

把案例中 externals 的文件全部 copy 放在一个文件夹下,再全部使用上述的 node 代码进行gzip压缩。执行命令如下,总共6条命令(其中 whole.js 就是把 5 个文件整合在一起的大文件~)

image.png

接下来,我们分别计算5个 gzip 文件大小的总和,再和我们5个文件合并的 whole.js 的 gzip 文件大小进行比较,结果如下图:

5个文件夹起来的计算结果是:144472 + 9719 + 34100 + 6100 + 3877 = 198268 字节!

whole文件的gz大小是: 196465 字节

image.png

根据上图可知,先把5个文件合并起来再进行gz压缩,实际大小是比分开进行gz压缩要小的,换算下来差不多 1 ~ 2k 的优化空间吧~

菜🐔笔者在这里问问大🔥,你们觉得这个方案可行吗?🤔


好了,本文到这里就结束了~其实读完的你可能会觉得笔者多此一举,因为优化力度不是十分大,但是其实笔者更多的是想分享这样一个优化的思路。毕竟性能能追求更好的,为什么不追求呢,这里提供的一个思路,在一定的场景说不定能优化好几k甚至上10k的资源体积大小~对于一些海外的用户来说,首屏能少10k说不定能极大的提升用户体验了~😜 还是文章开头说的吧,不是业界的方案,只是个人的思考的一个方向,大佬们觉得没用的也不要喷,觉得有用的话可以实战应用到自己的项目中试试效果,反正我就是这么应用的~