免費 CDN 玩法 —— 將整個網站打包成一個圖片文件

資源合併

前端開發者都知道,過多的請求對性能影響很大。而且有些 CDN 不僅按流量收費,請求數也收費,如果網頁裏有大量小文件,顯然不划算。

爲此不少開發者將零碎的小文件進行合併優化,例如 JS/CSS 合併在一起,圖片合併成精靈圖等。

不過傳統的合併方式有一定的侷限性,只能合併同類型的文件。例如 JS、CSS 等文本格式的數據可以合併,但 JS 和圖片顯然無法合併,畢竟一個是文本格式,一個是二進制格式。而且合併過程需對現有資源進行修改,最終發佈的文件與原始文件差異很大。

有沒有什麼辦法,可將任何類型的資源併成在一起,並且不改變原始文件?

類似方案

Google 推出了一個 Web Bundles 方案,可將任意類型的資源打包成一個文件:

細節可參考:https://web.dev/web-bundles/

不過 Web Bundles 注重的是離線分享。如文中所提到,在沒有網絡的飛機上,可將網頁小遊戲通過單個文件的方式分享給旁邊的人一起玩。

演示可見,通過本地文件打開的網站仍保留原始 URL。

由於 Web Bundles 目前仍未正式啓用,需在 flags 中手動開啓,因此該方案仍無法解決本文提出的問題。

通用方案

事實上,我們大可不必關心資源類型,將所有文件都當做二進制文件合併在一起,運行時再通過 JS 提取。

但是,網頁裏的資源引用的仍是原始 URL,例如 <img src="a.gif">。怎樣才能讓網頁使用 JS 提供的數據,而不是從原始 URL 加載?

這就需要藉助 HTML5 的一個黑科技 —— Service Worker。它能攔截網頁產生的 HTTP 請求,並能控制返回內容。這樣即可實現所有資源都從單個文件中提取!

初始化

既然要調用 Service Worker,那麼是否得修改現有的 HTML 文件,在其中添加腳本?

事實上不需要!用戶首次訪問時,無論訪問哪個路徑,後端都返回 Service Worker 安裝頁;安裝完成後頁面自動刷新,這時請求即可被 Service Worker 攔截,從而使用資源包中的 HTML 文件。

至於實現其實很簡單,使用 404.html 即可!

免費空間

雖然我們將資源請求數降低到只有 1 個,但流量仍然是存在的。並且任何一個資源更新都得重新下載整個資源包,導致流量成本進一步增長。

有沒有什麼辦法,可大幅降低流量成本?很簡單,使用免費 CDN 即可。你可將資源包發佈 GitHub、NPM 等空間,然後通過 jsdelivr、unpkg 等免費 CDN 加速。

這樣,你的網站只需提供 404.html 和 sw.js 兩個極小的文件即可,其他所有內容都可從免費空間獲取!

演示站點:https://fanhtml5.github.io/

原始文件:https://github.com/fanhtml5/test-site (多個文件,總共數 MB)

發佈文件:https://github.com/fanhtml5/fanhtml5.github.io (只有兩個,壓縮後不到 2kB)

圖片空間

類似 jsdelivr、unpkg 這麼好用的免費 CDN 並不多,用在這裏太過浪費,作爲開發者也不建議過度使用它們。

我們可使用更低廉更廣泛的免費空間 —— 各大網站的貼圖相冊,例如知乎、B 站、簡書等文章的貼圖,它們不僅支持 CORS,而且允許空 referrer,完全可用於存儲數據。

參照之前寫的《利用 canvas 實現數據壓縮》文章,我們可將原始數據編碼成圖片像素,從而可將任意文件寫入圖片並上傳到相冊;運行時再解碼還原,將原始文件寫入 Storage Cache 供 Service Worker 使用。

至於穩定性,可將圖片上傳到多個站點作冗餘。如果加載失敗或 Hash 不正確則使用下一個備用圖片,從而大幅提升穩定性和安全性。

隱私保護

爲了儘可能減少隱私泄露,同時防止外鏈限制,我們可通過 referrer-policy 對 referrer 進行隱藏。例如:

var img = new Image()
img.crossOrigin = true
img.referrerPolicy = 'no-referrer'
img.src = 'https://pic3.zhimg.com/80/v2-a492fc0204ad0275e0b609ceac2dab10.png'

這樣請求中就沒有 Referer 頭了。

不過需注意的是,爲了能讀取圖片中的像素數據,必須使用 CORS 模式,即設置 crossOrigin 屬性。這種模式下請求會出現 Origin 頭。雖然大部分網站不會使用該頭限制外鏈,但它會泄露你的站點域名。這仍不完美。

爲了能隱藏 Origin 請求頭,這裏使用一種簡單古老但有效的黑科技 —— 使用無源的頁面加載圖片,例如通過 Data URI 創建的 iframe:

var iframe = document.createElement('iframe')
iframe.src = `data:text/html,
<script>
  var img = new Image()
  img.crossOrigin = true
  img.referrerPolicy = 'no-referrer'
  img.src = 'https://pic3.zhimg.com/80/v2-a492fc0204ad0275e0b609ceac2dab10.png'
</script>
`
document.body.appendChild(iframe)

這個方案雖然無法讓 Origin 請求頭消失,但可將其設置爲 null,從而保護你的站點域名不被泄露。

演示

基於上述思路,這裏實現了一個簡單的工具,暫且稱之 web2img

GitHub: https://github.com/EtherDream/web2img

工具演示:

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