随着前端技术的快速发展,在浏览器里能做的事情越来越多,我们的web应用也变得越来越复杂,处理的数据量越来越大,很多之前需要交给服务端或者需要其他语言编写的库再编译成JS使用,亦或者有些只能靠第三方的库来实现的功能,现在我们逐渐的可以直接使用浏览器自带的API来实现了。
今天给大家介绍的一个场景是前端压缩(如:gzip压缩),利用浏览器自带的CompressionStream
和DecompressionStream
以Stream流的形式来做压缩解压缩操作,使用这些Web API配合流的好处是:
- 减少第三方库的依赖,提升页面加载速度,顺便避免三方库因安全漏洞带来的风险
- 原生API理论上能带来更好的运行性能
- 使用流可以大大减少内存占用,对于大批量数据可以做到按需处理
关于前端Stream流的更多指南,可参考之前的文章:Node与Web Streams完全指南
我们在前端看到压缩比较多的地方是静态资源的gzip压缩,这种一般由服务端压缩或打包时直接压缩相关资源,然后传给前端,浏览器拿到数据后根据http头里的压缩算法信息自动解压缩,对页面JS无感知。
但是当我们需要上传大量数据时,为了避免数据过大时间过长,我们就需要先把数据压缩后再上传给服务端,或者仅仅是为了做一些混淆,不让人轻易地看到传输内容?;我们也可以做本地文件的压缩,或者在需要提供用户本地下载数据的情形下,前端处理压缩后直接下载文件至本地文件夹等场景。
由于压缩解压缩可能会处理非常大的数据,所以这个能力一般都是基于流的形式,不管是Web Stream还是Node.js Stream。
fetch下载文件至本地
在Web端最早提供ReadableStream
可读流的地方就是fetch
的Response里的body属性(如:res.body),你甚至可以用fetch来做个大文件下载,比如:
async function downloadMovie() {
// https://developer.mozilla.org/en-US/docs/Web/API/window/showSaveFilePicker
const newHandle = await window.showSaveFilePicker({
suggestedName: `movie.mp4`,
});
// 得到writableStream用来写进上面选择/新建的文件
const writableStream = await newHandle.createWritable();
// 开始下载
const res = await fetch('movie.com/123')
await res.body
// 可读流调用pipeTo把数据传到可写流里
.pipeTo(writableStream)
console.log('下载完成')
}
下载压缩文件,实时解压缩保存至本地
我们也可以请求一个压缩的文件,直接解压缩并保存至本地:
async function downloadAndDecompressAndSave() {
// https://developer.mozilla.org/en-US/docs/Web/API/window/showSaveFilePicker
const newHandle = await window.showSaveFilePicker({
suggestedName: `test.js`,
});
const writableStream = await newHandle.createWritable();
// 开始下载
const res = await fetch('cdn.com/test.js.gz')
await res.body
// pipeThrough用来写到转换流,并返回该转换流的可读流,也就是输出解压缩后的数据
.pipeThrough(new DecompressionStream('gzip'))
// 可读流调用pipeTo把数据传到可写流里
.pipeTo(writableStream)
console.log('下载完成')
}
本地文件压缩下载
我们也可以用这些API做一个简单的网页版压缩工具,用户通过选择一个本地的文本文件,通过CompressionStream
转换流做压缩处理,并实时写到一个新的文件里,如:
<input type='file' onchange='onFileChange(...arguments)'>
async function onFileChange(e) {
const file = e.target.files[0]
// 写文件流
const newHandle = await window.showSaveFilePicker({
suggestedName: `${file.name}.gz`,
});
const writableStream = await newHandle.createWritable();
// File的stream()返回一个该文件的可读流
await file.stream()
// pipeThrough用来写到转换流,并返回该转换流的可读流,也就是输出压缩后的数据
.pipeThrough(new CompressionStream('gzip'))
// 压缩后的数据直接pipe给新文件的可写流
.pipeTo(writableStream)
console.log('压缩完成')
}
以下为GIF示例:
兼容性
以上为利用浏览器原生WEB API处理纯前端压缩解压缩的一些应用场景,在浏览器兼容的情况下建议WEB API走起。
CompressionStream
及DecompressionStream
: Chrome >= 80;
上面的window.showSaveFilePicker
是试验性API, Chrome >= 86。