您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“微前端框架qiankun源碼分析”,內容詳細,步驟清晰,細節處理妥當,希望這篇“微前端框架qiankun源碼分析”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
要了解qiankun的實現機制,那我們不得不從其底層依賴的single-spa說起。隨著微前端的發展,我們看到在這個領域之中出現了各式各樣的工具包和框架來幫助我們方便快捷的實現自己的微前端應用。在發展早期,single-spa可以說是獨樹一幟,為我們提供了一種簡便的微前端路由工具,大大降低了實現一個微前端應用的成本。我們來看一下一個典型single-spa微前端應用的架構及代碼。
主應用(基座):
作為整個微前端應用中的項目調度中心,是用戶進入該微前端應用時首先加載的部分。在主應用中,通過向single-spa提供的registerApplication
函數傳入指定的參數來注冊子應用,這些參數包括子應用名稱name
、子應用如何加載app
、子應用何時激活activeWhen
、以及需要向子應用中傳遞的參數customProps
等等。在完成整體注冊后調用start
函數啟動整個微前端項目。
// single-spa-config.js import { registerApplication, start } from 'single-spa'; // Config with more expressive API registerApplication({ name: 'app1', app: () => import('src/app1/main.js'), activeWhen: ['/myApp', (location) => location.pathname.startsWith('/some/other/path')], customProps: { some: 'value', } }); start();
子應用:
子應用是實際展示內容的部分,最主要的工作是導出single-spa中所規定的生命周期函數,以便于主應用調度。其中,bootstrap在子應用第一次加載時調用,mount在子應用每次激活時調用,unmount在子應用被移出時調用。此外在這些生命周期函數中我們可以看到props參數被傳入,這個參數中包含了子應用注冊名稱、singleSpa實例、用戶自定義參數等信息,方便子應用的使用。
console.log("The registered application has been loaded!"); export async function bootstrap(props) { const { name, // The name of the application singleSpa, // The singleSpa instance mountParcel, // Function for manually mounting customProps, // Additional custom information } = props; // Props are given to every lifecycle return Promise.resolve(); } export async function mount(props) {...} export async function unmount(props) {...}
可以看到Single-spa作為一個微前端框架領域最為廣泛使用的包,其為我們提供了良好的子應用路由機制。但是除此之外,single-spa也留下了很多需要用戶自行解決的問題:
子應用究竟應該如何加載,從哪里加載?
子應用運行時會不會互相影響?
主應用與子應用、子應用之間具體可以通過customProps互相通信,但是怎樣才能知道customProps發生了變化呢?
因此,市面上出現了很多基于single-spa二次封裝的微前端框架。他們分別使用不同的方式,基于各自不同的側重點包裝出了更加完善的產品。對于這些產品,我們可以將single-spa在其中的作用類比位理解為react-router之于react項目的作用——single-spa作為一個沒有框架、技術棧限制的微前端路由為它們提供了最底層的子應用間路由及生命周期管理的服務。在近幾年微前端的發展壯大過程中,早期推出并經久不衰的阿里qiankun框架算的上是一枝獨秀了。
作為目前微前端領域首屈一指的框架,qiankun無論是從接入的方便程度還是從框架本身提供的易用性來說都是可圈可點的。qiankun基于single-spa進行了二次開發,不但為用戶提供了簡便的接入方式(包括減少侵入性,易于老項目的改造),還貼心的提供了沙箱隔離以及實現了基于發布訂閱模式的應用間通信方式,大大降低了微前端的準入門檻,對于微前端工程化的推動作用是不可忽視的。
因為其基于single-spa二次開發, 所以qiankun微前端架構與第一章中所提及的并無二致,下面我們列出一個典型的qiankun應用的代碼并類比其與single-spa的代碼區別。
主應用:
這里qiankun將single-spa中的app改為了entry并對其功能進行了增強,用戶只需要輸入子應用的html入口路徑即可,其余加載工作由qiankun內部完成,當然也可以自行列出所需加載的資源。此外加入了container選項,讓用戶顯示指定并感知到子應用所掛載的容器,簡化了多個子應用同時激活的場景。
import { registerMicroApps, start } from 'qiankun'; registerMicroApps([ { name: 'react app', // app name registered entry: '//localhost:7100', container: '#yourContainer', activeRule: '/yourActiveRule', }, { name: 'vue app', entry: { scripts: ['//localhost:7100/main.js'] }, container: '#yourContainer2', activeRule: '/yourActiveRule2', }, ]); start();
子應用:
與single-spa基本一致,導出了三個生命周期函數。這里可以看到在mount中我們手動將react應用渲染到了頁面上,反之在unmount中我們將其從頁面上清除。
/** * bootstrap 只會在微應用初始化的時候調用一次,下次微應用重新進入時會直接調用 mount 鉤子,不會再重復觸發 bootstrap。 * 通常我們可以在這里做一些全局變量的初始化,比如不會在 unmount 階段被銷毀的應用級別的緩存等。 */ export async function bootstrap() { console.log('react app bootstraped'); } /** * 應用每次進入都會調用 mount 方法,通常我們在這里觸發應用的渲染方法 */ export async function mount(props) { ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root')); } /** * 應用每次 切出/卸載 會調用的方法,通常在這里我們會卸載微應用的應用實例 */ export async function unmount(props) { ReactDOM.unmountComponentAtNode( props.container ? props.container.querySelector('#root') : document.getElementById('root'), ); }
可以看到,由于其幫助我們完成了子應用的加載工作,所以用戶的配置相比于single-spa更為簡便了。但是,除了這個明面上的工作,qiankun還在暗處為我們的易用性做出了很多努力,接下來,我們會圍繞著以下三個方面來深入剖析qiankun內部源碼和相關實現原理:
qiankun如何實現用戶只需配置一個URL就可以加載相應子應用資源的;
qiankun如何幫助用戶做到子應用間獨立運行的(包括JS互不影響和CSS互不污染);
qiankun如何幫助用戶實現更簡便高效的應用間通信的;
qiankun的子應用注冊方式非常簡單,用戶只需要調用registerMicroApps函數并將所需參數傳入即可.前文中我們說到qiankun是基于single-spa二次封裝的框架,因此qiankun中的路由監聽和子應用生命周期管理實際上都是交給了single-spa來進行實現的。我們一起來看一下該方法的實現方式(部分截取)
import { registerApplication } from 'single-spa'; let microApps: Array<RegistrableApp<Record<string, unknown>>> = []; export function registerMicroApps<T extends ObjectType>( apps: Array<RegistrableApp<T>>, lifeCycles?: FrameworkLifeCycles<T>, ) { // 判斷應用是否注冊過,保證每個應用只注冊一次 const unregisteredApps = apps.filter((app) => !microApps.some((registeredApp) => registeredApp.name === app.name)); microApps = [...microApps, ...unregisteredApps]; unregisteredApps.forEach((app) => { // 取出用戶輸入的參數 const { name, activeRule, loader = noop, props, ...appConfig } = app; // 調用single-spa的子應用注冊函數,將用戶輸入的參數轉換為single-spa所需的參數 registerApplication({ name, // 這里提供了single-spa所需的子應用加載方式函數 app: async () => { loader(true); await frameworkStartedDefer.promise; // 調用轉換函數loadApp將用戶輸入的url等解析轉換運行,最終生成增強后的子應用生命周期函數(包括mount,unmount,bootstrap) const { mount, ...otherMicroAppConfigs } = ( await loadApp({ name, props, ...appConfig }, frameworkConfiguration, lifeCycles) )(); // 返回值為loadApp生成的一系列生命周期函數,其中mount函數數組再次增強 return { mount: [async () => loader(true), ...toArray(mount), async () => loader(false)], ...otherMicroAppConfigs, }; }, activeWhen: activeRule, customProps: props, }); }); }
可以看到,qiankun在子應用加載上所做的工作就是將用戶調用registerMicroApps
時所提供的參數經過一系列處轉換之后,改造成single-spa中registerApplication
所需要的參數。下面,我們給出qiankun中實現該轉換子的主要函數loadApp
的部分實現代碼(源代碼地址github.com/umijs/qiank…
import { importEntry } from 'import-html-entry'; export async function loadApp<T extends ObjectType>( app: LoadableApp<T>, configuration: FrameworkConfiguration = {}, lifeCycles?: FrameworkLifeCycles<T>, ): Promise<ParcelConfigObjectGetter> { const { entry, name: appName } = app; const { singular = false, sandbox = true, excludeAssetFilter, globalContext = window, ...importEntryOpts } = configuration; // 。。。。。。 // 依賴了import-html-entry庫中的方法解析了用戶輸入的url(entry參數),得到了template(HTML模版),execScripts(所依賴JS文件的執行函數)以及assetPublicPath(公共資源路徑) const { template, execScripts, assetPublicPath } = await importEntry(entry, importEntryOpts); // 。。。。。。 // 在window沙箱中(global參數)執行entry依賴的js文件,得到相關生命周期( bootstrap, mount, unmount, update) // 這里可以忽略getLifecyclesFromExports函數,其返回與scriptExports一致,只是為了檢查子應用是否導出了必須的生命周期 const scriptExports: any = await execScripts(global, sandbox && !useLooseSandbox, { scopedGlobalVariables: speedySandbox ? trustedGlobals : [], }); const { bootstrap, mount, unmount, update } = getLifecyclesFromExports( scriptExports, appName, global, sandboxContainer?.instance?.latestSetProp, ); // 。。。。。 // 導出single-spa所需配置的getter方法(因為配置項與子應用掛在的container相關,默認為用戶輸入的container,后續用戶可以手動加載子應用并指定其渲染位置) const initialContainer = 'container' in app ? app.container : undefined; const parcelConfigGetter: ParcelConfigObjectGetter = (remountContainer = initialContainer) => { const parcelConfig: ParcelConfigObject = { name: appInstanceId, bootstrap, // mount數組在子應用渲染時依次執行 mount: [ // 。。。。。。 // 執行沙箱隔離 mountSandbox, // 調用用戶自定義mount生命周期,并傳入setGlobalState/onGlobalStateChange的應用間通信方法函數 async (props) => mount({ ...props, container: appWrapperGetter(), setGlobalState, onGlobalStateChange }), // 。。。。。。 ], // unmount數組在子應用卸載時依次執行 unmount: [ // 。。。。。。。 // 調用用戶自定義unmount生命周期 async (props) => unmount({ ...props, container: appWrapperGetter() }), // 卸載隔離沙箱 unmountSandbox, // 清理工作 async () => { render({ element: null, loading: false, container: remountContainer }, 'unmounted'); // 清理子應用對全局通信的訂閱 offGlobalStateChange(appInstanceId); // for gc appWrapperElement = null; syncAppWrapperElement2Sandbox(appWrapperElement); }, // 。。。。。。。 ], }; return parcelConfig; } return parcelConfigGetter }
可以看到,qiankun在其加載函數loadApp
中做了一些額外的工作。
為了方便使用,qiankun提供了基于url入口來加載子應用的方式。為了獲取用戶提供的html文件(或者資源文件數組)并解析出其中所需的資源,qiankun依賴了import-html-entry
庫中的相關方法,執行并得到了子應用導出的用戶自定義生命周期。
對用戶自定義的生命周期進行增強(包括掛載/卸載應用間的隔離沙箱,初始化或傳入應用間通信方法等等),返回框架增強后的生命周期函數數組并注冊在single-spa中。
經過源碼的分析我們可以看出,qiankun在子應用加載上就是作為中間層存在的,其主要作用就是簡化用戶對于子應用注冊的輸入,通過框架內部的方法轉換并增強了用戶的輸入最終將其傳入了single-spa之中,在后續的執行中真正負責子應用加載卸載的是single-spa。
讀到這里,這篇“微前端框架qiankun源碼分析”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。