在軟件開發中,逐漸出現了類,模塊化,組件化,設計模式等來解耦和拆分咱們的代碼,使得代碼更易讀,易維護。而微前端架構其實也是一種新的思想來幫助咱們更好的拆分一些如今方式沒法解決的問題而已。html
相似與操做系統,將系統的實現與系統的基本操做規則區分開來。將核心功能模塊化,劃分紅幾個獨立的進程,各自運行,全部的服務進程都運行在不一樣的地址空間,讓服務各自獨立。前端
如上圖所示:微前端落地到瀏覽器,瀏覽器將承載一個html頁面,在頁面中安裝,啓動相應的服務。web
我的感受除去微前端的一些技術實現,它主要難點就在於如何拆分應用,在拆分的同時還需結合團隊規模等問題:npm
並非全部應用都適合使用微前端架構,在一個簡單的單體應用中使用,反而拔苗助長。
微前端主要應用在大型的互聯網應用,該應用可能具有幾個特色:系統龐大到不少人去開發,頁面數量達到某個量級,在這種狀況下可能會致使系統難以維護,代碼量逐漸增大,同時也使得協做方面難以管控,包括測試,迴歸,而且在工程化方面編譯顯的耗時。總結有如下幾點狀況bootstrap
和微服務同樣,微前端的獨立部署是關鍵。減小服務間的耦合性,不管前端代碼部署在哪裏,每一個微前端都有本身持續交付pipeline,進行構建,測試,部署到生產環境中。最後將多個子系統集成到主系統中。設計模式
基座工程:數組
子應用:promise
路由控制層主要監控路由的變動,經過路由變動來控制子系統是否須要加載。子系統路由發生變化首先會有主系統攔截路由變動時間,決定是否加載子系統,若是路由不須要切換子系統,則將該事件交還給子系統處理。瀏覽器
應用註冊其實就是相似於平時的帳號註冊,須要填寫一些基本信息,而在微前端中所指的應用註冊主要是指app名稱,以及對應子系統配置文件的url。而在single-spa中registerApplication主要包含三個參數,appName,app,activeWhen緩存
在主應用中會註冊多個子應用,而這些子應用的信息及狀態會進行保存和管理。
註冊對應的子系統,當路由規則匹配到某個子系統的時候會先去加載該子系統的manifest文件來獲取該子系統的信息,經過該文件去加載對應的子系統。
single-spa執行隊列有兩個入口,一是經過監聽路由的變化,二是register函數。
每次觸發會先判斷是否已啓動,若是未啓動則執行loadApps去加載須要加載的app。若是已啓動則調用performAppChanges函數去mount app。
在執行期間,若是有新的app進來也就是隊列發生了變動,會將新的app緩存待執行完當前的再循環執行下一次,這個操做由finishUpAndReturn這個函數內部作判斷來完成。
single-spa主要有一下幾個模塊:
下面來大體看一下幾個核心函數大體的實現
該函數接收4個參數:
export function registerApplication( appNameOrConfig, appOrLoadApp, activeWhen, customProps ) { const registration = sanitizeArguments( appNameOrConfig, appOrLoadApp, activeWhen, customProps ); apps.push( assign( { loadErrorTime: null, status: NOT_LOADED, }, registration ) ); reroute(); }
function urlReroute() { reroute([], arguments); } window.addEventListener("hashchange", urlReroute); window.addEventListener("popstate", urlReroute);
const originalAddEventListener = window.addEventListener; window.addEventListener = function(eventName, fn) { if (typeof fn === "function") { if ( routingEventsListeningTo.indexOf(eventName) >= 0 && !find(capturedEventListeners[eventName], listener => listener === fn) ) { capturedEventListeners[eventName].push(fn); return; } } return originalAddEventListener.apply(this, arguments); };
let appChangeUnderway = false, peopleWaitingOnAppChange = []; function reroute(pendingPromises = [], eventArguments) { ....... }
函數接收兩個參數:
下面都是reroute函數裏面的代碼:
if (appChangeUnderway) { return new Promise((resolve, reject) => { peopleWaitingOnAppChange.push({ resolve, reject, eventArguments }); }); }
let wasNoOp = true; if (isStarted()) { appChangeUnderway = true; return performAppChanges(); } else { return loadApps(); }
function loadApps() { return Promise.resolve().then(() => { const loadPromises = getAppsToLoad().map(toLoadPromise); if (loadPromises.length > 0) { wasNoOp = false; } return ( Promise.all(loadPromises) .then(callAllEventListeners) .then(() => []) .catch(err => { callAllEventListeners(); throw err; }) ); }); }
function performAppChanges() { return Promise.resolve().then(() => { const unloadPromises = getAppsToUnload().map(toUnloadPromise); const unmountUnloadPromises = getAppsToUnmount() .map(toUnmountPromise) .map(unmountPromise => unmountPromise.then(toUnloadPromise)); const allUnmountPromises = unmountUnloadPromises.concat(unloadPromises); if (allUnmountPromises.length > 0) { wasNoOp = false; } const unmountAllPromise = Promise.all(allUnmountPromises); const appsToLoad = getAppsToLoad(); /* We load and bootstrap apps while other apps are unmounting, but we * wait to mount the app until all apps are finishing unmounting */ const loadThenMountPromises = appsToLoad.map(app => { return toLoadPromise(app) .then(toBootstrapPromise) .then(app => { return unmountAllPromise.then(() => toMountPromise(app)); }); }); if (loadThenMountPromises.length > 0) { wasNoOp = false; } /* These are the apps that are already bootstrapped and just need * to be mounted. They each wait for all unmounting apps to finish up * before they mount. */ const mountPromises = getAppsToMount() .filter(appToMount => appsToLoad.indexOf(appToMount) < 0) .map(appToMount => { return toBootstrapPromise(appToMount) .then(() => unmountAllPromise) .then(() => toMountPromise(appToMount)); }); if (mountPromises.length > 0) { wasNoOp = false; } return unmountAllPromise .catch(err => { callAllEventListeners(); throw err; }) .then(() => { /* Now that the apps that needed to be unmounted are unmounted, their DOM navigation * events (like hashchange or popstate) should have been cleaned up. So it's safe * to let the remaining captured event listeners to handle about the DOM event. */ callAllEventListeners(); return Promise.all(loadThenMountPromises.concat(mountPromises)) .catch(err => { pendingPromises.forEach(promise => promise.reject(err)); throw err; }) .then(finishUpAndReturn); }); }); }
能不用則不用!!!!!