最近,我們團隊開源了一套沉澱了2年的前端SPA架構框架,主要是用來解決動態路由的問題。我們的思路來源於後端,採用中介軟體的設計模式來架構整個框架。我們的原則是讓大家快速開發一個SPA單頁應用,只關心業務邏輯,其他的行為都可以幫助處理掉。
其實我們的開源比較匆忙,從很多方面看還是有些不標準的,但是之後我們會嚴格按照標準的規範和流程走下去,給大家一份穩定的架構。對於現在開始關注我們團隊的小夥伴,我表示非常的感謝。有大家的支援,我們會做的更好。
Miox到底要解決什麼問題?
這個問題從我們設計的初衷來說,就是解決所有路由的問題。在業界,其實大家都普遍認可一對一的路由模式,從而生產出了很多路由體系,比如vue-router
和react-router
。他們都是非常不錯的架構,從某種意義上來說,引領了前端路由的前進。很多小夥伴都是直接使用他們的全家桶來開發專案,也得到了很好的效果。
我不確定大家是否考慮過一個問題,當一個使用者從登陸頁面A登陸後進入B,再回到A頁面的情況。一對一的靜態路由其實理論上最終會將A頁面顯示為未登入,這是完全正確的,但是邏輯上它應該是一個登入的頁面。靜態路由無法區分環境變數對頁面選擇的影響,只能通過hook等手段來將頁面內容替換掉。理論上,我們藉助後端的寫法應該是這樣的邏輯:
route.get('/a', ctx => {
if (global.logined) {
ctx.render(webveiwA);
} else {
ctx.render(webviewB);
}
})複製程式碼
我們需要根據周邊的環境變數去自動選擇該渲染哪個頁面,如此的邏輯才是我們想要的。所以我們會根據這樣的思路來設計動態路由。在nodejs
的世界中,這種模式已經非常常見,比如express-router
與koa-router
,都是採用這種設計思路來實現動態話的路由體系。我觀察了前端的發展,都沒有提出在前端實現這樣的邏輯。於是,我們便開始研究如何將這種思路架設在前端使用,來獲得更理想的邏輯體驗。
Miox從來都不是依賴任何MVX框架來實現
為什麼這麼說呢?原因非常簡單。在公司裡面,我們大概有90%的H5業務都是採用Miox來實現的。我們的技術棧其實是Vue,因為Miox對Vue的結合太過深入,所以自然有部分小夥伴認為Miox是基於Vue來開發的,也就是說Miox是依賴著Vue?其實不是,Miox並不依賴任何框架實現。我來舉個列子:
我們的電腦,如果換了一個顯示卡,那麼必須要裝顯示卡驅動。根據不同的顯示卡驅動,表現也不同。如果我們將Vue當作一個顯示卡,而Miox當作我們的電腦,那麼我們需要一份顯示卡驅動來讓整臺電腦接受這塊顯示卡。
所以我提出一個概念,就是渲染驅動的概念。Vue僅僅是我們Miox的一個渲染引擎,用來渲染頁面的,可以理解為模板。我們還需要一份驅動告訴Miox,來說明Vue的渲染是如何在Miox實現的。這部分可以從這裡看明白。當然,不僅僅是Vue,我們還能夠將React接入到Miox中。理論上,只要能提供對應的渲染驅動,都可以將任意的渲染引擎接入到Miox中來使用。自然的,大家的書寫都將會變成那種引擎的書寫方式。這就是我們的插拔式設計。在公司裡面使用的時候,我們不必在意使用何種渲染引擎,Miox都可以支援,同時幫您管理好整個路由體系。
基於中介軟體實現
在前端,如果我們能夠用中介軟體來攔截整個邏輯過程的話,對開發是相當有利的,不僅僅在程式碼層面能夠提高可讀性,同時可以在具體業務層面提高效率。我們用後端路由邏輯來舉個例子:
當我們遇到一些API都是需要經過登入驗證的時候,我們可以將
/authorize
的路由字首都使用中介軟體統一處理。其他的都不走驗證邏輯。
const Authorize = require('./auth');
const AuthorizeApi = require('./auth/routes');
route.use(
'/authorize',
Authorize.connect(), // Authorize.connect是統一的驗證邏輯程式碼
AuthorizeApi.routes(),
AuthorizeApi.allowedMethods()
);複製程式碼
如此當通過/authorize
的路由都需要經過Authorize.connect()
來驗證是否具有許可權。這使得程式碼非常簡潔易懂。
Miox的設計便是如此,通過這種中介軟體的架構,使得我們在前端獲得了統一攔截處理的能力。在實際生產中,我們很多地方都用到了中介軟體來處理統一的校驗邏輯,使得程式碼維護性得到很大地提升。
那麼如果不使用呢?我們需要在進入頁面的時候,每個頁面中都要嵌入一段程式碼來處理許可權問題,不僅僅程式碼量增加,而且對於之後的維護,可能會產生漏改的問題。
具體想了解中介軟體的小夥伴,我推薦去看下koa-router。
快取頁面得到更好的渲染效能
說到快取,這個話題過於龐大,對於Miox的快取機制,我只能簡單介紹一下,有興趣的小夥伴可以看下原始碼。
在開發過程中,特別是對於開發移動端頁面,我們需要儲存前一個頁面滾動位置,那麼我們在切換到另一個頁面的時候是不能將前一個頁面銷燬的,原因是我們希望回到前一個頁面的時候還是停留在之前滾動的位置。那麼我們需要快取這個頁面來確保位置的不改動。Miox模擬了history的部分API,同時增加了一層頁面堆疊。我們需要維護這層頁面堆疊來確保頁面的可溯性。每次我們通過一種演算法來動態比較路由與頁面的關係,從而從這個堆疊中選出我們想要的快取頁面。當然,沒有這個對應關係的時候,我們自然是要建立的。我們基於盡最大可能限度複用頁面的宗旨,來快取這些頁面與路由的關係。
可能有人要問,如果快取過多,對於頁面的切換會有效能上的影響吧?是的,過多的頁面快取也是阻礙效能的關鍵。這裡我們進行了快取個數的優化。在啟動miox服務的時候,我們有一個配置的引數max
,一般預設為一個,當然,大家也可以自己自由設定最大個數,來保障快取的效能問題。
歷史遺留問題:history方向問題的解決方案
在History中,我們都承認無法判斷出當前瀏覽器行為的方向性,所以無法給出我們想要的方向來自動做頁面切換的效果。為了解決這個問題,我們蒐集了業界的解決方案,採用sessionStorage
來模擬history堆疊,從而解決這個問題(新history的API中已經增加了history.index
動態屬性來告訴我們現在位於堆疊中的位置)。我們將此方案整合到了Miox中,並且對其加強,來告訴動畫引擎這次行為在瀏覽器中是如何表現的。自然,動畫引擎就能夠根據這個來自動切換頁面的動畫,達到自動處理的效果。
官方提供了一個簡單的模組來支援動畫,當然小夥伴想要自定義動畫也是非常簡單的,具體見這裡。
獨立的頁面生命週期
在傳統的MVX框架中提供了元件的生命週期,我們在某種意義上也認為是頁面的生命週期,但是我們對比原生IOS的週期行為,還是有所欠缺的。比如說active
生命週期。這個是什麼意思呢?我來舉例說明:
當一個頁面被推入後臺,又被喚起的時候,我們根本不知道它是不是再一次被啟用,我們只能知道頁面又一次被進入,第二次進入的概念是需要很多程式碼來輔助完成的。而在傳統框架中,很難觸發再一次的
mounted
生命週期,因為頁面已經被mounted
過了。Miox提供了這樣的生命週期的定義。
// use vue.js
export default Vue.extend({
mounted() {
this.$on('webview:active', this.activeLife);
},
methods: {
activeLife() {
console.log('我被喚起了');
}
}
})複製程式碼
當然,我們也可以將這些生命週期直接拋到全域性去,用於全域性的監控。
app.on('webview:mounted', webview => {
console.log(webview);
})複製程式碼
對於前置的生命週期,我們同樣提供了以下的生命週期來輔助:
- webview:enter
- webview:leave
- webview:beforeEnter
- webview:beforeLeave
這些週期能夠讓你很好地掌控整個過程,對於自動埋點什麼的功能非常實用。
服務端渲染
目前主流的架構都支援了服務端渲染來增強SEO的能力,那麼對於Miox而言,也需要支援他們的服務端渲染。考慮到Miox自身會給渲染出來的內容包裹一些程式碼,所以,我們需要自己實現SSR。當然,渲染引擎的SSR實現是交給自己來完成的。也就是說我們需要給他包裹一層SSR渲染。
Miox暫時支援Vue的SSR渲染,後續會逐步新增對於React的SSR渲染。還有比如百度的san.js
其實也可以接入進來實現SSR渲染。服務端渲染並不是太麻煩,如果大家能夠掌握Miox的執行原理的話。
最後
對於開源,我們團隊內部做了很多努力,也諮詢了很多大牛,希望能夠給大家創造出一份簡易開發的架構來幫助大家完成業務。目前,團隊後續計劃如下:
- 完善SSR的文件
- 規範化Github上的git message
- 維護Github上的Issues和PR
- 單測和程式碼覆蓋率增強
- 對路由架構更多思考來完善Miox
- 提供Pc端的演示demo
- 提供Miox實際開發場景下的開發程式碼示例
希望大家看到這篇文章後可以支援我們,給我們多提供一些意見和建議,讓我們共同將Miox完善下去。喜歡的小夥伴,幫忙點個Star。
專案開源在 GitHub: 51nb/Miox