使用Data URI Scheme優雅的實現前端導出csv

問題描述

項目裏需要實現一個導出csv的功能,這是個老生常談的需求,而且我們使用的是iview的組件庫,按道理說實現起來應該簡單,但實則不然,我在做的時候遇到了一些問題。受限於請求需要token後端分頁接口性能等原因不得不放棄iview的導出方式。所以我需要尋找一種優雅的、合理的導出方案,那就是Data URI Scheme

方案實現

Data URI Scheme是利用HTML標籤的hrefsrc屬性來實現的。他看起來像是這樣的:

<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便會報這樣的錯誤(其他瀏覽器這裏不做討論):

下載
失敗-網絡錯誤

clipboard.png

這裏2MB的大小可以在chromium源碼中可以看到:

const size_t kMaxURLChars = 2 * 1024 * 1024;
...
if (!iter->ReadString(&s) || s.length() > url::kMaxURLChars) {
    *p = GURL();
    return false;
}
  1. 變量聲明部分源碼鏈接
  2. 變量引用部分源碼鏈接

而關於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對象,所以我們要看看瀏覽的支持情況

clipboard.png

恩,看起來沒有問題,那我們來看看代碼實現。

<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)。所以從現在看來這種方法應該是安全的。至此,這個問題算是完整的解決了。
另外,在我寫這篇文章的時候我發現iviewexport-csv方法也是按照這個方案實施的,而且做了更多兼容,可以方便大家參考。但他在資源釋放的地方做的還需改進,也希望大家注意。

參考文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章