使用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
的 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