大众号:【可乐前端】,共享前端面试与web实战知识

前语

咱们平常开发的过程中,常常会遇到功用优化的要求。而关于前端的功用优化,我理解为有两大类,一类是网络层面的优化,即加速你的资源加载;另一类也是代码层面上的优化,即依据不同的事务场景写出更高功用的代码。

网络层面上的优化常常令人体验最深,幻想一下你把站点的包从 10M 紧缩到了 1M ,那么用户打开页面的时刻也会大大缩短,相比这样用户的体验也会更好。

前端需求加载一个大体积的文件时,能够这么优化

本文会以加载 FFmpeg.wasm 为例,论述在实际项目中遇到大体积的包时如何优化加载速度。

初探

开端之前,先来介绍一下 FFmpeg.wasm 是什么。 FFmpeg.wasm 运用 WebAssembly 技能将 FFmpeg 的功用集成到 Web 应用程序中,使开发者能够在浏览器环境中处理音频和视频。

它的一些要害特色和用处如下:

  • 多媒体处理: FFmpeg.wasm 答应在浏览器中进行媒体处理,如音频和视频的解码、编码、剪辑、合成、添加字幕等操作。
  • 转码: 能够在 Web 应用程序中实现实时的音视频转码
  • 独立性: FFmpeg.wasm 能够独立运行,不需求服务器端的支持,因而能够直接在客户端进行媒体处理,下降服务器担负。

扼要来说 FFmpeg 是一个处理音视频的库,惯例的音视频处理使命常常放在服务端履行,这些使命非常消耗服务端的 CPU 、内存资源。得益于 WebAssemblyFFmpeg 能够移植到浏览器端运用——即 FFmpeg.wasm ,也便是说这些消耗资源的使命能够放在客户端去履行,也无疑是帮咱们省掉了很多服务端资源。

前端需求加载一个大体积的文件时,能够这么优化

在它的运用文档中,咱们发现了这么一句话。

前端需求加载一个大体积的文件时,能够这么优化

我把这个文件下载了下来,果真是超越30M的体积

前端需求加载一个大体积的文件时,能够这么优化

也便是说加载这个库时咱们需求加载将近 30M 的资源,假设咱们的服务器下行带宽是 4M ,在用户能跑满这个带宽的情况下,那么加载这个库大概需求 60秒 左右。假如每一次进来都要等候加载 60秒 的话,那用户估量早就受不了。

假如你的团队里有 FFmpeg 的大佬,那么能够依据你们事务的要求去裁剪一个 FFmpeg ,这样的包体积应该会削减不少。本文仍是会选用一些惯例的思路去做优化,即紧缩与缓存。

PS:假如你是 vite 用户,在跑上面的官方 demo 时遇到了这个报错的话,请把这段装备加到你的 vite 装备文件中。

前端需求加载一个大体积的文件时,能够这么优化

optimizeDeps: {
    exclude: ["@ffmpeg/ffmpeg"],
},

紧缩

在现代化构建东西中,咱们会发现打包出来的产品中往往存在 .gz 后缀名的这种类型的产品。它便是今天咱们需求介绍的主角—— gzip 紧缩。

gzip 运用 DEFLATE 算法进行数据紧缩。 DEFLATE 算法是一种无损数据紧缩算法,它根据 霍夫曼编码LZ77 算法的组合。紧缩后的文件通常以 .gz 作为扩展名。 gzip 能够紧缩单个文件或多个文件,并将它们打包成一个紧缩文件。

紧缩文件能够运用gzip filename指令,默许情况下, gzip 在紧缩文件时会不保留原始文件,能够加上 -k 选项能够防止删除原始文件, gzip 支持不同的紧缩等级,通过指定 -1-9 之间的数字来调整。等级越高,紧缩比越高,但消耗的时刻也越多。解压文件则能够运用:gunzip filename.gzgzip -d filename.gz 指令。

接下来就能够运用 gzip 紧缩去紧缩咱们的 wasm 文件,即 gzip -9 ffmpeg-core.wasm ,紧缩后的文件体积降到了原文件体积的 1/3 左右。

前端需求加载一个大体积的文件时,能够这么优化

我运用的服务器是 nginx ,要在 nginx 中启用 gzip 紧缩,需求填入以下装备:

http{
    gzip on;
    gzip_comp_level 9;
    gzip_min_length 1100;
    gzip_buffers 16 8k;
    gzip_proxied any;
    gzip_types application/wasm;
}

解说一下上面装备的参数:

这段 nginx 装备首要用于启用 gzip 紧缩,并针对一些特定设置进行了装备。以下是对每个装备指令的解说:

  • gzip on;:启用 gzip 紧缩功用
  • gzip_comp_level 9;:设置 gzip 紧缩等级,范围为 19
  • gzip_min_length 1100;:设置启用紧缩的最小文件长度,这儿是 1100 字节
  • gzip_buffers 16 8k;:设置用于 gzip 紧缩的内存缓冲区数量和大小,指定了 16 个内存缓冲区,每个缓冲区大小为 8k 。这样能够用来调整紧缩时的内存运用情况。
  • gzip_proxied any;:设置在呼应署理恳求时启用 gzip 紧缩
  • gzip_types application/wasm;:指定需求进行 gzip 紧缩的 MIME 类型

当启用 gzip 紧缩时, nginx 会查看是否存在预先紧缩过的.gz文件。假如存在,它会直接供给这个预先生成的.gz文件。假如不存在, nginx 会测验动态地紧缩内容,并将紧缩后的内容发送给客户端。

为了削减服务器的开销,我这儿把提早紧缩好的 .gz 文件放到了 nginx 中,前端恳求的时分会直接恳求紧缩好的文件。前端恳求的是 gz 文件,可是关于 nginx 的呼应来说,咱们期望呼应的 content-typeapplication/wasm ,所以这儿还需求有一些额外的装备。

首先咱们能够看到nginx的装备文件中运用了 include mime.types; 这个来装备 nginx 所能识别的 mime 类型。这个时分你能够查看跟 nginx 装备同目录的 mime.types 文件,看看这个文件中是否包括application/wasm wasm;这一项装备,假如没有的话,咱们得手动把它加上,不然 nginx 是不认识这个 mine 类型的,到时分传输的过程中会把它当成application/octet-stream来处理,这样前端接收到的数据是无法正常运用的。

其次是咱们在恳求 wasm.gz 的时分,需求告知 nginx ,我尽管恳求的是一个 .gz 文件,可是我期望你返回的 content-typewasm 所对应的类型。因而能够加上如下装备:

location /your-path/ {
    index index.html index.htm;
    location ~ .wasm.gz$ {
        types {
            application/wasm     wasm;
        }
        default_type application/wasm;
    }
}

前端恳求代码也需求做出如下修正:

    await ffmpeg.load({
      coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
      wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm.gz`, "application/wasm"),
    });

能够看到通过咱们一顿操作猛如虎的装备之后, nginx 已经是能够正确传输紧缩往后的文件

前端需求加载一个大体积的文件时,能够这么优化

demo 代码也是能够正确的跑起来的

前端需求加载一个大体积的文件时,能够这么优化

前端需求加载一个大体积的文件时,能够这么优化

缓存

聊完紧缩之后,能够再看一下缓存。假如你运用的是 nginx ,它会默许发动协商缓存,呼应头如下:

前端需求加载一个大体积的文件时,能够这么优化

尽管核算 ETag 可能会在必定程度上添加服务器的核算担负,不过在现代网络硬件架构下,这一成本通常相对较小了。但这儿仍是期望测验给出别的一种缓存的解决思路——把加载好的文件存入 indexDB ,后续直接从前端缓存中获取,这样做的好处仍是从必定程度上减轻服务端的压力。

这儿用到了 localforage 来处理缓存的存取,全体思路是先看缓存有没有,假如有,直接返回,假如没有则去读取网络资源,然后写缓存。

前端需求加载一个大体积的文件时,能够这么优化

nginx 传输 gz 资源的时分可能会开启分段传输,所以咱们需求写一段代码来收集传输回来的所有片段。

const loadFFmpegCore = async () => {
  const cache = await localforage.getItem(FFMPEG_CACHE_KEY);
  console.log("cache", cache);
  if (cache) {
    console.log("load from cache");
    return URL.createObjectURL(cache);
  }
  try {
    const response = await fetch(FFMPEG_CORE_PATH);
    if (!response.ok) {
      throw new Error(`load failed`);
    }
    let receivedLength = 0;
    const chunks = [];
    const reader = response.body.getReader();
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        break;
      }
      chunks.push(value);
      receivedLength += value.length;
    }
    // 兼并所有 chunks
    const arrayBuffer = new Uint8Array(receivedLength);
    let position = 0;
    for (const chunk of chunks) {
      arrayBuffer.set(chunk, position);
      position += chunk.length;
    }
    // 处理 arrayBuffer
    console.log("arrayBuffer", arrayBuffer);
    const blob = new Blob([arrayBuffer], { type: "application/wasm" });
    await localforage.setItem(FFMPEG_CACHE_KEY, blob);
    return URL.createObjectURL(blob);
  } catch (error) {
    console.error("Error fetching data:", error);
  }
};

运用到的地方也需求相应改一下:

await ffmpeg.load({
      coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
      wasmURL: await loadFFmpegCore(),
    });

前端需求加载一个大体积的文件时,能够这么优化

能够看到当咱们后续加载的时分,就直接从缓存里拿了。

前端需求加载一个大体积的文件时,能够这么优化

最终

以上便是本文关于前端大体积文件加载的全部内容,首要仍是环绕紧缩与缓存展开。假如你觉得有意思的话,点点重视点点赞吧~