微前端qiankun原理分析

主要研究對象qiankun,探究內部原理css

乾坤qiankun簡介

1. 安裝

$ yarn add qiankun # 或者 npm i qiankun -S
複製代碼

2. 主應用註冊

registerMicroApps([
    {
        name: 'reactApp',
        entry: '//localhost:3000',
        container: '#container',
        activeRule: '/views/app-react',
    },
    {
        name: 'vueApp',
        entry: '//localhost:8080',
        container: '#container',
        activeRule: '/views/app-vue',
    },
    {
        name: 'reactApp2',
        entry: 'http://localhost:9100/react-app2/template/index.html',
        container: '#container',
        activeRule: '/views/app-react2',
    }
]);
// 啓動 qiankun
start();
複製代碼

當微應用信息註冊完以後,一旦瀏覽器的 url 發生變化,便會自動觸發 qiankun 的匹配邏輯,全部 activeRule 規則匹配上的微應用就會被插入到指定的 container 中,同時依次調用微應用暴露出的生命週期鉤子。若是子應用還存在子路由,則須要配置子應用路由的basenamehtml

3. 子應用的改造

導出生命週期鉤子

微應用須要在本身的入口 js (一般就是 webpack 的 entry js) 導出 bootstrap、mount、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'));
}

/** * 可選生命週期鉤子,僅使用 loadMicroApp 方式加載微應用時生效 */
export async function update(props) {
  console.log('update props', props);
}
複製代碼

修改Webpack配置

  1. 新增public-path.js文件,並在入口文件最頂部引用,用於修改運行時的 publicPath
  2. 容許開發環境跨域和 umd 打包,修改 webpack 打包:
const packageName = require('./package.json').name;

module.exports = {
  output: {
    library: `${packageName}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${packageName}`,
  },
};
複製代碼

關於public-path的描述能夠參見此文檔:webpack.docschina.org/guides/publ…vue

關於library的描述能夠參見此文檔:webpack.docschina.org/guides/auth…react

single-spa的使用

single-spa主要功能是實現了路由劫持與應用加載,但未實現js、css隔離webpack

乾坤qiankun的原理

在研究qiankun的原理以前,咱們應該試着提出一些問題,而後在閱讀源碼的過程當中去尋找答案。對於qiankun,咱們比較關心的問題有:git

  • qiankunsingle-spa是什麼關係?
  • qiankun如何實現js沙箱?
  • qiankun如何實現css隔離?

registerMicroApps

配置微應用的第一步就是在主應用中註冊子應用,配置好子應用的名稱、入口、dom容器以及喚醒條件,以後qiankun會按照喚醒條件將子應用掛載到dom容器上。github

  1. 用法:registerMicroApps(apps, lifeCycles?)
    • 參數:
      • apps - Array<RegistrableApp> - 必選,微應用的一些註冊信息
      • lifeCycles - LifeCycles - 可選,全局的微應用生命週期鉤子
    • 詳解
      • RegistrableApp
        • name - string - 必選,微應用的名稱,微應用之間必須確保惟一
        • entry - string | { scripts?: string[]; styles?: string[]; html?: string } - 必選,微應用的入口
        • container - string | HTMLElement - 必選,微應用的容器節點的選擇器或者 Element 實例。如container: '#root'container: document.querySelector('#root')
        • activeRule - string | (location: Location) => boolean | Array<string | (location: Location) => boolean> - 必選,微應用的激活規則
        • loader - (loading: boolean) => void - 可選,loading 狀態發生變化時會調用的方法
        • props - object - 可選,主應用須要傳遞給微應用的數據
      • LifeCycles
        • beforeLoad - Lifecycle | Array<Lifecycle> - 可選
        • beforeMount - Lifecycle | Array<Lifecycle> - 可選
        • afterMount - Lifecycle | Array<Lifecycle> - 可選
        • beforeUnmount - Lifecycle | Array<Lifecycle> - 可選
        • afterUnmount - Lifecycle | Array<Lifecycle> - 可選
  2. 示例
import { registerMicroApps } from 'qiankun';

registerMicroApps([{
    name: 'app1',
    entry: '//localhost:8080',
    container: '#container',
    activeRule: '/react',
    props: {
        name: 'kuitos'
    }
}], {
    beforeLoad: app => console.log('before load', app.name),
    beforeMount: [
        app => console.log('before mount', app.name)
    ]
});
複製代碼
  1. 實現原理:背後調用single-sparegisterApplication方法,此處主要工做在registerApplicationapp參數中處理:
registerApplication({
    name,
    app: async () => {
        loader(true);
        await frameworkStartedDefer.promise;
        const { mount, ...otherMicroAppConfigs } = (
            await loadApp({ name, props, ...appConfig }, frameworkConfiguration, lifeCycles)
        )();
        return {
            mount: [async () => loader(true), ...toArray(mount), async () => loader(false)],
            ...otherMicroAppConfigs,
        };
    },
    activeWhen: activeRule,
    customProps: props,
});
複製代碼

其中loadApp是核心,從新包裝了single-spa所需applicatmountunmount方法,在這兩個生命週期中分別加入了一些額外的操做。如在mount的時候,會執行beforeMountafterMount回調,以及mountSandbox開啓沙箱。在unmount的時候,會執行beforeUnmountafterUnmount回調,以及unmountSandbox釋放沙箱。web

  • qiankun是如何實現沙箱的?npm

    經過調用createSandboxContainer生成一個沙箱,若是當前瀏覽器環境支持Proxy則採用Proxy,反之則採用快照方式。對於Proxy模式來講,首先調用createFakeWindow(rawWindow)生成一個原始window的複製版fakeWindow

loadApp函數原理

執行用戶調用registerApplication時傳入的app方法,加載應用

  1. 調用import-html-entry模塊的importEntry函數,獲取到對應子應用的html文件、可執行腳本文件以及publicpath
  2. 調用getDefaultTplWrapper將子應用的html內容用div標籤包裹起來
  3. 調用createElement函數生成剔除html、body、head標籤後的子應用html內容(經過innerHTML達到過濾效果)
  4. 調用getRender函數獲得render函數
  5. 調用第4步獲得的render,將container內部清空,並將子應用的dom元素渲染到指定的contanter元素上
  6. 調用getAppWrapperGetter函數,生成一個能夠獲取處理過的子應用dom元素的函數initialAppWrapperGetter,以備後續使用子應用dom元素
  7. 若是sandbox爲true,則調用createSandboxContainer函數
  8. 執行execScripts函數,執行子應用腳本
  9. 執行getMicroAppStateActions函數,獲取onGlobalStateChangesetGlobalStateoffGlobalStateChange,用於主子應用傳遞信息
  10. 執行parcelConfigGetter函數,包裝mountunmount

createSandboxContainer函數原理

registerApplication函數原理

  1. 規則化參數,registerApplication參數有兩種形式,首先將不一樣的參數形式歸結爲同一種格式{name,loadApp,activeWhen,customProps}
  2. 檢測註冊的appName是否重名,重名則拋出異常
  3. application配置推入到全局數組apps中保存
  4. 若是是瀏覽器環境(以是否存在window變量做爲依據),則:
    1. 確保在引入了jQuery的前提早下window.jQuery可以使用
    2. 執行reroute函數,並進行下列操做:
      1. 遍歷apps數組,獲取到NOT_MOUNTEDMOUNTEDLOADING_SOURCE_CODEapplication
      2. 檢測是否執行了start方法,若是已經執行了(例如在頁面上切換路由),則執行performAppChanges函數,反之執行loadApps函數(首次刷新頁面時)

performAppChanges函數原理

利用Promise.resolve().then()將函數內的所有代碼異步執行

  1. 利用window.dispatchEventCustomEvent觸發single-spa:before-no-app-changesingle-spa:before-app-change以及single-spa:before-routing-event事件(這兩個接口IE不支持)
  2. 執行隊列中appmountunmount操做(具體操做有待進一步研究

start函數原理

啓動 qiankun,能夠傳入一些資源獲取方式以及是否採用沙箱等配置

  1. 用法參見:qiankun.umijs.org/zh/api#star…
  2. 實現原理:背後調用single-spastart方法,執行reroutesetUrlRerouteOnly函數

附錄

參考資料

相關文章
相關標籤/搜索