qiankun 框架分析

qiankun是基于 single-spa 做的二次封裝,主要解決了single-spa 的一些痛點(diǎn)和不足。

single-spa存在的問題?

  • 1、對(duì)微應(yīng)用侵入性太強(qiáng)
    微應(yīng)用的改造步驟:
    • 微應(yīng)用路由改造,添加一個(gè)特定的前綴
    • 微應(yīng)用入口改造,掛載點(diǎn)變更和生命周期函數(shù)導(dǎo)出
    • 打包工具配置更改

single-spa 采用JS Entry 的方式接入微應(yīng)用。也就是說single-spa 接入微應(yīng)用需要將微應(yīng)用整個(gè)打包成一個(gè)JS文件,發(fā)布到靜態(tài)資源服務(wù)器,然后再主應(yīng)用中配置該JS文件的地址告訴 single-spa 去這個(gè)地址加載微應(yīng)用。問題出現(xiàn)了,如按需加載、首屏資源加載優(yōu)化、css獨(dú)立打包等優(yōu)化沒有了。

  • 2、樣式隔離
    single-spa 沒有做。怎么做到主應(yīng)用和微應(yīng)用之間的樣式,微應(yīng)用和微應(yīng)用的樣式互不影響?這個(gè)只能通過約定命名規(guī)范來實(shí)現(xiàn),比如應(yīng)用樣式以自己的應(yīng)用名稱開頭。

  • 3、JS隔離
    single-spa 沒有做。JS全局對(duì)象污染,A應(yīng)用在window上加一個(gè)自己的屬性window.A,微應(yīng)用B 也能訪問到。

  • 4、資源預(yù)加載
    single-spa 沒有做。例如怎么實(shí)現(xiàn)在第一個(gè)微應(yīng)用加載完后,后臺(tái)悄悄加載其他微應(yīng)用。

  • 5、應(yīng)用間通信
    single-spa 沒有做。它只在注冊(cè)微應(yīng)用時(shí)給微應(yīng)用注入一些狀態(tài)信息,后續(xù)就不管了,沒有任何通信的手段。

qiankun 如何解決以上問題

  • 1、HTML Entry
    qiankun 通過HTML Entry 的方式來解決JS Entry帶來的問題

  • 2、樣式隔離
    采用shadow dom 包裹沒一個(gè)微應(yīng)用,從而確保微應(yīng)用的樣式互不干擾
    采用css scoped 方式(實(shí)驗(yàn)性)動(dòng)態(tài)改寫 css 選擇器來實(shí)現(xiàn)

  • 3、運(yùn)行時(shí)沙箱
    qiankun的運(yùn)行時(shí)沙箱分為 JS 沙箱和樣式沙箱

  • 4、資源預(yù)加載
    qiankun 實(shí)現(xiàn)預(yù)加載的思路有兩種,一種是當(dāng)主應(yīng)用執(zhí)行 start 方法啟動(dòng) qiankun 以后立即去預(yù)加載微應(yīng)用的靜態(tài)資源,另一種是在第一個(gè)微應(yīng)用掛載以后預(yù)加載其它微應(yīng)用的靜態(tài)資源,這個(gè)是利用 single-spa 提供的 single-spa:first-mount 事件來實(shí)現(xiàn)的

  • 5、應(yīng)用間通信
    qiankun 通過發(fā)布訂閱模式來實(shí)現(xiàn)應(yīng)用間通信

示例項(xiàng)目

官網(wǎng)地址
源碼地址

yarn examples:install
yarn examples:start

qiankun 提供了6種實(shí)例,vue、vue3、react15、react16、angular9、purehtml。

image.png

主應(yīng)用在 examples/main 目錄下,提供了兩種實(shí)現(xiàn)方式,基于路由配置的 registerMicroApps 和 手動(dòng)加載微應(yīng)用的loadMicroApp。通過 webpak.config.js 的 entry 可以知道有兩個(gè)入口文件 multiple.js 和 index.js。

  • 1、基于路由配置
    在 examples/main/index.js 中,將微應(yīng)用關(guān)聯(lián)到一些 url 規(guī)則,實(shí)現(xiàn)當(dāng)瀏覽器 url 發(fā)生變化時(shí),自動(dòng)加載相應(yīng)的微應(yīng)用。主應(yīng)用可以使用react進(jìn)行運(yùn)行,也可以使用vue進(jìn)行運(yùn)行。
registerMicroApps(
  [
    {
      name: 'vue',
      entry: '//localhost:7101',
      container: '#subapp-viewport',
      loader,
      activeRule: '/vue',
    },
  ],
  {
    beforeLoad: [
      app => {
        console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
      },
    ],
    beforeMount: [
      app => {
        console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
      },
    ],
    afterUnmount: [
      app => {
        console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
      },
    ],
  },
);
  • 2、手動(dòng)加載微應(yīng)用
    在 examples/main/multiple.js 中有l(wèi)oadMicroApp實(shí)現(xiàn)的例子
function mount() {
  app = loadMicroApp(
    { name: 'react15', entry: '//localhost:7102', container: '#react15' },
    { sandbox: { experimentalStyleIsolation: true } },
  );
}

vue微應(yīng)用引入,需要修改 vue.config.js 和 mian.js 、public-path.js

{
  ...
  // publicPath 沒在這里設(shè)置,是通過 webpack 提供的全局變量 __webpack_public_path__ 來即時(shí)設(shè)置的,webpackjs.com/guides/public-path/
  devServer: {
    ...
    // 設(shè)置跨域,因?yàn)橹鲬?yīng)用需要通過 fetch 去獲取微應(yīng)用引入的靜態(tài)資源的,所以必須要求這些靜態(tài)資源支持跨域
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  output: {
    // 把子應(yīng)用打包成 umd 庫格式
    library: `${name}-[name]`,  // 庫名稱,唯一
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${name}`,
  }
  ...
}
let router = null;
let instance = null;

function render(props = {}) {
  const { container } = props;
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/vue' : '/',
    mode: 'history',
    routes,
  });

  instance = new Vue({
    router,
    store,
    render: h => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
}

if (!window.__POWERED_BY_QIANKUN__) {
  render();
}
export async function bootstrap() {
  console.log('[vue] vue app bootstraped');
}

export async function mount(props) {
  console.log('[vue] props from main framework', props);
  render(props);
}

export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
  router = null;
}
if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

運(yùn)行時(shí)沙箱

運(yùn)行時(shí)沙箱包括 JS 沙箱 和 樣式沙箱

JS 沙箱

JS 沙箱是通過 proxy 代理 window 對(duì)象,記錄window對(duì)象上屬性的增刪改查

  • 單例模式
    直接代理了原生 window 對(duì)象,記錄原生 window 對(duì)象的增刪改查,當(dāng) window 對(duì)象激活時(shí)恢復(fù) window 對(duì)象到上次即將失活時(shí)的狀態(tài),失活時(shí)恢復(fù) window 對(duì)象到初始初始狀態(tài)
  • 多例模式
    代理了一個(gè)全新的對(duì)象,這個(gè)對(duì)象是復(fù)制的 window 對(duì)象的一部分不可配置屬性,所有的更改都是基于這個(gè) fakeWindow 對(duì)象,從而保證多個(gè)實(shí)例之間屬性互不影響

樣式沙箱

樣式沙箱實(shí)際做的事情其實(shí)很簡(jiǎn)單,就是將動(dòng)態(tài)添加的 script、link、style 這三個(gè)元素插入到對(duì)的位置,屬于主應(yīng)用的插入主應(yīng)用,屬于微應(yīng)用的插入到對(duì)應(yīng)的微應(yīng)用中,方便微應(yīng)用卸載的時(shí)候一起刪除,當(dāng)然樣式沙箱還額外做了兩件事:
(1)在卸載之前為動(dòng)態(tài)添加樣式做緩存,在微應(yīng)用重新掛載時(shí)再插入到微應(yīng)用內(nèi)
(2)將 proxy 對(duì)象傳遞給 execScripts 函數(shù),將其設(shè)置為微應(yīng)用的執(zhí)行上下文

  • 樣式隔離
    qiankun 的樣式隔離有兩種方式,一種是嚴(yán)格樣式隔離,通過 shadow dom 來實(shí)現(xiàn),另一種是實(shí)驗(yàn)性的樣式隔離,就是 scoped css,兩種方式不可共存。

    在 qiankun 中的嚴(yán)格樣式隔離,就是在這個(gè) createElement 方法中做的,通過 shadow dom 來實(shí)現(xiàn), shadow dom 是瀏覽器原生提供的一種能力,在過去的很長(zhǎng)一段時(shí)間里,瀏覽器用它來封裝一些元素的內(nèi)部結(jié)構(gòu)。以一個(gè)有著默認(rèn)播放控制按鈕的 <video> 元素為例,實(shí)際上,在它的 Shadow DOM 中,包含來一系列的按鈕和其他控制器

  • 實(shí)驗(yàn)性樣式隔離
    實(shí)驗(yàn)性樣式的隔離方式其實(shí)就是 scoped css,qiankun 會(huì)通過動(dòng)態(tài)改寫一個(gè)特殊的選擇器約束來限制 css 的生效范圍

HTML Entry

HTML Entry 是由 import-html-entry 庫實(shí)現(xiàn)的,通過 http 請(qǐng)求加載指定地址的首屏內(nèi)容即 html 頁面,然后解析這個(gè) html 模版得到 template, scripts , entry, styles。

{
  template: 經(jīng)過處理的腳本,link、script 標(biāo)簽都被注釋掉了,
  scripts: [腳本的http地址 或者 { async: true, src: xx } 或者 代碼塊],
  styles: [樣式的http地址],
  entry: 入口腳本的地址,要不是標(biāo)有 entry 的 script 的 src,要不就是最后一個(gè) script 標(biāo)簽的 src
}

然后遠(yuǎn)程加載 styles 中的樣式內(nèi)容,將 template 模版中注釋掉的 link 標(biāo)簽替換為相應(yīng)的 style 元素。然后向外暴露一個(gè) Promise 對(duì)象。

{
    // template 是 link 替換為 style 后的 template
    template: embedHTML,
    // 靜態(tài)資源地址
    assetPublicPath,
    // 獲取外部腳本,最終得到所有腳本的代碼內(nèi)容
    getExternalScripts: () => getExternalScripts(scripts, fetch),
    // 獲取外部樣式文件的內(nèi)容
    getExternalStyleSheets: () => getExternalStyleSheets(styles, fetch),
    // 腳本執(zhí)行器,讓 JS 代碼(scripts)在指定 上下文 中運(yùn)行
    execScripts: (proxy, strictGlobal) => {
        if (!scripts.length) {
            return Promise.resolve();
        }
        return execScripts(entry, scripts, proxy, { fetch, strictGlobal });
    }
}

HTML Entry 最終會(huì)返回一個(gè) Promise 對(duì)象,qiankun 就用了這個(gè)對(duì)象中的 template、assetPublicPath 和 execScripts 三項(xiàng),將 template 通過 DOM 操作添加到主應(yīng)用中,執(zhí)行 execScripts 方法得到微應(yīng)用導(dǎo)出的生命周期方法,并且還順便解決了 JS 全局污染的問題,因?yàn)閳?zhí)行 execScripts 方法的時(shí)候可以通過 proxy 參數(shù)指定 JS 的執(zhí)行上下文。

內(nèi)容來源

微前端框架 之 qiankun 從入門到源碼分析
qiankun 2.x 運(yùn)行時(shí)沙箱 源碼分析
HTML Entry 源碼分析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文將針對(duì)微前端框架 qiankun 的源碼進(jìn)行深入解析,在源碼講解之前,我們先來了解一下什么是 微前端。 微前端...
    昵稱不用太拉風(fēng)閱讀 9,880評(píng)論 4 30
  • 背景 隨著項(xiàng)目的演進(jìn),前端的業(yè)務(wù)架構(gòu)也會(huì)變得更加龐大、復(fù)雜,并常常會(huì)出現(xiàn)需要模塊復(fù)用的場(chǎng)景:1、組件復(fù)用,例如統(tǒng)一...
    Ygria閱讀 5,684評(píng)論 1 5
  • 微前端架構(gòu)之single-spa single-spa是什么 Single-spa 是一個(gè)將多個(gè)單頁面應(yīng)用聚合為一...
    yolkpie閱讀 921評(píng)論 0 0
  • 什么是微前端 微前端是一種多個(gè)團(tuán)隊(duì)通過獨(dú)立發(fā)布功能的方式來共同構(gòu)建現(xiàn)代化 web 應(yīng)用的技術(shù)手段及方法策略. 微前...
    lean_閱讀 2,771評(píng)論 0 7
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險(xiǎn)厭惡者,不喜歡去冒險(xiǎn),但是人生放棄了冒險(xiǎn),也就放棄了無數(shù)的可能。 ...
    yichen大刀閱讀 7,912評(píng)論 0 4

友情鏈接更多精彩內(nèi)容