快来学习那些减少文件体积的小妙招吧~

1,992 阅读8分钟
本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

前言

hello,小伙伴们好呀,我是小羽同学~

文件的大小关乎到网页的加载速度,尤其是在弱网的环境下,一个2-3m的文件就可以让让你的网站白屏大半天。对此咱们通常会对咱们的文件进行一些列的操作,使得文件的体积变小,加载更快。

Gzip和Brotli

Gzip 基于DEFLATE 算法,它是LZ77霍夫曼编码的组合,最早用于 UNIX 系统的文件压缩。而当下主流的浏览器,包括老牌的浏览器 IE6+系列、Opera ,还是目前活跃于市面的FireFox、Google Chrome、safari、edge等都已经支持 Gzip 压缩(从can i use上看,它已经兼容所有的主流浏览器)。作为目前市面上使用最普遍的一种数据格式,它一般可以将文件压缩到原大小的40%,还会提供9个等级的压缩,一般来说如果是在服务器上的动态压缩只需要压缩到6即可,后面再压缩也不会有太大的效果,而且会消耗cpu的资源。如果是通过webpack/vite等工具生成静态的.gz文件,那么可以直接选择压缩最高的等级,因为后续服务器会直接读取.gz文件不需要消耗cpu的性能,而解析时间各个等级基本没有太多的差别(注意,这里是需要配合服务器的静态gzip模块一起使用)

image-20221128220508239

Brotli简称br,他是 Google 在 2015 年 9 月推出的一种压缩算法,与其他压缩算法相比,Brotli 有着更高的压缩效率。它通过变种的LZ77 算法Huffman 编码以及二阶文本建模等方式进行数据压缩。

  • 针对常见的 Web 资源内容,Brotli 的性能相比 Gzip 提高了10-25%
  • Brotli 压缩级别为 1 时,压缩率Gzip 压缩等级为 9(最高)时还要高;
  • 在处理不同 HTML 文档时,Brotli 依然能够提供非常高的压缩率

虽然Brotli拥有非常好的性能,但从can i use中,咱们可以发现它并没有兼容一些旧版本的浏览如ie。如果需要兼容这些浏览器的小伙伴,建议使用gzip。关于压缩级别的选择,和gzip保持一致就好(动态选择6,静态选择11)。

image-20221128215927421

刚刚介绍了那么多理论知识,那么gzipBrotli该怎么使用呢?

在nginx中(这里就直接将动态和静态的一起写啦)

#gzip
gzip_static on;
gzip_proxied any; #指定反向代理的文件
gzip on;
gzip_min_length 2k; #指定压缩数据的最小长度,只有大于或等于最小长度才会对其压缩。这里指定 2k
gzip_buffers   4 32k; #请求缓冲区的数量和大小
gzip_comp_level 6; #等级,默认 6,最高 9,太高的压缩水平可能需要更多的 CPU
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml; #指定允许进行压缩类型
gzip_vary on; #用于在响应消息头中添加 Vary:Accept-Encoding ,使代理服务器根据请求头中的 Accept-Encoding 识别是否启用 gzip 压缩
#br(需要安装三方模块ngx_brotli)
brotli on;              #启用
brotli_comp_level 6;    #等级,默认 6,最高 11,太高的压缩水平可能需要更多的 CPU
brotli_buffers 16 32k;   #请求缓冲区的数量和大小
brotli_min_length 128;   #指定压缩数据的最小长度,只有大于或等于最小长度才会对其压缩。这里指定 128 字节
brotli_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/json image/svg application/font-woff application/vnd.ms-fontobject application/vnd.apple.mpegurl ;   #指定允许进行压缩类型
brotli_static always;   #是否允许查找预处理好的、以.br 结尾的压缩文件,可选值为 on、off、always
brotli_window 512k;     #窗口值,默认值为 512k

前面咱们有说到静态gzip和Brotli,是去读取后缀为.gz.br的文件,不需要每次都从服务端生成新的压缩文件,从而降低服务器cpu的压力。那么这种静态文件咱们又是怎么生成的呢?

这里以vite为例子

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import viteCompression from 'vite-plugin-compression';
​
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    viteCompression({
      //生成压缩包.gz/.br
      verbose: true,
      disable: false,
      threshold: 1024,
      algorithm: 'gzip',
      ext: '.gz',
      // algorithm: 'brotliCompress',
      // ext: '.br',
    }),
  ]
});
​

image-20221128222348827

image-20221128222651171

分别通过gzip和Brotli生成压缩文件后,可以发现通过br压缩后的文件大概会比gzip小10%-20%。效果已经很棒的啦~

路由懒加载

路由懒加载的作用就是将咱们的路由模块,剥离出来成为一个个单独的jscss文件,当你需要使用到他的时候,再将相关的文件加载并渲染出来。可以显著减少主包的体积,加快加载速度,从而提升用户体验

前面的文章讲过了,这里就不讲了,想了解的小伙伴可以去看这篇文章——《原来懒加载有这些玩法,你确定不看看?》

按需加载

按需加载主要是利用了打包工具的摇树机制

那么摇树是什么呢?

摇树(tree-shaking),简单地说,它是一种消除死代码(不会使用到的代码)的方法。 你可以把你的应用想象成一棵树。 您实际使用的源代码和库代表了树的绿色、有生命的叶子。 死代码代表秋天消耗的树的棕色枯叶。 为了让你的树(应用)看起来是健康的绿色,你必须摇晃树,让枯叶(死代码)掉落。

那么摇树的实现原理是怎么样的呢?

CommonJs的模块化方案中,导入导出行为是高度动态,难以预测的,例如:

if(process.env.NODE_ENV === 'development'){
  require('./bar');
  exports.foo = 'foo';
}

ESM方案则从规范层面规避这一行为,它要求所有的导入导出语句只能出现在模块顶层,且导入导出的模块名必须为字符串常量,这意味着下述代码在 ESM 方案下是非法的:

if(process.env.NODE_ENV === 'development'){
  import bar from 'bar';
  export const foo = 'foo';
}

所以,ESM 下模块之间的依赖关系是高度确定的,与运行状态无关,编译工具只需要对 ESM模块做静态分析,就可以从代码字面量中推断出哪些模块值未曾被其它模块使用,这是实现Tree Shaking 技术的必要条件。

因此咱们在日常的开发中要尽量使用esm,避免使用commonJs,这样子可以有效地减少咱们在代码书写时,部分代码未删除导致体积上升的问题,也可以有效处理引入第三方npm包中某个方法,却需要引入整个包所有代码的问题。

目前antd中的组件时支持esm的摇树机制的,但是样式是不支持的。所以咱们需要对less进行处理,这里还是以vite为例子,使用了vite-plugin-imp这个插件。然后记得把直接引用的import 'antd/dist/antd.css'或者import 'antd/dist/antd.less'删掉。

image-20221128233347211

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import vitePluginImp from 'vite-plugin-imp';
​
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vitePluginImp({
      libList: [
        {
          libName: 'antd',
          style: (name) => `antd/es/${name}/style/index.less`,
        },
      ],
    }),
  ]
});
​

但这样子引用方式,小羽发现这个插件存在一点问题,比如tabale组件,它并没有把一些子组件pagination的样式加载进来,所以需要咱们单独加载一下pagination的less文件就好。

image-20221128234046445

减少图片的体积

关于图片体积,咱们可以直接利用canvas将图片按质量进行压缩从而得到一个不错的效果。具体代码如下:

  const compressImg = async(url) => {
    const res = await new Promise((resolve, reject) => {
      const img = new Image();
      img.src = url;
      img.setAttribute('crossOrigin', 'Anonymous');
      img.onload = () => {
        resolve(img);
      };
      img.onerror = (e) => {
        reject(e);
      };
    });
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = res.width;
    canvas.height = res.height;
    ctx.drawImage(res, 0, 0, canvas.width, canvas.height);
    return canvas.toDataURL('image/jpeg', 0.75);
  };

如果你在做那种需要批量压缩图片的功能,可以去看看这篇利用多线程对图片进行压缩的文章——《我不允许你们学会了worker却还没有应用场景》

除了利用canvas按质量对图片进行压缩外,咱们还可以使用webp

webp 是一种新的图像格式,用于web项目,可以大大提高网站访问速度。

  • 同样的分辨率,大小比 jpg、png 小25% 以上;

  • Chrome、Firefox、Edge、Opera、safari 等主流的浏览器都支持此格式。

    image-20221128235746484

那咱们怎么判断咱们的浏览器是否支持webp

可以直接使用canvas中的taDataURL方法,看返回的字符串中是否含有image/webp字段。如果需要兼容这些低版本的浏览器的话,咱们通常会上传原图和webp的图片到后台。如果发现浏览器不支持,就改用原图即可。

document.createElement('canvas').toDataURL('image/webp', 0.5).indexOf('data:image/webp') === 0;

利用canvas将图片转成webp

const imgToWebP = async(url) => {
    const res = await new Promise((resolve, reject) => {
      const img = new Image();
      img.src = url;
      img.setAttribute('crossOrigin', 'Anonymous');
      img.onload = () => {
        resolve(img);
      };
      img.onerror = (e) => {
        reject(e);
      };
    });
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = res.width;
    canvas.height = res.height;
    ctx.drawImage(res, 0, 0, canvas.width, canvas.height);
    return canvas.toDataURL('image/webp', 0.75);
  };

小结

本文主要介绍了几种可以减少文件体积的小妙招,包括文件压缩、懒加载、按需加载(摇树)等。希望可以在小伙伴们日常的开发和面试中,可以提供到一些帮助。

如果看这篇文章后,感觉有收获的小伙伴们可以点赞+收藏哦~

img

如果想和小羽交流技术可以加下wx,也欢迎小伙伴们来和小羽唠唠嗑,嘿嘿~