溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

微前端框架qiankun源碼分析

發(fā)布時(shí)間:2023-02-10 09:14:10 來源:億速云 閱讀:100 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“微前端框架qiankun源碼分析”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“微前端框架qiankun源碼分析”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識(shí)吧。

一、single-spa簡(jiǎn)介

要了解qiankun的實(shí)現(xiàn)機(jī)制,那我們不得不從其底層依賴的single-spa說起。隨著微前端的發(fā)展,我們看到在這個(gè)領(lǐng)域之中出現(xiàn)了各式各樣的工具包和框架來幫助我們方便快捷的實(shí)現(xiàn)自己的微前端應(yīng)用。在發(fā)展早期,single-spa可以說是獨(dú)樹一幟,為我們提供了一種簡(jiǎn)便的微前端路由工具,大大降低了實(shí)現(xiàn)一個(gè)微前端應(yīng)用的成本。我們來看一下一個(gè)典型single-spa微前端應(yīng)用的架構(gòu)及代碼。

主應(yīng)用(基座):

作為整個(gè)微前端應(yīng)用中的項(xiàng)目調(diào)度中心,是用戶進(jìn)入該微前端應(yīng)用時(shí)首先加載的部分。在主應(yīng)用中,通過向single-spa提供的registerApplication函數(shù)傳入指定的參數(shù)來注冊(cè)子應(yīng)用,這些參數(shù)包括子應(yīng)用名稱name、子應(yīng)用如何加載app、子應(yīng)用何時(shí)激活activeWhen、以及需要向子應(yīng)用中傳遞的參數(shù)customProps等等。在完成整體注冊(cè)后調(diào)用start函數(shù)啟動(dòng)整個(gè)微前端項(xiàng)目。

// 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();

子應(yīng)用:

子應(yīng)用是實(shí)際展示內(nèi)容的部分,最主要的工作是導(dǎo)出single-spa中所規(guī)定的生命周期函數(shù),以便于主應(yīng)用調(diào)度。其中,bootstrap在子應(yīng)用第一次加載時(shí)調(diào)用,mount在子應(yīng)用每次激活時(shí)調(diào)用,unmount在子應(yīng)用被移出時(shí)調(diào)用。此外在這些生命周期函數(shù)中我們可以看到props參數(shù)被傳入,這個(gè)參數(shù)中包含了子應(yīng)用注冊(cè)名稱、singleSpa實(shí)例、用戶自定義參數(shù)等信息,方便子應(yīng)用的使用。

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作為一個(gè)微前端框架領(lǐng)域最為廣泛使用的包,其為我們提供了良好的子應(yīng)用路由機(jī)制。但是除此之外,single-spa也留下了很多需要用戶自行解決的問題:

子應(yīng)用究竟應(yīng)該如何加載,從哪里加載?

子應(yīng)用運(yùn)行時(shí)會(huì)不會(huì)互相影響?

主應(yīng)用與子應(yīng)用、子應(yīng)用之間具體可以通過customProps互相通信,但是怎樣才能知道customProps發(fā)生了變化呢?

因此,市面上出現(xiàn)了很多基于single-spa二次封裝的微前端框架。他們分別使用不同的方式,基于各自不同的側(cè)重點(diǎn)包裝出了更加完善的產(chǎn)品。對(duì)于這些產(chǎn)品,我們可以將single-spa在其中的作用類比位理解為react-router之于react項(xiàng)目的作用——single-spa作為一個(gè)沒有框架、技術(shù)棧限制的微前端路由為它們提供了最底層的子應(yīng)用間路由及生命周期管理的服務(wù)。在近幾年微前端的發(fā)展壯大過程中,早期推出并經(jīng)久不衰的阿里qiankun框架算的上是一枝獨(dú)秀了。

二、qiankun簡(jiǎn)介

作為目前微前端領(lǐng)域首屈一指的框架,qiankun無(wú)論是從接入的方便程度還是從框架本身提供的易用性來說都是可圈可點(diǎn)的。qiankun基于single-spa進(jìn)行了二次開發(fā),不但為用戶提供了簡(jiǎn)便的接入方式(包括減少侵入性,易于老項(xiàng)目的改造),還貼心的提供了沙箱隔離以及實(shí)現(xiàn)了基于發(fā)布訂閱模式的應(yīng)用間通信方式,大大降低了微前端的準(zhǔn)入門檻,對(duì)于微前端工程化的推動(dòng)作用是不可忽視的。

因?yàn)槠浠趕ingle-spa二次開發(fā), 所以qiankun微前端架構(gòu)與第一章中所提及的并無(wú)二致,下面我們列出一個(gè)典型的qiankun應(yīng)用的代碼并類比其與single-spa的代碼區(qū)別。

主應(yīng)用:

這里qiankun將single-spa中的app改為了entry并對(duì)其功能進(jìn)行了增強(qiáng),用戶只需要輸入子應(yīng)用的html入口路徑即可,其余加載工作由qiankun內(nèi)部完成,當(dāng)然也可以自行列出所需加載的資源。此外加入了container選項(xiàng),讓用戶顯示指定并感知到子應(yīng)用所掛載的容器,簡(jiǎn)化了多個(gè)子應(yīng)用同時(shí)激活的場(chǎng)景。

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();

子應(yīng)用:

與single-spa基本一致,導(dǎo)出了三個(gè)生命周期函數(shù)。這里可以看到在mount中我們手動(dòng)將react應(yīng)用渲染到了頁(yè)面上,反之在unmount中我們將其從頁(yè)面上清除。

/**
 * bootstrap 只會(huì)在微應(yīng)用初始化的時(shí)候調(diào)用一次,下次微應(yīng)用重新進(jìn)入時(shí)會(huì)直接調(diào)用 mount 鉤子,不會(huì)再重復(fù)觸發(fā) bootstrap。
 * 通常我們可以在這里做一些全局變量的初始化,比如不會(huì)在 unmount 階段被銷毀的應(yīng)用級(jí)別的緩存等。
 */
export async function bootstrap() {
  console.log('react app bootstraped');
}
/**
 * 應(yīng)用每次進(jìn)入都會(huì)調(diào)用 mount 方法,通常我們?cè)谶@里觸發(fā)應(yīng)用的渲染方法
 */
export async function mount(props) {
  ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}
/**
 * 應(yīng)用每次 切出/卸載 會(huì)調(diào)用的方法,通常在這里我們會(huì)卸載微應(yīng)用的應(yīng)用實(shí)例
 */
export async function unmount(props) {
  ReactDOM.unmountComponentAtNode(
    props.container ? props.container.querySelector('#root') : document.getElementById('root'),
  );
}

可以看到,由于其幫助我們完成了子應(yīng)用的加載工作,所以用戶的配置相比于single-spa更為簡(jiǎn)便了。但是,除了這個(gè)明面上的工作,qiankun還在暗處為我們的易用性做出了很多努力,接下來,我們會(huì)圍繞著以下三個(gè)方面來深入剖析qiankun內(nèi)部源碼和相關(guān)實(shí)現(xiàn)原理:

qiankun如何實(shí)現(xiàn)用戶只需配置一個(gè)URL就可以加載相應(yīng)子應(yīng)用資源的;

qiankun如何幫助用戶做到子應(yīng)用間獨(dú)立運(yùn)行的(包括JS互不影響和CSS互不污染);

qiankun如何幫助用戶實(shí)現(xiàn)更簡(jiǎn)便高效的應(yīng)用間通信的;

三、子應(yīng)用加載

qiankun的子應(yīng)用注冊(cè)方式非常簡(jiǎn)單,用戶只需要調(diào)用registerMicroApps函數(shù)并將所需參數(shù)傳入即可.前文中我們說到qiankun是基于single-spa二次封裝的框架,因此qiankun中的路由監(jiān)聽和子應(yīng)用生命周期管理實(shí)際上都是交給了single-spa來進(jìn)行實(shí)現(xiàn)的。我們一起來看一下該方法的實(shí)現(xiàn)方式(部分截取)

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>,
) {
  // 判斷應(yīng)用是否注冊(cè)過,保證每個(gè)應(yīng)用只注冊(cè)一次
  const unregisteredApps = apps.filter((app) => !microApps.some((registeredApp) => registeredApp.name === app.name));
  microApps = [...microApps, ...unregisteredApps];
  unregisteredApps.forEach((app) => {
    // 取出用戶輸入的參數(shù)
    const { name, activeRule, loader = noop, props, ...appConfig } = app;
    // 調(diào)用single-spa的子應(yīng)用注冊(cè)函數(shù),將用戶輸入的參數(shù)轉(zhuǎn)換為single-spa所需的參數(shù)
    registerApplication({
      name,
      // 這里提供了single-spa所需的子應(yīng)用加載方式函數(shù)
      app: async () => {
        loader(true);
        await frameworkStartedDefer.promise;
				// 調(diào)用轉(zhuǎn)換函數(shù)loadApp將用戶輸入的url等解析轉(zhuǎn)換運(yùn)行,最終生成增強(qiáng)后的子應(yīng)用生命周期函數(shù)(包括mount,unmount,bootstrap)
        const { mount, ...otherMicroAppConfigs } = (
          await loadApp({ name, props, ...appConfig }, frameworkConfiguration, lifeCycles)
        )();
				// 返回值為loadApp生成的一系列生命周期函數(shù),其中mount函數(shù)數(shù)組再次增強(qiáng)
        return {
          mount: [async () => loader(true), ...toArray(mount), async () => loader(false)],
          ...otherMicroAppConfigs,
        };
      },
      activeWhen: activeRule,
      customProps: props,
    });
  });
}

可以看到,qiankun在子應(yīng)用加載上所做的工作就是將用戶調(diào)用registerMicroApps時(shí)所提供的參數(shù)經(jīng)過一系列處轉(zhuǎn)換之后,改造成single-spa中registerApplication所需要的參數(shù)。下面,我們給出qiankun中實(shí)現(xiàn)該轉(zhuǎn)換子的主要函數(shù)loadApp的部分實(shí)現(xiàn)代碼(源代碼地址github.com/umijs/qiank&hellip;

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庫(kù)中的方法解析了用戶輸入的url(entry參數(shù)),得到了template(HTML模版),execScripts(所依賴JS文件的執(zhí)行函數(shù))以及assetPublicPath(公共資源路徑)
  const { template, execScripts, assetPublicPath } = await importEntry(entry, importEntryOpts);
  // 。。。。。。
  // 在window沙箱中(global參數(shù))執(zhí)行entry依賴的js文件,得到相關(guān)生命周期( bootstrap, mount, unmount, update)
  // 這里可以忽略getLifecyclesFromExports函數(shù),其返回與scriptExports一致,只是為了檢查子應(yīng)用是否導(dǎo)出了必須的生命周期
  const scriptExports: any = await execScripts(global, sandbox && !useLooseSandbox, {
    scopedGlobalVariables: speedySandbox ? trustedGlobals : [],
  });
  const { bootstrap, mount, unmount, update } = getLifecyclesFromExports(
    scriptExports,
    appName,
    global,
    sandboxContainer?.instance?.latestSetProp,
  );
  // 。。。。。
  // 導(dǎo)出single-spa所需配置的getter方法(因?yàn)榕渲庙?xiàng)與子應(yīng)用掛在的container相關(guān),默認(rèn)為用戶輸入的container,后續(xù)用戶可以手動(dòng)加載子應(yīng)用并指定其渲染位置)
  const initialContainer = 'container' in app ? app.container : undefined;
  const parcelConfigGetter: ParcelConfigObjectGetter = (remountContainer = initialContainer) => {
    const parcelConfig: ParcelConfigObject = {
      name: appInstanceId,
      bootstrap,
      // mount數(shù)組在子應(yīng)用渲染時(shí)依次執(zhí)行
      mount: [
        // 。。。。。。
        // 執(zhí)行沙箱隔離
        mountSandbox,
        // 調(diào)用用戶自定義mount生命周期,并傳入setGlobalState/onGlobalStateChange的應(yīng)用間通信方法函數(shù)
        async (props) => mount({ ...props, container: appWrapperGetter(), setGlobalState, onGlobalStateChange }),
        // 。。。。。。
      ],
      // unmount數(shù)組在子應(yīng)用卸載時(shí)依次執(zhí)行
      unmount: [
        // 。。。。。。。
        // 調(diào)用用戶自定義unmount生命周期
        async (props) => unmount({ ...props, container: appWrapperGetter() }),
        // 卸載隔離沙箱
        unmountSandbox,
        // 清理工作
        async () => {
          render({ element: null, loading: false, container: remountContainer }, 'unmounted');
          // 清理子應(yīng)用對(duì)全局通信的訂閱
          offGlobalStateChange(appInstanceId);
          // for gc
          appWrapperElement = null;
          syncAppWrapperElement2Sandbox(appWrapperElement);
        },
        // 。。。。。。。
      ],
    };
    return parcelConfig;
  }
	return parcelConfigGetter
}

可以看到,qiankun在其加載函數(shù)loadApp中做了一些額外的工作。

為了方便使用,qiankun提供了基于url入口來加載子應(yīng)用的方式。為了獲取用戶提供的html文件(或者資源文件數(shù)組)并解析出其中所需的資源,qiankun依賴了import-html-entry庫(kù)中的相關(guān)方法,執(zhí)行并得到了子應(yīng)用導(dǎo)出的用戶自定義生命周期。

對(duì)用戶自定義的生命周期進(jìn)行增強(qiáng)(包括掛載/卸載應(yīng)用間的隔離沙箱,初始化或傳入應(yīng)用間通信方法等等),返回框架增強(qiáng)后的生命周期函數(shù)數(shù)組并注冊(cè)在single-spa中。

經(jīng)過源碼的分析我們可以看出,qiankun在子應(yīng)用加載上就是作為中間層存在的,其主要作用就是簡(jiǎn)化用戶對(duì)于子應(yīng)用注冊(cè)的輸入,通過框架內(nèi)部的方法轉(zhuǎn)換并增強(qiáng)了用戶的輸入最終將其傳入了single-spa之中,在后續(xù)的執(zhí)行中真正負(fù)責(zé)子應(yīng)用加載卸載的是single-spa。

讀到這里,這篇“微前端框架qiankun源碼分析”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI