大众号:【可乐前端】,共享前端面试与web实战知识
前语
咱们平常开发的过程中,常常会遇到功用优化的要求。而关于前端的功用优化,我理解为有两大类,一类是网络层面的优化,即加速你的资源加载;另一类也是代码层面上的优化,即依据不同的事务场景写出更高功用的代码。
网络层面上的优化常常令人体验最深,幻想一下你把站点的包从 10M
紧缩到了 1M
,那么用户打开页面的时刻也会大大缩短,相比这样用户的体验也会更好。
本文会以加载 FFmpeg.wasm
为例,论述在实际项目中遇到大体积的包时如何优化加载速度。
初探
开端之前,先来介绍一下 FFmpeg.wasm
是什么。 FFmpeg.wasm
运用 WebAssembly
技能将 FFmpeg
的功用集成到 Web
应用程序中,使开发者能够在浏览器环境中处理音频和视频。
它的一些要害特色和用处如下:
-
多媒体处理:
FFmpeg.wasm
答应在浏览器中进行媒体处理,如音频和视频的解码、编码、剪辑、合成、添加字幕等操作。 -
转码: 能够在
Web
应用程序中实现实时的音视频转码 -
独立性:
FFmpeg.wasm
能够独立运行,不需求服务器端的支持,因而能够直接在客户端进行媒体处理,下降服务器担负。
扼要来说 FFmpeg
是一个处理音视频的库,惯例的音视频处理使命常常放在服务端履行,这些使命非常消耗服务端的 CPU
、内存资源。得益于 WebAssembly
, FFmpeg
能够移植到浏览器端运用——即 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.gz
或 gzip -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
紧缩等级,范围为1
到9
-
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-type
是 application/wasm
,所以这儿还需求有一些额外的装备。
首先咱们能够看到nginx的装备文件中运用了 include mime.types;
这个来装备 nginx
所能识别的 mime
类型。这个时分你能够查看跟 nginx
装备同目录的 mime.types
文件,看看这个文件中是否包括application/wasm wasm;
这一项装备,假如没有的话,咱们得手动把它加上,不然 nginx
是不认识这个 mine
类型的,到时分传输的过程中会把它当成application/octet-stream
来处理,这样前端接收到的数据是无法正常运用的。
其次是咱们在恳求 wasm.gz
的时分,需求告知 nginx
,我尽管恳求的是一个 .gz
文件,可是我期望你返回的 content-type
是 wasm
所对应的类型。因而能够加上如下装备:
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(),
});
能够看到当咱们后续加载的时分,就直接从缓存里拿了。
最终
以上便是本文关于前端大体积文件加载的全部内容,首要仍是环绕紧缩与缓存展开。假如你觉得有意思的话,点点重视点点赞吧~