摘要:我們繼續(xù)沿用了原來就有的,借此把融入整個(gè)微前端框架,而已經(jīng)改造的則不需要我們的開發(fā)團(tuán)隊(duì),分框架組和各個(gè)業(yè)務(wù)組。項(xiàng)目該項(xiàng)目是整個(gè)微前端項(xiàng)目的入口。本坑實(shí)踐它很大的理由也是用自己的方法初探微前端實(shí)踐方法的可行性。
在寫這篇文章的一個(gè)多月前,本坑還不知道微前端是什么,大概從字面上的含義是比較小的前端項(xiàng)目。
本坑開始實(shí)踐它,是由于工作要求。改造一個(gè)運(yùn)行多年,前端用jsp寫的服務(wù)平臺(tái)項(xiàng)目(以下簡(jiǎn)稱該平臺(tái))。改造它是改造它的前端架構(gòu)。改造它的原因是比較多人反饋,其頁面加載和渲染顯得吃力,頁面切換后首屏等待時(shí)間長(zhǎng)的問題,交互體驗(yàn)舒適度不可避免的下降了,特別是在老式電腦面前。
該平臺(tái)業(yè)務(wù)比較多,所以組長(zhǎng)希望前端框架組能把平臺(tái)中的前端部分分離出來,最好用當(dāng)下滿大街的Vue、能夠按照各個(gè)一級(jí)菜單分成若干前端子項(xiàng)目,用戶訪問依然是整體的項(xiàng)目,同時(shí)這一改造實(shí)施過程不需要重做一個(gè)、而是整個(gè)500多個(gè)頁面從局部開始、是逐步、兼容的,新舊同時(shí)運(yùn)行,直至整體被替換。(ps:不重做?這......科學(xué)嗎?)
其實(shí)大概知道慢在哪里,但是不知道究竟慢在具體哪個(gè)部分。和其他一以貫之的類似管理平臺(tái)布局并無不同。左邊導(dǎo)航欄,上面頂欄,右側(cè)內(nèi)容欄,整體頁面是一個(gè)index.jsp。上面提到的內(nèi)容欄是一個(gè)iframe,里面通過切換src來切換頁面。更多的業(yè)務(wù)造就更多頁面,更多頁面帶來更多的加載。加上長(zhǎng)時(shí)間沒有做好資源加載的管理,導(dǎo)致渲染一次頁面需要加載大量js,css或者多次加載同一個(gè)文件的情況。該平臺(tái)大量的配置頁面生成,是通過easyui的來做的。通過數(shù)據(jù)來創(chuàng)造整個(gè)頁面dom節(jié)點(diǎn),也拖累了內(nèi)容完整呈現(xiàn)的時(shí)間。
我們使用谷歌瀏覽器performance可以最終追查到這個(gè)系統(tǒng)在哪些方面,哪個(gè)方法存在著哪些延遲。結(jié)論是:
1、混亂的項(xiàng)目資源管理導(dǎo)致大量的資源請(qǐng)求。
2、easyui和項(xiàng)目中不少的dom操作帶來大量的重排和重繪。
3、埋點(diǎn),插件使用不當(dāng)以及其他。
它是什么呢?
微前端的概念來自于之前流行的微服務(wù)。它的來源很大程度是來自于這篇文章 。微服務(wù)系統(tǒng)使得后臺(tái)服務(wù)架構(gòu)能夠比較好地規(guī)避越來越臃腫的體積帶來的性能下降。根據(jù)業(yè)務(wù)合理拆分成一個(gè)個(gè)的服務(wù),盡量避免一個(gè)子服務(wù)影響整個(gè)項(xiàng)目運(yùn)行的優(yōu)勢(shì),有效的進(jìn)行隔離。
那么,前端也有同樣的需求嗎?答案是肯定的。
今天,日益更新的前端技術(shù),已經(jīng)能夠把一個(gè)個(gè)頁面各個(gè)小元素打包成組件庫,功能包,在多個(gè)項(xiàng)目中引入使用。此外,我們不用再使用難受的iframe來聚合不同的項(xiàng)目,而是導(dǎo)出一個(gè)個(gè)web component,只需要import 到頁面就可以使用。把一個(gè)個(gè)子項(xiàng)目打包成一個(gè)個(gè)web component,聚合在入口項(xiàng)目之內(nèi)。這也許就是微前端現(xiàn)在比較時(shí)髦的樣子。
如果一個(gè)大項(xiàng)目有以下特點(diǎn),微前端可以在這些項(xiàng)目中運(yùn)用:
1、大項(xiàng)目有統(tǒng)一的入口,子項(xiàng)目頁面需要無刷新下切換,可是各個(gè)子項(xiàng)目在業(yè)務(wù)上和開發(fā)團(tuán)隊(duì)上是不同的。
2、項(xiàng)目過大,打包、運(yùn)行、部署效率出現(xiàn)顯著下降的問題。這時(shí)希望能根據(jù)業(yè)務(wù)拆分打包,部署。
回到本文開頭,一開始面對(duì)這樣的需求還是有些想辭職的沖動(dòng),因?yàn)橛X得需求有點(diǎn)不是符合實(shí)際,實(shí)際上要實(shí)施改版也是需要過程的。
不過靜下來想想,搜搜,翻了翻當(dāng)前項(xiàng)目的前端結(jié)構(gòu),隱隱約約似乎浮現(xiàn)一些需求可行性的線索。
因?yàn)轫?xiàng)目的最終目的是把整個(gè)jsp頁面改成vue來寫。而這一要求是逐步替換的過程,所以在改造過程中,同時(shí)要保證項(xiàng)目兼容jsp的頁面。我們繼續(xù)沿用了原來就有的iframe,借此把jsp融入整個(gè)微前端框架,而已經(jīng)改造的micro則不需要iframe.
我們的開發(fā)團(tuán)隊(duì),分框架組和各個(gè)業(yè)務(wù)組。其中每個(gè)業(yè)務(wù)組有3到8個(gè)人,他們大多數(shù)是后端背景,主要做的也是后端開發(fā)??蚣芙M有前端和后端。為了應(yīng)付龐大的業(yè)務(wù)開發(fā)需求。大部分后端人員都需要使用jsp,js等前端技術(shù)進(jìn)行開發(fā)。
框架組為了減少他們的前端開發(fā)門檻,前端框架組會(huì)封裝好easyui組件,提供業(yè)務(wù)組使用。所以,正如前文提到,后端人員是通過數(shù)據(jù),結(jié)合框架組提供的組件來完成頁面的開發(fā)的。從某種角度來說,數(shù)據(jù)配置的頁面對(duì)接下來的改造工作有一定的幫助,因?yàn)榇蟛糠猪撁婵梢酝瑫r(shí)改寫。
我們對(duì)整個(gè)項(xiàng)目進(jìn)行了大致的分類。
1、portal 項(xiàng)目:該項(xiàng)目是整個(gè)微前端項(xiàng)目的入口。里面含有l(wèi)oader,用以加載各個(gè)項(xiàng)目模塊。它也嵌入到子項(xiàng)目中,使得多帶帶運(yùn)行子項(xiàng)目和portal項(xiàng)目一樣的界面要求。
2、permission 項(xiàng)目:該項(xiàng)目包含菜單組件,登錄頁面,頂欄組件,權(quán)限控制等。在任何環(huán)境下,它都必須首先加載,為子項(xiàng)目模塊掛載提供錨點(diǎn)。
3、common項(xiàng)目:該項(xiàng)目包含公共業(yè)務(wù)組件。比如封裝好的頁面,可以直接給不太能夠掌握vue項(xiàng)目的后端人員更加友好的去使用。
4、業(yè)務(wù)項(xiàng)目:就是指業(yè)務(wù)組各個(gè)模塊開發(fā)的前端項(xiàng)目。什么樣的業(yè)務(wù)分為一個(gè)項(xiàng)目,這點(diǎn)由產(chǎn)品和技術(shù)人員一起來決定。相對(duì)于portal項(xiàng)目,業(yè)務(wù)項(xiàng)目相當(dāng)于它的子項(xiàng)目。
前端框架組必須提供一套統(tǒng)一的業(yè)務(wù)項(xiàng)目的前端模板,可以在確認(rèn)新建的子項(xiàng)目后迅速的加入到整個(gè)項(xiàng)目中,進(jìn)行開發(fā)和部署,而這一過程不能影響其他項(xiàng)目的部署和運(yùn)行。
除了上述方案浮出水面,還會(huì)在改造過程中遇到一個(gè)個(gè)細(xì)節(jié)問題。 不過在大方向,框架組成上,前端結(jié)構(gòu)上做好了,細(xì)節(jié)問題也會(huì)隨耐心和時(shí)間被解決。
本坑根據(jù)以上的分類,大致進(jìn)行說明其實(shí)現(xiàn)。這其中結(jié)合了不少前輩之經(jīng)驗(yàn),在文章結(jié)尾處鳴謝。
portal 項(xiàng)目是整個(gè)項(xiàng)目部署的入口,它的核心來自于single-spa
在整個(gè)項(xiàng)目結(jié)構(gòu)中它將集成到每一個(gè)子項(xiàng)目。集成的方式很粗暴簡(jiǎn)單,就是外聯(lián)加載。
portal負(fù)責(zé)根據(jù)不同的環(huán)境來對(duì)應(yīng)的組件和app,同時(shí)也安裝各個(gè)app,卸載各個(gè)app等,它負(fù)責(zé)app在single-spa的生命周期。比如集成模式下根據(jù)環(huán)境和路由加載對(duì)應(yīng)的app,而在子項(xiàng)目運(yùn)行時(shí)只加載公共組件和不同業(yè)務(wù)的app。
那么protal是如何加載的呢?
protal維護(hù)了一個(gè)json里面包含了各個(gè)子項(xiàng)目的index.html的信息,通過匹配index.html里面的src 、link,加載各項(xiàng)資源。
module.exports = { common: { webName:"common", globalVarName: "mfe:common", componentsTarget: "/common/release/components/web.html", resourcePatterns: ["/components.[0-9a-z]{8}.js/g"], loadType:"before" }, permission: { webName:"permission", globalVarName: "mfe:permission", // URL 匹配模式 matchUrlHash: "", // 微前端地址 componentsTarget: "/permission/release/components/web.html", webTarget:"/permission/release/web/web.html", // 資源匹配模式 resourcePatterns: ["/common.[0-9a-z]{8}.css/g","/store.[0-9a-z]{8}.js/g", "/publicPath.[0-9a-z]{8}.js/g","/singleSpaEntry.[0-9a-z]{8}.js/g","/components.[0-9a-z]{8}.js/g"], //是否要在項(xiàng)目啟動(dòng)前加載,before為提前加載,after為hash變化后加載 loadType:"before" }, app4vue:{ webName:"repair-order", globalVarName: "mfe:app4vue", matchUrlHash: "/layout/repair-order", webTarget: "/app4/release/web/web.html", resourcePatterns: ["/common.[0-9a-z]{8}.css/g","/store.[0-9a-z]{8}.js/g", "/singleSpaEntry.[0-9a-z]{8}.js/g"], loadType:"after" } }
async gatherResource () { const self = this // const spaEntry = "portal" const web = self._webName //如果是微前端聚合模式 if (window._IS_SIGLE_PORTAL) { if (web !== "mfe-permission") { await self.loadComponents(micros.common) await self.loadApp(micros.permission) } } else { if (web === "mfe-permission") { await self.loadComponents(micros.common) } else { if (web !== "mfe-common") { await self.loadComponents(micros.common) } await self.loadApp(micros.permission) } } // return new Promise(resolve => resolve("loader:all Finish!")) }
permission負(fù)責(zé)登錄頁,layout中的菜單欄,頂欄。所有的子項(xiàng)目app都必須掛載到permission項(xiàng)目中的顯示區(qū)塊里。也就是說permssion會(huì)提供錨點(diǎn)給子項(xiàng)目掛載。
所以permission負(fù)責(zé)路由的控制,這里的路由有總體路由和app內(nèi)路由切換。如果app切換的路由控制涉及到singer-spa,app的切換會(huì)觸發(fā)single-spa:routing-event事件,portal監(jiān)聽該事件 unmounted和mounted app,如果app內(nèi)部的路由切換,需要觸發(fā)app內(nèi)部路由切換。
本坑嘗試監(jiān)聽permission的 hash,由于vue新版本,hash實(shí)際是監(jiān)聽不了的,所以監(jiān)聽hash是沒辦法的。
這看上去會(huì)干涉到子項(xiàng)目的代碼,如果哪位大神有好方法可以在評(píng)論區(qū)貼上你的看法。
業(yè)務(wù)項(xiàng)目的獨(dú)立運(yùn)行只會(huì)發(fā)生在開發(fā)模式之下,在生產(chǎn)或者測(cè)試環(huán)境并不會(huì)獨(dú)立運(yùn)行。
集成環(huán)境下輸出成三個(gè)周期,提供給single-spa
export var global = {}; export const bootstrap = () => { return Promise.resolve(); } export function mount (props) { Vue.mixin({ data: function () { return { props } } }) return Promise.resolve().then(() => { createDomElement(); global.instance = new Vue({ el: "#app4", router, render: h => h(App) }) }) } export function unmount () { return Promise.resolve().then(() => { global.instance.$destroy(); global.instance.$el.innerHTML = ""; delete global.instance }) } function createDomElement () { // Make sure there is a div for us to render into let node = document.getElementById("main-content"); let el = document.getElementById("app4"); if (!el) { el = document.createElement("div"); el.id = "app4"; node.appendChild(el); } return el; }
開發(fā)模式獨(dú)立運(yùn)行
const init = async () => { //啟動(dòng)single-spa const loader = new Loader(process.env) await loader.startSingleSpa() Vue.mixin({ data () { return { loader } } }) Vue.config.productionTip = false; //permission渲染后再掛載自己上去 window.addEventListener("single-spa:main-content-mount", evt => { if (!window.vim) { window.vm = new Vue({ el: loader.createHookEle("app4"),//掛載自己 // store, router, render: h => h(App) }) } }) } init()
common 類似插件的打包,不贅述。
import "./styles/vars.scss" import MButton from "./components/button" const components = [MButton]; const install = function (Vue) { if (install.installed) return; components.map(component => { Vue.use(component); }); }; // 全局引用可自動(dòng)安裝 if (typeof window !== "undefined" && window.Vue) { install(window.Vue); } export default { install, MButton }
幾個(gè)主要源碼采用外聯(lián)形式
single-spa是怎么聚合各個(gè)獨(dú)立的vue app的呢?本坑嘗試?yán)斫馑?/p>
Single-spa 把a(bǔ)pp聚合成三個(gè)周期 bootstrap mounted unmounted,這三個(gè)周期是需要自己去配置改寫的,其實(shí)single-spa還有其他周期,不需要改寫。
也就是對(duì)single-spa來說app只有這三個(gè)東西需要特別的關(guān)心,app的卸載和加載。其他都是app自己的事。mounted、unmonuted和vue app 獨(dú)立運(yùn)行的mounted和destroy本質(zhì)上沒有區(qū)別,只是single-spa做了一層代理。代理完成app的掛載和銷毀。
single-spa內(nèi)部也保存了一個(gè)數(shù)組,負(fù)責(zé)維護(hù)內(nèi)部注冊(cè)的app.注冊(cè)完后代理完成app的掛載。卸載后銷毀之。
如果親愛的客官,你也遇到這種問題,用這種改法是沒有保證的。
本坑實(shí)踐它很大的理由也是用自己的方法初探微前端實(shí)踐方法的可行性。
這種大跨度的改變會(huì)帶來不少不可預(yù)測(cè)的底層沖突,前后端的沖突。
第二呢,這種大跨度的改變幾乎等同于重構(gòu)。
第三呢,微前端方案也有自身的局限性,比如對(duì)庫版本的管理,app樣式的隔離沒有做到很好等等。還是要根據(jù)實(shí)際來衡量。
針對(duì)太過古老的系統(tǒng)比如jsp,可以先嘗試把jsp轉(zhuǎn)為html,在前端性能上多改進(jìn),再另行考慮綜合性改版。
估計(jì)很多地方搞的不好,代碼或者信息錯(cuò)誤,歡迎各位指導(dǎo)。
single-spa官網(wǎng)
微前端實(shí)踐
前端微服務(wù)化解決方案
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/54165.html
摘要:為了幫助用戶更好地完成消費(fèi)決策閉環(huán),馬蜂窩上線了大交通業(yè)務(wù)?,F(xiàn)在,用戶在馬蜂窩也可以完成購買機(jī)票火車票等操作。第二階段架構(gòu)轉(zhuǎn)變及服務(wù)化初探從年開始,整個(gè)大交通業(yè)務(wù)開始從架構(gòu)向服務(wù)化演變。 交通方式是用戶旅行前要考慮的核心要素之一。為了幫助用戶更好地完成消費(fèi)決策閉環(huán),馬蜂窩上線了大交通業(yè)務(wù)。現(xiàn)在,用戶在馬蜂窩也可以完成購買機(jī)票、火車票等操作。 與大多數(shù)業(yè)務(wù)系統(tǒng)相同,我們一樣經(jīng)歷著從無到有...
閱讀 3214·2023-04-25 20:43
閱讀 1797·2021-09-30 09:54
閱讀 1656·2021-09-24 09:47
閱讀 2970·2021-09-06 15:02
閱讀 3573·2021-02-22 17:09
閱讀 1325·2019-08-30 15:53
閱讀 1514·2019-08-29 17:04
閱讀 2030·2019-08-28 18:22