抱歉,用 Serverless 作這個標題主要是借勢炒做。本文內容跟 Serverless 其實沒半毛錢關係。前端
可是2X做者爲啥要起這個名字博眼球呢?node
衆所周知,Serverless 的概念並不像字面意義上那樣的 「無服務」,而是將中心化的服務端應用打散成爲一個個函數式的服務,節約了前端編碼到產品上線中間服務部署的操做成本。本質上是一種 雲計算執行模型(Cloud Computing Execution Model)。ios
聰明的童鞋應該看完標題和開篇第一張圖就猜到了我藥裏究竟賣的什麼葫蘆 —— 本文內容實際上是關於 SSG(Static Site Generation,靜態站點生成) 的一個解決方案。介紹一下鄙人最近是如何將一個 Mongo + Egg + React + Node 架構的動態站點(內部非開源版本圖表庫官網)降(mo)級(gai)爲純靜態 SPA (開源圖表庫 Cloud Charts 的官網站點)並部署在 github pages 上的(注:考慮國內訪問問題,目前託管在 gitee 和 netlify)。git
爲了凸顯方案的獨特之處,順便爲開源項目引引流,難免要先說明一下我所遇到的需求場景的複雜度和非典型性:github
稍微介紹一下咱們目前正在折(guan)騰(shui)的一個 Github 開源項目:阿里雲 Cloud Charts —— 又一個開源的信息可視化圖表庫。npm
衆所周知,這絕壁又雙叒叕是一羣無良技工在重複造輪子!(這裏是一段辛辣的諷刺)。json
一段不正經的補充說明:axios
- Cloud Charts 的前身是一套長期服務、應用於阿里雲混合雲場景的數據可視化解決方案(非開源版本),在平常工做中切實方便了不少不熟悉前端工做的研發童鞋進行業務實踐
- 咱們推出開源版本的其中一個目的,在於尋找更多適合沉澱的業務場景和更多志同道合的優秀的小夥伴 -(^_^)/ 沒錯~就是你!
在維護 Cloud Charts 開源項目的同時,咱們也須要將其內網版本的文檔、示例、教程等資料同步搬運到公網,以方便公網用戶對項目的瞭解和使用。後端
而對於 Cloud Charts 的開源版本(TXD Widgets),咱們已經實現了一套用於託管相關文檔和數據的官網站點(集團內網)。這個內部站點是基於 MongoDB + Egg.js + React.js 形式開發和維護的,最重要的一個特色是依靠一個後臺管理系統,方便開發圖表庫的童鞋可以及時維護和發佈圖表庫項目的功能特性和對應的教程、示例、API文檔等內容。api
爲了不手動搬運內容、文檔到外網所產生的費力且繁瑣的工做,更爲了能將站點的原生功能、特色儘量透出給外網用戶,咱們但願將這套站點(前臺頁面功能和內容)部署到外網。
通過一番討論和思慮,咱們決定將原站點經過一系列工做流構建爲一個 SPA 應用,方便快速發佈和更新到 github pages 類型的靜態頁面服務中(真-Serverless 🐶🐶🐶)。
針對兩種主流的 SSG 模型進行分析,咱們能夠看到其各自的優缺點:
(1)基於構建時的 SSR(無頭瀏覽器渲染後爬取靜態內容)
優點
不足
(2)XPress 類型(如 VuePress Next 等框架)
優點
不足
內網站點的功能、複雜度以下圖所示。
結合前面的相關分析,咱們須要另闢蹊徑,找到一條符合國情特點的發展道路。通過必定的業務抽象和一段時間的技術嘗試,咱們最終造成了一整套站點遷移的技術方案。
咱們發現,動態站點要構建靜態站點,最主要也是最重要的一環就是數據服務的靜態化(即:數據接口的內容構建爲可靜態託管的前端資源)。
如上圖所示,整個過程能夠分爲:數據靜態化、靜態數據加載、圖片資源本地化、SPA 構建打包 共 4 個環節。下面的篇幅咱們將逐個環節展開介紹,展示每一個環節的具體內容、遇到的問題和相應解法。
因爲官網形態是比較結構化的內容頁面,咱們針對不一樣頁面的特色、不一樣數據的關聯關係,撰寫了相應的瀏覽器腳原本模擬用戶的點擊、切換和跳轉等行爲。同時將 exceed 的數據請求記錄下來(注:exceed 是一個 http 請求工具庫,實質上是對 axios 的業務包裝 - npm地址),通過必定處理後轉存爲本地數據(對敏感數據記錄和字段進行過濾,將 「請求參數-數據返回」 以對應關係存儲爲本地 JSON 文件)。
靜態化的數據格式形如:
// demo.json [ // 單個請求的靜態化數據 { // 查詢的條件(請求的參數) params: { api: 'fetchAll', params: { module: 'studio' }, data: { page: 1, size: 0 }, }, // 查詢的結果(請求的返回) response: { status: 0, message: 'success', data: [...], } }, ... ]
(1)頁面掛載靜態數據
SPA 頁面加載時,先將靜態化的數據加載到頁面的 js 做用域中,便於數據查詢方法的消費:
// 將靜態數據解析爲字典形式以方便查詢(字典鍵值對的查詢效率遠高於列表) function parseGlobalData(list) { var globalData = {}; list.forEach(function (x) { var key = objectToKey(x.params); if (!globalData[key]) { globalData[key] = x.response; } }); return globalData; } // 將請求參數(一個obj)轉爲字符串(base64),便於做爲字典鍵值對中的鍵名 function objectToKey(obj) { return btoa(JSON.stringify(Object.entries(obj).sort())); } // 將靜態數據掛載到頁面的 js 全局變量 function appendGlobalData(data) { if (!window.staticGlobalData) { window.staticGlobalData = {}; } window.staticGlobalData = Object.assign( {}, window.staticGlobalData, parseGlobalData(data) ); }
(2)改造 exceed.fetch 方法以消費全局數據
內網站點的數據是使用 exceed 類庫請求後端接口進行異步加載的。在外網版本中,咱們使用 Proxy 的方式重寫了 exceed.fetch 方法以從本地讀取數據(前面環節產生的靜態化數據)。原理示意以下:
// 判斷是否爲外網站點 if (isStaticSite()) { const handler = { apply: (target, thisCtx, [params]) => loadLocalData(params), }; // 代理 exceed.fetch 方法以消費全局變量中的數據 exceed.fetch = new Proxy(eceed.fetch, handler); } // 從全局變量中查詢數據的方法 function loadLocalData(params) { const key = objectToKey(params); return window.staticGlobalData[key]; }
在完成數據的本地化後,經過 nodejs 腳本實現能夠匹配出官網所用到的所有圖片資源(CDN 地址),對公網不能訪問的圖片資源(如內網語雀、OSS 服務上的資源)進行本地化轉存。
內、外版本的站點比較效果以下(僅方便展現部分首頁內容):
能夠看到,內網版本中圖表庫須要的重要功能模塊都被原滋原味地還原、構建到外網站點中了(不可對外輸出的部分在 SPA 構建時已經作了有效的過濾處理)。
寫在後面:本文主要介紹 SSG 的技術方案和一些關鍵環節的奇技淫巧。SSG 的過程當中其實還有不少細節問題,如:如何在一套代碼中維護內、外網不一樣版本的內容差別等。感興趣的童鞋歡迎留言討論。