本文介绍一个能让前端优雅下载大文件的工具:StreamSaver.js

StreamSaver.js可用于实现在Web浏览器中直接将大文件流式传输到用户设备的功能。

传统的下载方式可能导致大文件的加载时间较长或造成内存占用过大的问题,使用标签打开新页面下载文件,遇到.txt或者.mp4之类的文件可能就直接在页面展示了,不会触发下载功能。而StreamSaver.js则通过流式下载的方式解决了这些问题。

StreamSaver.js将大文件拆分成小块,并在下载过程中逐块传输到硬盘,从而降低内存占用和提高下载速度。

环境准备

要学习StreamSaver.js首先要准备一份或者多份可下载的文件。

你可以使用网络上的文件资源,但这需要你自己去找。

你也可以在自己的电脑运行个服务,把文件资源丢进去即可。

如果你用脚手架创建项目,比如vue或者react之类的项目,也可以把文件放在静态资源目录里。

比如用vite创建一个Vue项目,然后在public目录下创建一个test.txt文件。项目运行起来,在浏览器访问:端口号/public/test.txt就能查看到这个文件内容。

安装 StreamSaver.js

可以使用CDN或者npm安装StreamSaver.js。

本文使用CDN的方式讲解。

CDN

打开StreamSaver.js的仓库[3]。

前端下载文件_前端下载文件跨域_前端下载文件重命名

把StreamSaver.js文件下载到你项目里引入即可。

<script src="../StreamSaver.js"></script>

npm

⚡️StreamSaver npm地址[4]

使用以下命令下载StreamSaver到项目里

npm i streamsaver

然后在要使用的地方引入即可。

import streamSaver from "streamsaver"

起步

起步阶段,我们先试试如何下载一个.txt文件。

如果我们要下载一些浏览器读不懂的文件,我们可以使用标签在新窗口打开链接,也可以使用windows.open(‘url’)的方式打开新窗口进行下载。

但如果这个文件浏览器是读得懂的,比如.txt文件,那浏览器就不会执行下载,而是会直接在页面中把文件内容展示出来。

此时就可以使用StreamSaver.js来解决这个问题。

使用StreamSaver.js下载文件的大概流程是这样的(为了方便理解,我用一些不专业的术语进行描述):

1.创建一个文件,该文件支持写入操作。streamSaver.createWriteStream(‘文件名.后缀’)。

2.使用fetch方法访问文件的url,将内容一点点的放到StreamSaver创建的文件里。

3.监听文件内容是否读取完整,读取完就执行“保存并关闭文件”的操作。

根据上面的指引编写代码:


<button id="download">下载</button>


<script src="../StreamSaver.js"></script>
<script>
  // 监听按钮点击事件,点击就下载文件
  download.onclick = () => {
    // 【步骤1】创建一个文件,该文件支持写入操作
    const fileStream = streamSaver.createWriteStream('test.txt'// 这里传入的是下载后的文件名,这个名字可以自定义
    
    // 【步骤2】使用 fetch 方法访问文件的url,将内容一点点的放到 StreamSaver 创建的文件里
    fetch('http://localhost:9988/public/test.txt')
      .then(res => {
        const readableStream = res.body
        if (window.WritableStream && readableStream.pipeTo) {
          return readableStream.pipeTo(fileStream)
            .then(() => console.log('完成写入'))
        }

        // 【步骤3】监听文件内容是否读取完整,读取完就执行“保存并关闭文件”的操作。
        window.writer = fileStream.getWriter()
        const reader = res.body.getReader()
        const pump = () => reader.read()
          .then(res => res.done
            ? writer.close()
            : writer.write(res.value).then(pump)
          )
        pump()
      })
  }
</script>

大概就是这样子了。

提示

如果遇到提示跨域的问题,可以配置mitm指向mitm.html。

mitm.html在StreamSaver.js仓库[5]里可以获取到。

前端下载文件跨域_前端下载文件重命名_前端下载文件

可以把mitm.html放到你服务器再配置。

streamSaver.mitm = 'https://你的服务器地址/mitm.html'

打包下载 zip

如果想将多个文件打包成zip下载到本地,可以将StreamSaver.js和zip-stream.js结合在一起使用。

zip-stream.js在StreamSaver.js的仓库里有。

前端下载文件重命名_前端下载文件跨域_前端下载文件

zip-stream.js在/examples目录里。

使用npm安装streamsaver也能在/examples目录下找到zip-stream.js,直接引入即可。

import 'node_modules/streamsaver/examples/zip-stream.js'

如果使用CDN的方式就直接用引入即可。

打包zip下载的步骤:

1.创建下载后的文件名和文件格式。

2.使用zip-stream创建一个ZIP实例,用来不断接收要下载的文件。

3.所有文件下载完成就执行close()方法将所有文件真正打包成一个zip。

<button id="download">下载</button>

<script src="../StreamSaver.js"></script>
<script src="zip-stream.js"></script>
<script>

    // 要下载的文件地址列表
    let urls = [
        {
            fileName'test.txt',
            url'http://localhost:9988/public/test.txt',
        },
        {
            fileName'test.csv',
            url'http://localhost:9988/public/test.csv',
        }
    ]

    download.onclick = () => {
        // 【步骤1】
        const fileStream = streamSaver.createWriteStream('test.zip')

        // 【步骤2】
        const readableZipStream = new ZIP({
            async pull(ctrl) {
                for (let i = 0; i < urls.length; i++) {
                    const res = await fetch(urls[i].url)
                    const stream = () => res.body
                    const name = urls[i].fileName
                    ctrl.enqueue({ name, stream }) // 不断接收要下载的文件
                }

                // 【步骤3】
                ctrl.close()
            }
        })

        if (window.WritableStream && readableZipStream.pipeTo) {
            return readableZipStream.pipeTo(fileStream).then(
                () => console.log('下载完了')
            )
        }
    }
    
</script>

点击下载按钮后的效果:

前端下载文件_前端下载文件跨域_前端下载文件重命名

这个例子准备了.csv和.txt文件。下载时会合并成.zip,解压后能看到里面的所有文件都是正常能打开的 。

合成文件再下载

在这个例子中,我要将2个.csv文件合并成1个再下载。

我准备了两个.csv文件(test1.csv 和 test2.csv),它们的内容分别长这个样子。

我要将它们合并成这样子:

前端下载文件跨域_前端下载文件重命名_前端下载文件

在合并文件之前我们首先要清楚这个文件的内容是如何组成的。

在 Excel 中打开.csv的每个单元格的内容转换成文本形式的话是用逗号分隔。

前端下载文件跨域_前端下载文件重命名_前端下载文件

如果要合并多个.csv文件,只需监听到每个.csv下载完成,然后再拼接一个n换行,再下载下一个.csv文件即可。

整理一下就是以下几个步骤:

1.拿到一组下载地址,把它们转存到一个迭代器里。

2.递归执行迭代器,如果迭代器里还有内容,就使用fetch请求数据。

3.如果迭代器没内容了,使用writer.close()关闭文件写入。

该功能写成真正的代码如下所示:

<button id="download" onclick="down()">下载</button>

<script src="../StreamSaver.js"></script>
<script>
  // 编码转换方法
  let encode = TextEncoder.prototype.encode.bind(new TextEncoder)

  // 准备好要下载的链接
  const urls = [
    'http://localhost:9988/public/test1.csv',
    'http://localhost:9988/public/test2.csv'
  ]

  // 迭代器数据
  let urlsIter = null

  // 写入方法放到全局中保存
  let writer = null

  // 下载按钮点击事件
  function down() {
    // 创建一个下载管道,并将下载后的文件命名为 newTest.csv
    const fileStream = streamSaver.createWriteStream('newTest.csv')
    // 创建写入方法
    writer = fileStream.getWriter()
    // 将要下载的链接转换成迭代器
    urlsIter = urls[Symbol.iterator]()

    // 开始执行循环下载
    forDown()
  }

  // 循环下载的方法
  async function forDown() {
    // 获取迭代器最新一条数据
    let urlIter = urlsIter.next()
    // 如果迭代器没数据,执行写入完成操作,并停止递归
    if (urlIter.done) {
      writer.close()
      return
    }

    // 迭代器有内容时执行请求操作
    await fetch(urlIter.value)
      .then(res => {
        // 通过请求文件url获取到的数据
        const readableStream = res.body
        if (window.WritableStream && readableStream.pipeTo) {
          const reader = readableStream.getReader()
          // 讲获取到的每一包写入文件里
          const pump = () => {
            return reader.read()
              .then(readRes => {
                if (readRes.done) { // 当前文件读取完成后执行
                  // 文件读取完成后换行
                  writer.write(encode('n'))
                  // 执行请求下一个文件
                  forDown()
                } else { // 文件读取过程执行
                  // 一包包写入
                  writer.write(readRes.value)
                    .then(pump)
                }
            })
          }
          pump()
        }
      })
  }
</script>

这个案例稍微复杂一丢丢,建议跟着手敲一遍。

限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击查看详情
站 长 微 信: lzxmw777

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注