傳統模組化在利於開發的同時,存在諸多不便,例如:
- 當前端多頁面工程場景中使用的元件庫更新時,需要編譯並上線所有使用到的頁面,提高了上線成本,存在部分頁面漏發或回滾時漏髮帶來的風險;
- 當使用CDN資源管理元件庫時,CDN資源更新可以實現瀏覽器端更新,但是伺服器端渲染場景node端載入資源並執行無法保證與瀏覽器端渲染中載入的CDN資源版本一致,會帶來渲染差異導致重新渲染問題;
- 當採用打包工具提取公共資源時,打包工具的chunk提取侷限於當前構建批次,若一個工程單次只構建部分頁面,無法實現工程內所有的公共資源被全部提取,由於各批次構建的公共資源hash值不同,會導致客戶端渲染時載入CDN資源的效率大打折扣。
致力於解決以上問題,我們開發了web模組的概念,從架構的層面解決困擾業務的問題。
願景
基於已知問題,我們的關注點有:
- 實現web模組單獨編譯上線,亦可單獨回滾;
- web模組上線後所有使用模組的工程頁面立即生效;
- 支援服務端渲染的同構工程。
在實際業務場景中,我們希望通過web模組擴充套件解決多頁面專案內部UI元件庫、工具庫(lodash,moment)、請求庫(axios)等的跨頁面資源共享,全量上線一次,後續更新共享資源只需要單獨構建釋出web模組擴充套件即可,釋出後通過我們提供的能力達到多頁面實時更新共享資源的效果。
另外我們希望業務方使用時引用web模組擴充套件與引用其它資源語法一致,採用原生的import或require,不引入特殊語法,避免增加業務線的學習成本。
實現思路
此方案基於智聯大前端Ada架構之上,可以到 《揭祕智聯招聘的大前端架構Ada》瞭解,下文會直接使用Ada進行描述。
Ada從開發到構建使用的是清單服務進行解析渲染的,構建工具打包會生成一份清單檔案,此檔案包含本次打包的url及打包資源等資訊。
利用清單的約定,我們實現了微前端落地方案Widget,本次設計一樣是針對清單進行一些約定實現執行時的動態載入。
首先我們約定了一種新的工件型別,叫做“web模組擴充套件”,開發時需要寫到固定的資料夾內,才會被腳手架識別並編譯。
同正常的入口檔案一樣,寫到規定資料夾內的檔案會被編譯成單獨的bundle檔案,方便引用這裡選擇只打包成一個bundle的方式,即js與css最多各只有一個檔案。
到這裡我們就拿到了web公共模組,並寫入到清單檔案當中,拿axios舉例如圖:
接下來就是專案中的axios提取,既然是公共的web資源,所以需要將 import 稍微修改一下,即:
import axios from 'axios' // 原引用
import axios from 'extensions/web-modules/axios' // 新引用
此時需要做的就是把 'extensions/web-modules/axios' 這個路徑變成一個動態載入的程式碼,各打包工具都提供了 Externals,利用這一特性可以自定義排除構建資源的載入方式。
拿webpack舉例,簡單實現如下:
{
externals: [
({ request }, callback) => {
// ...
if (!matched) return callback() // 未match到web模組擴充套件路徑直接返回
const url = path.posix.join(projectKey, WEB_MODULES_SCOPE, matched.name) // 真實URL路徑
const name = target === 'node'
? `ada.webModules.require('${url}')` // 由Ada server內部實現此方法
: url
return callback(null, name, scope)
}
]
}
將靜態路徑引用變成動態獲取,瀏覽器端使用window全域性變數的方式,node端使用自實現require的方式。
如圖,編譯後的web模組URL,分別對應清單內的web與node屬性內的資源,下一步只需要將資源掛載到服務當中就實現閉環了。
瀏覽器端我們實現了template的模版佔位符,我們將web相關的資源掛載到當前請求上下文的ctx當中,使用時:
async function GET (ctx) {
const html = `
...
<script src="${ctx.template.placeholders.webModules.axios.js}"></script>
...
`
ctx.response.set(html, 200)
}
服務端有釋出模組提供監聽器,當監聽到web模組擴充套件上線時,會拉取最新的清單並將資原始檔更新至本地快取中,這樣下次請求拿到的 ctx.template.placeholders.webModules.axios.js 就是替換後的最新資源。
同理,ada.webModules.require 方法是一個node端暴漏到全域性的工具方法,裡面對服務端的web擴充套件模組進行了資源載入代理,當資源更新的時候會清除require快取,重新require最新的js bundle。
這裡不深入介紹釋出註冊及發現的機制,因為更偏向於服務端同步分發的內容,與本文中心想表達的設計思想無關。
到這裡web模組擴充套件機制就完成了,達成了我們設計之初的願景。
總結
模組化和動態化帶來開發便利的同時,也增加了上線帶來的影響和風險,畢竟上線會影響所有使用的頁面,對開發者的風險把控能力也有一定的門檻。
智聯大前端團隊釋出web模組擴充套件已有一年多的時間,內部反響比較正面,說明相對比保守大家更願意節約時間成本和嘗試新能力。