微前端及Vue + qiankun 實現(xiàn)案例

微前端架構(gòu)

微前端特征

一、什么是微前端

Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. -- Micro Frontends

微前端是一種多個團隊通過獨立發(fā)布功能的方式來共同構(gòu)建現(xiàn)代化 web 應(yīng)用的技術(shù)手段及方法策略省咨。

微前端的概念出現(xiàn)于2016年末绣夺,其將微服務(wù)的概念引入前端世界逸爵。用以解決在需求执俩、人員蚂维、技術(shù)棧等因素不斷更迭下前端工程演變成巨石應(yīng)用(Frontend Monolith)**而不可維護的問題背捌。這類問題尤其常見于企業(yè)級Web項目中毙籽。

微前端架構(gòu)具備以下幾個核心價值:

  • 技術(shù)棧無關(guān)
    主框架不限制接入應(yīng)用的技術(shù)棧,微應(yīng)用具備完全自主權(quán)

  • 獨立開發(fā)毡庆、獨立部署
    微應(yīng)用倉庫獨立坑赡,前后端可獨立開發(fā)烙如,部署完成后主框架自動完成同步更新

  • 增量升級

    在面對各種復(fù)雜場景時,我們通常很難對一個已經(jīng)存在的系統(tǒng)做全量的技術(shù)棧升級或重構(gòu)毅否,而微前端是一種非常好的實施漸進式重構(gòu)的手段和策略

  • 獨立運行時
    每個微應(yīng)用之間狀態(tài)隔離亚铁,運行時狀態(tài)不共享

微前端架構(gòu)旨在解決單體應(yīng)用在一個相對長的時間跨度下,由于參與的人員螟加、團隊的增多徘溢、變遷,從一個普通應(yīng)用演變成一個巨石應(yīng)用(Frontend Monolith)后捆探,隨之而來的應(yīng)用不可維護的問題然爆。這類問題在企業(yè)級 Web 應(yīng)用中尤其常見。

更多關(guān)于微前端的相關(guān)介紹黍图,推薦大家可以去看這幾篇文章:

二曾雕、 qiankun

qiankun 是螞蟻金服開源的一套完整的微前端解決方案。具體描述可查看 文檔Github助被。
鏈接: https://qiankun.umijs.org/zh/guide 是qiankun的說明以及API教程 剖张。

2.1 qiankun 的核心設(shè)計理念

?? 簡單

由于主應(yīng)用微應(yīng)用都能做到技術(shù)棧無關(guān),qiankun 對于用戶而言只是一個類似 jQuery 的庫揩环,你需要調(diào)用幾個 qiankun 的 API 即可完成應(yīng)用的微前端改造搔弄。同時由于 qiankun 的 HTML entry 及沙箱的設(shè)計,使得微應(yīng)用的接入像使用 iframe 一樣簡單丰滑。

?? 解耦/技術(shù)棧無關(guān)

微前端的核心目標(biāo)是將巨石應(yīng)用拆解成若干可以自治的松耦合微應(yīng)用肯污,而 qiankun 的諸多設(shè)計均是秉持這一原則,如 HTML entry吨枉、沙箱蹦渣、應(yīng)用間通信等。這樣才能確保微應(yīng)用真正具備 獨立開發(fā)貌亭、獨立運行 的能力柬唯。

2.2 Why Not Iframe

為什么不用 iframe,這幾乎是所有微前端方案第一個會被 challenge 的問題圃庭。但是大部分微前端方案又不約而同放棄了 iframe 方案锄奢,自然是有原因的,并不是為了 "炫技" 或者刻意追求 "特立獨行"剧腻。

如果不考慮體驗問題拘央,iframe 幾乎是最完美的微前端解決方案了。

iframe 最大的特性就是提供了瀏覽器原生的硬隔離方案书在,不論是樣式隔離灰伟、js 隔離這類問題統(tǒng)統(tǒng)都能被完美解決。但他的最大問題也在于他的隔離性無法被突破儒旬,導(dǎo)致應(yīng)用間上下文無法被共享栏账,隨之帶來的開發(fā)體驗帖族、產(chǎn)品體驗的問題。

其實這個問題之前這篇也提到過挡爵,這里再單獨拿出來回顧一下好了竖般。

  1. url 不同步。瀏覽器刷新 iframe url 狀態(tài)丟失茶鹃、后退前進按鈕無法使用涣雕。
  2. UI 不同步,DOM 結(jié)構(gòu)不共享闭翩。想象一下屏幕右下角 1/4 的 iframe 里來一個帶遮罩層的彈框胞谭,同時我們要求這個彈框要瀏覽器居中顯示,還要瀏覽器 resize 時自動居中..
  3. 全局上下文完全隔離男杈,內(nèi)存變量不共享丈屹。iframe 內(nèi)外系統(tǒng)的通信、數(shù)據(jù)同步等需求伶棒,主應(yīng)用的 cookie 要透傳到根域名都不同的子應(yīng)用中實現(xiàn)免登效果旺垒。
  4. 慢。每次子應(yīng)用進入都是一次瀏覽器上下文重建肤无、資源重新加載的過程先蒋。

其中有的問題比較好解決(問題1),有的問題我們可以睜一只眼閉一只眼(問題4)宛渐,但有的問題我們則很難解決(問題3)甚至無法解決(問題2)竞漾,而這些無法解決的問題恰恰又會給產(chǎn)品帶來非常嚴(yán)重的體驗問題, 最終導(dǎo)致我們舍棄了 iframe 方案窥翩。

2.3 特性

  • ?? 基于 single-spa 封裝业岁,提供了更加開箱即用的 API。
  • ?? 技術(shù)棧無關(guān)寇蚊,任意技術(shù)棧的應(yīng)用均可 使用/接入笔时,不論是 React/Vue/Angular/JQuery 還是其他等框架。
  • ?? HTML Entry 接入方式仗岸,讓你接入微應(yīng)用像使用 iframe 一樣簡單允耿。
  • ?? 樣式隔離,確保微應(yīng)用之間樣式互相不干擾扒怖。
  • ?? JS 沙箱较锡,確保微應(yīng)用之間 全局變量/事件 不沖突。
  • ?? 資源預(yù)加載盗痒,在瀏覽器空閑時間預(yù)加載未打開的微應(yīng)用資源蚂蕴,加速微應(yīng)用打開速度。
  • ?? umi 插件,提供了 @umijs/plugin-qiankun 供 umi 應(yīng)用一鍵切換成微前端架構(gòu)系統(tǒng)掂墓。

2.4 qiankun快速上手

2.4.1 主應(yīng)用

2.4.1.1 安裝 qiankun

$ yarn add qiankun # 或者 npm i qiankun -S

2.4.1.2 在主應(yīng)用中注冊微應(yī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();

當(dāng)微應(yīng)用信息注冊完之后,一旦瀏覽器的 url 發(fā)生變化看成,便會自動觸發(fā) qiankun 的匹配邏輯君编,所有 activeRule 規(guī)則匹配上的微應(yīng)用就會被插入到指定的 container 中,同時依次調(diào)用微應(yīng)用暴露出的生命周期鉤子川慌。

如果微應(yīng)用不是直接跟路由關(guān)聯(lián)的時候吃嘿,你也可以選擇手動加載微應(yīng)用的方式:

import  { loadMicroApp }  from  'qiankun';

loadMicroApp(

  {  

 name:  'app',  

 entry:  '//localhost:7100',

 container:  '#yourContainer',  

  }

);

2.4.2 微應(yīng)用

微應(yīng)用不需要額外安裝任何其他依賴即可接入 qiankun 主應(yīng)用。

2.4.2.1 導(dǎo)出相應(yīng)的生命周期鉤子

微應(yīng)用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 導(dǎo)出 bootstrap梦重、mount兑燥、unmount 三個生命周期鉤子,以供主應(yīng)用在適當(dāng)?shù)臅r機調(diào)用琴拧。

/**

 * bootstrap 只會在微應(yīng)用初始化的時候調(diào)用一次降瞳,下次微應(yīng)用重新進入時會直接調(diào)用 mount 鉤子,不會再重復(fù)觸發(fā) bootstrap蚓胸。

 * 通常我們可以在這里做一些全局變量的初始化挣饥,比如不會在 unmount 階段被銷毀的應(yīng)用級別的緩存等。

 */

export  async  function  bootstrap()  {

 console.log('react app bootstraped');

}

/**

 * 應(yīng)用每次進入都會調(diào)用 mount 方法沛膳,通常我們在這里觸發(fā)應(yīng)用的渲染方法

 */

export  async  function  mount(props)  {

 ReactDOM.render(<App  />, props.container ? props.container.querySelector('#root')  : document.getElementById('root'));

}

/**

 * 應(yīng)用每次 切出/卸載 會調(diào)用的方法扔枫,通常在這里我們會卸載微應(yīng)用的應(yīng)用實例

 */

export  async  function  unmount(props)  {

 ReactDOM.unmountComponentAtNode(props.container ? props.container.querySelector('#root')  : document.getElementById('root'));

}

/**

 * 可選生命周期鉤子,僅使用 loadMicroApp 方式加載微應(yīng)用時生效

 */

export  async  function  update(props)  {

 console.log('update props', props);

}

qiankun 基于 single-spa锹安,所以你可以在這里找到更多關(guān)于微應(yīng)用生命周期相關(guān)的文檔說明短荐。

無 webpack 等構(gòu)建工具的應(yīng)用接入方式請見這里

2.4.2.2 配置微應(yīng)用的打包工具

除了代碼中暴露出相應(yīng)的生命周期鉤子之外叹哭,為了讓主應(yīng)用能正確識別微應(yīng)用暴露出來的一些信息忍宋,微應(yīng)用的打包工具需要增加如下配置:

webpack:
const packageName =  require('./package.json').name;

module.exports  =  {

 output:  {

 library:  `${packageName}-[name]`,

 libraryTarget:  'umd',

 jsonpFunction:  `webpackJsonp_${packageName}`,

  },

};

2.4.3 官方項目實踐

參考鏈接:https://qiankun.umijs.org/zh/guide/tutorial

三风罩、Vue+qiankun實現(xiàn)案例

首先分別創(chuàng)建我們的 項目基座 和 子項目 在這里我分別創(chuàng)建了 qiankun-base qiankun-vue qiankun-react 三個基礎(chǔ)的前端項目讶踪,直接用vue 和react的官方腳手架創(chuàng)建即可。
vue create qiankun-base 然后 install 或者add qiankun
vue create qiankun-vue
npx create-reacte-app qiankun-react

為了美觀 創(chuàng)建結(jié)束后 在基座應(yīng)用中 引用一下 element-ui 泊交,基座的app.vue這里就是簡單的配置了一個項目路由的顯示乳讥。

<template>
  <div>
    <el-menu :router="true"
             mode="horizontal">
      <!-- 基座內(nèi)不可以放自己的路由 -->
      <el-menu-item index="/">Home</el-menu-item>
 
      <!-- 引用vue子路由 -->
      <el-menu-item index="/vue">vue應(yīng)用</el-menu-item>
 
      <!-- 引用react子路由 -->
      <el-menu-item index="/react">react應(yīng)用</el-menu-item>
    </el-menu>
    <router-view></router-view>
    <div id="vue"></div>
    <div id="react"></div>
  </div>
</template>
 
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
 
#nav {
  padding: 30px;
}
 
#nav a {
  font-weight: bold;
  color: #2c3e50;
}
 
#nav a.router-link-exact-active {
  color: #42b983;
}
</style>

然后再main.js中正式注冊子項目 詳細(xì)代碼意義 的內(nèi)容代碼里做了注釋。

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import Elementui from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import { registerMicroApps, start } from 'qiankun'
 
// Vue.config.productionTip = false
 
const apps = [
  {
    name: 'vueApp', // 應(yīng)用名
    entry: 'http://localhost:8081/', // 默認(rèn)會加載這個html 解析里面的js 動態(tài)的執(zhí)行 (子應(yīng)用必須支持跨域)
    // fetch
    container: '#vue', // 容器
    activeRule: '/vue' // 激活路由
  },
  {
    name: 'reactApp',
    entry: 'http://localhost:8082/', // 默認(rèn)會加載這個html 解析里面的js 動態(tài)的執(zhí)行 (子應(yīng)用必須支持跨域)
    // fetch
    container: '#react',
    activeRule: '/react'
  }
]
registerMicroApps(apps, {
  // beforeMount()
  // beforeUnmount()
}) //注冊app +生命周期
start({
  prefetch: false // 取消預(yù)加載
}) // 啟動
Vue.use(Elementui)
new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

這里的registerMicroApps 和start 都是qiankun內(nèi)部的注冊方法 我就沒有過多的注釋詳細(xì)的內(nèi)容講解可以 查看官方的API文檔,其中也可以加一些app生命周期的操作,到這里其實基座的準(zhǔn)備已經(jīng)結(jié)束了 其實基座的作用就是承接一個子應(yīng)用的一個掛載 至于內(nèi)部的樣式隔離 js 隔離qiankun在掛載的時候已經(jīng)做了處理 在這里就不需要我們另外處理了廓俭。

接下來我們修改我們的子應(yīng)用云石。

main.js。

import Vue from 'vue'
import App from './App.vue'
import router from './router'
 
// Vue.config.productionTip = false
 
let instance = null
function render (props) {
  instance = new Vue({
    router,
    //store:[],
    render: h => h(App)
    // props:{}
  }).$mount('#app') // 掛在到自己的HTML中 基座中會拿到這個掛載好的最終html 將其插入
}
 
if (window.__POWERED_BY_QIANKUN__) { // 判斷是否為 qiankun掛載 不是的話自行啟動掛載
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
} else {
  render();
}
// export const mount = async () => render();
 
// 子組件的渲染
export async function bootstrap (props) { };
 
export async function mount (props) {
  render(props)
};
 
export async function unmount (props) {
  instance.$destroy()
};

這里面有幾個變量是qiankun的內(nèi)置API變量和幾個掛載方法研乒,這里的render的判斷條件 是為了區(qū)分獨立運行和注入兩種狀態(tài)汹忠。

然后配置我們的vue.config.js。

module.exports = {
  devServer: {
    port: 8081,
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  },
  configureWebpack: {
    output: {
      library: 'vueApp',
      libraryTarget: 'umd'
    }
  }
}

設(shè)置允許所有人訪問 和導(dǎo)出模式,這樣我們的vue子應(yīng)用就配置完成了宽菜。

react 其實也是大同小異 但是為了改變react 默認(rèn)的配置 我們需要安裝一個插件 react-app-rewired谣膳。

然后改變package.json配置。

"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
  },

創(chuàng)建 config-overrides.js铅乡。

module.exports = {
  webpack: (config) => {
    config.output.library = 'reactApp'
    config.output.libraryTarget = 'umd'
    config.output.publicPath = '//localhost:8082'
    return config
  },
  devServer: (configFunction) => {
    return function (proxy, allowedHost) {
      const config = configFunction(proxy, allowedHost)
      config.headers = {
        "Access-Control-Allow-Origin": '*'
      }
      return config
    }
  }
}

內(nèi)容跟vue 基本一致继谚。

修改index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// import * as serviceWorker from './serviceWorker';

function render () {
 ReactDOM.render(
   <React.StrictMode>
     <App />
   </React.StrictMode>,
   document.getElementById('root')
 );
}
if (!window.__POWERED_BY_QIANKUN__) { // 判斷是否為 qiankun掛載 不是的話自行啟動掛載
 render();
}
// export const mount = async () => render();

// 子組件的渲染
export async function bootstrap (props) { };

export async function mount (props) {
 render(props)
};

export async function unmount (props) {
 ReactDOM.unmountComponentAtNode(document.getElementById('root'))
};

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
// serviceWorker.unregister();

新建一個.env文件配置一下自己的啟動端口。

PORT=8082
WDS_SOCKET_PORT=8082

最后app.js:

import React from 'react';
import logo from './logo.svg';
import './App.css';
import { BrowserRouter, Route, Link } from 'react-router-dom'
 
function App () {
  return (
    <BrowserRouter basename="/react">
      <Link to="/">首頁</Link>
      <Link to="/about">關(guān)于</Link>
      <Route path="/" exact render={() => (
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <p>
              Edit <code>src/App.js</code> and save to reload.
          </p>
            <a
              className="App-link"
              
              target="_blank"
              rel="noopener noreferrer"
            >
              Learn React
          </a>
          </header>
        </div>
      )}></Route>
      <Route path="/about" exact render={() => (
        <div>about頁面</div>
      )}></Route>
    </BrowserRouter>
  );
}
 
export default App;

這樣我們的開發(fā)步驟基本就結(jié)束了阵幸。
接下來看成果:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末花履,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子挚赊,更是在濱河造成了極大的恐慌诡壁,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荠割,死亡現(xiàn)場離奇詭異妹卿,居然都是意外死亡,警方通過查閱死者的電腦和手機蔑鹦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門纽帖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人举反,你說我怎么就攤上這事懊直。” “怎么了火鼻?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵室囊,是天一觀的道長。 經(jīng)常有香客問我魁索,道長融撞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任粗蔚,我火速辦了婚禮尝偎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鹏控。我一直安慰自己致扯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布当辐。 她就那樣靜靜地躺著抖僵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缘揪。 梳的紋絲不亂的頭發(fā)上耍群,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天义桂,我揣著相機與錄音,去河邊找鬼蹈垢。 笑死慷吊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的曹抬。 我是一名探鬼主播溉瓶,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼沐祷!你這毒婦竟也來了嚷闭?” 一聲冷哼從身側(cè)響起攒岛,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤赖临,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后灾锯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兢榨,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年顺饮,在試婚紗的時候發(fā)現(xiàn)自己被綠了吵聪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡兼雄,死狀恐怖吟逝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赦肋,我是刑警寧澤块攒,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站佃乘,受9級特大地震影響囱井,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜趣避,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一庞呕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧程帕,春花似錦住练、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至敛苇,卻和暖如春妆绞,著一層夾襖步出監(jiān)牢的瞬間顺呕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工括饶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留株茶,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓图焰,卻偏偏與公主長得像启盛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子技羔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 本文介紹如何使用qiankun + Vue搭建一個前端微服務(wù) 一僵闯、什么是微前端 Techniques, strat...
    超人s閱讀 5,189評論 3 8
  • 什么是微前端 微前端是一種多個團隊通過獨立發(fā)布功能的方式來共同構(gòu)建現(xiàn)代化 web 應(yīng)用的技術(shù)手段及方法策略. 微前...
    lean_閱讀 2,583評論 0 7
  • 微前端介紹: 微前端解決特定問題,他并不是一個通用的解決方案,與技術(shù)棧無關(guān),例如有一個龐大的應(yīng)用集合蹭越,匯集了rea...
    巧克力_404閱讀 459評論 0 2
  • 2020.12.30微前端解決特定問題:技術(shù)棧無關(guān)https://micro-frontends.org/[htt...
    monkeyfly36閱讀 425評論 0 0
  • 今天感恩節(jié)哎争涌,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,571評論 0 11