乐趣区

使用Data URI Scheme优雅的实现前端导出csv

问题描述
项目里需要实现一个导出 csv 的功能,这是个老生常谈的需求,而且我们使用的是 iview 的组件库,按道理说实现起来应该简单,但实则不然,我在做的时候遇到了一些问题。受限于请求需要 token、后端分页、接口、性能等原因不得不放弃 iview 的导出方式。所以我需要寻找一种可行的、合理的、优雅的导出方案,那就是 Data URI Scheme。
方案实现
方案介绍
Data URI Scheme 是利用 HTML 标签的 href 和 src 属性来实现的。他看起来像是这样的:
<img src=”data:image/png;base64,iVBORw0KGgoAAA
ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4
//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU
5ErkJggg==” alt=”Red dot” />
或者
<a href=”data:text/csv,something”>download</a>
按照这种方案的介绍,我们把要导出的数据拼接在 href 指定位置就能实现导出的需求,代码实现看起来像这样:
<a href=”” download=”export.csv” id=”export_csv” style=”display=’none'”>download</a>
function export_csv (data) {
$(‘#export_csv’).href = ‘data:attachment/csv,’ + encodeURI(data);
$(‘#export_csv’).click();
setTimeout(function () {
$(‘#export_csv’).href = ”;
})
}
export_csv(csv_data_str);
测试发现,妥妥的,没毛病。
存在问题
但在实践中这个方案是有限制的:在 chrome 的实现中这个 url 最大限制为 2MB。所以,当在 Chrome 下载的文件大小超过 2MB chrome 便会报这样的错误(其他浏览器这里不做讨论):
下载
失败 - 网络错误

这里 2MB 的大小可以在 chromium 源码中可以看到:
const size_t kMaxURLChars = 2 * 1024 * 1024;

if (!iter->ReadString(&s) || s.length() > url::kMaxURLChars) {
*p = GURL();
return false;
}

变量声明部分源码链接
变量引用部分源码链接

而关于 2MB 限制的问题在 chromium 论坛在 2010 年就被人作为 bug 提出来了,但是从 2010 年一直讨论到 2019 年也没有明显的改善。
方案改进
chromium 不改,那我们只能自己想办法了,于是有大牛提出来使用 URL.createObjectURL + Blob 来突破这个限制。借助 Blob 对象和 URL.createObjectURL 我们可以得到形如下面的 URL:
blob:https://xxx.com/0bde569d-20a2-4085-95e6-dcec242962c6
这样就能突破 Chrome 对 Data URI Scheme URL 大小的限制了。当然呢,我们没用过 URL.createObjectURL 这个方法,也没用过 Blob 对象,所以我们要看看浏览的支持情况

恩,看起来没有问题,那我们来看看代码实现。
<a href=”” download=”export.csv” id=”export_csv” style=”display=’none'”>download</a>
function export_csv (data) {
const BOM = ‘\uFEFF’;
let blob_obj = new Blob([BOM + data], {type: ‘text/csv’});
let download_url = URL.createObjectURL(blob_obj);
$(‘#export_csv’).href = download_url;
$(‘#export_csv’).click();
setTimeout(function () {
// 通过 createObjectURL 创建的 url 需要通过 revokeObjectURL() 来释放
URL.revokeObjectURL(download_url);
$(‘#export_csv’).href = ”;
})
}
export_csv(csv_data_str);
恩,这样就不怕超过 2MB 的 CSV 的导出了,但是 Blob 对象有大小限制吗?
Good question !
我们在 chromium 的说明文档中可以看到一个表:

Device
Ram
In-Memory Limit
Disk
Disk Limit
Min Disk Availability

Cast
512 MB
102 MB
0
0
0

Android Minimal
512 MB
5 MB
8 GB
491 MB
10 MB

Android Fat
2 GB
20 MB
32 GB
1.9 GB
40 MB

CrOS
2 GB
409 MB
8 GB
4 GB
0.8 GB

Desktop 32
3 GB
614 MB
500 GB
50 GB
1.2 GB

Desktop 64
4 GB
2 GB
500 GB
50 GB
4 GB

从这个表中,大概可以看出来在 In-Memory Storage 的时候桌面版 64 位 Chrome Blob 的上限为 2GB(在 Chrome 57 似乎上限是 500MB)。所以从现在看来这种方法应该是安全的。至此,这个问题算是完整的解决了。
iview 的实现
另外,在我写这篇文章的时候我发现 iview 的 export-csv 方法也是按照这个方案实施的,而且做了更多兼容,可以方便大家参考。但他在资源释放的地方做的还需改进,也希望大家注意。
参考文档

Data protocol URL size limitations
Excellent Export and the Chrome URL limit
Data_URI_scheme
excellentexport pull request
无法在 nodejs 中下载大文件
Issue 69227: Loading large URLs kills the renderer
Issue 375297: the total blobs’ size cannot exceed about 500MiB
Is there any limitation on JavaScript Max Blob size
chromium/url/url_param_traits.cc#L36
chromium/url/url_constants.cc#L32
iview 3.x export-csv

退出移动版