<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          微前端框架 qiankun 項(xiàng)目實(shí)戰(zhàn)(一)--本地開發(fā)篇

          共 16260字,需瀏覽 33分鐘

           ·

          2021-06-13 04:58

          點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)

          回復(fù)1,加入高級(jí) Node 進(jìn)階交流群



          作者:黑化程序員

          https://juejin.cn/post/6970310177517993998

          ?

          大家好,我是小黑。

          公司使用技術(shù)棧是vue,最近遇到了一個(gè)需求,要把原有后臺(tái)管理系統(tǒng)的功能模塊搬遷到新的后臺(tái)管理系統(tǒng)上面去。原本這沒(méi)有多復(fù)雜的事,直接復(fù)制粘貼改改就可以,但是有這么幾個(gè)坑點(diǎn),我瞬間陷入了沉思:

          1. 新的后臺(tái)使用的是vue3,原有的后臺(tái)使用的是vue2
          2. 新的后臺(tái)有自己的一套登錄角色權(quán)限管理方案,舊的后臺(tái)也有
          3. 由于vue3和vue2區(qū)別還是比較大的,vue3相當(dāng)于整個(gè)vue重寫了,雖說(shuō)做了向下兼容,但是直接復(fù)制粘貼過(guò)去不太現(xiàn)實(shí)(主要是我試過(guò)復(fù)制了一個(gè)模塊過(guò)去devtools的紅色慘不忍睹)

          怎么辦,把vue2寫好的模塊重新用vue3寫一次(令人窒息)?正準(zhǔn)備含淚敲鍵盤的時(shí)候,我想到了以前看過(guò)的微前端的相關(guān)文章,不如試試這個(gè)玩意吧,然后,微前端正式踩坑。

          什么是微前端?

          按照網(wǎng)上的說(shuō)法和小黑的理解,微前端就是應(yīng)用分割獨(dú)立運(yùn)行獨(dú)立部署,將原本把所有功能集中于一個(gè)項(xiàng)目中的方式轉(zhuǎn)變?yōu)榘压δ馨礃I(yè)務(wù)劃分成一個(gè)主項(xiàng)目和多個(gè)子項(xiàng)目,每個(gè)子項(xiàng)目負(fù)責(zé)自身功能,同時(shí)具備和其它子項(xiàng)目和主項(xiàng)目進(jìn)行通信的能力,達(dá)到更細(xì)化更易于管理的目的。總的來(lái)說(shuō)微前端就是

          ?

          一個(gè)完整應(yīng)用劃分成一個(gè)主應(yīng)用和一個(gè)或多個(gè)微應(yīng)用,應(yīng)用間相互獨(dú)立,可相互通信。

          ?

          如何實(shí)現(xiàn)微前端?

          符合上面條件,最容易想到的就是iframe,下面貼上兩段最簡(jiǎn)單的iframe及其通訊代碼

          // parent.html
          <div>我是parent</div>
          <button id="parentBtn">parent btn</
          button>
          <iframe src="./child.html" id="frame"></iframe>

          <script>  
          function parentFunc(msg{    
            console.log("parent 的方法:", msg)  
          }  

          var btn = document.querySelector("#parentBtn")  
          btn.addEventListener('click'function({    
            console.log("我是parent的button")    
            console.log("我調(diào)用了:")    
            document.getElementById('frame').contentWindow.childFunc('parent');  
          })
          </script>


          // child.html
          <div>我是child</div>
          <button id="childBtn">child btn</button>
          <script>  
          function childFunc(msg{    
            console.log("child 的方法:", msg)  
          }  

          var btn = document.querySelector("#childBtn")  
          btn.addEventListener('click'function({    
            console.log("我是child的button")    
            console.log("我調(diào)用了:")    
            parent.window.parentFunc('child');   
          })
          </script>

          以上兩段代碼放到本地服務(wù)器中就是這樣的然后點(diǎn)擊兩個(gè)按鈕,就可以互相通信傳參了

          ?

          以上的兩個(gè)html必須放到有域名的環(huán)境中運(yùn)行,否則會(huì)報(bào)錯(cuò)。

          ?

          當(dāng)然了,這次的項(xiàng)目遷移我不是直接用iframe改造的,而是站在巨人的肩膀上,我用了一個(gè)叫qiankun的微前端框架改造,因?yàn)楣镜拇a我不能貼上來(lái),下面我會(huì)建一個(gè)vue3項(xiàng)目和一個(gè)vue2項(xiàng)目來(lái)大概還原一下我是如何改造公司項(xiàng)目的,還有我遇到的坑是怎么填的。

          微前端框架qiankun

          首先,用vue官方的腳手架建立一個(gè)vue3的基本后臺(tái)界面和一個(gè)vue2的基本后臺(tái)界面,注意這里因?yàn)関ue3打包使用了vite的原因,所以qiankun框架不能使用vue3作為微應(yīng)用,這里我們主應(yīng)用是vue3,微應(yīng)用是vue2,這跟我改造的也是一致的,兩個(gè)項(xiàng)目大概結(jié)構(gòu)是一樣的,如下:

          為了方便大家,貼上我建好的模板倉(cāng)庫(kù)地址

          vue3模板:https://gitee.com/jimpp/vue3-main-app(主應(yīng)用,主應(yīng)用必須安裝qiankun)

          vue2模板:https://gitee.com/jimpp/vue2-micro-app(微應(yīng)用)

          上面master分支都是未改造前能獨(dú)立運(yùn)行的項(xiàng)目,dev分支是最終改造后的項(xiàng)目,當(dāng)然自己從頭到尾建立也是可以的,但是要保證兩個(gè)倉(cāng)庫(kù)都具備router,store,登錄攔截的功能

          兩個(gè)模板都具備這樣的界面

          1.登錄界面

          咳咳,簡(jiǎn)陋了點(diǎn),為了顯示請(qǐng)不要打我哈哈。

          2.左側(cè)菜單和router-view界面

          好了,下面開始基于qiankun框架改造兩個(gè)項(xiàng)目

          主應(yīng)用啟動(dòng)qiankun

          這里我使用了qiankun官網(wǎng)的registerMicroApps注冊(cè)微應(yīng)用

          在主應(yīng)用的src文件夾下新建一個(gè)micros文件夾,在micros文件夾新建index.js,app.js

          // index.js
          import NProgress from "nprogress";
          import "nprogress/nprogress.css";
          import {  
            registerMicroApps,  
            addGlobalUncaughtErrorHandler,  
            start,
          from "qiankun";// 微應(yīng)用注冊(cè)信息
          import apps from "./app";

          registerMicroApps(apps, {  
            beforeLoad(app) => {    
              // 加載微應(yīng)用前,加載進(jìn)度條    
              NProgress.start();    
              console.log("before load", app.name);    
              return Promise.resolve();  
            },  
            afterMount(app) => {    
              // 加載微應(yīng)用前,進(jìn)度條加載完成    
              NProgress.done();    
              console.log("after mount", app.name);    
              return Promise.resolve();  
            },
          });

          addGlobalUncaughtErrorHandler((event) => {  
            console.error(event);  
            const { message: msg } = event  
            if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {    
            console.error("微應(yīng)用加載失敗,請(qǐng)檢查應(yīng)用是否可運(yùn)行");  
            }
          });
          export default start;

          首先yarn add nprogress安裝nprogress這個(gè)庫(kù),是為了到時(shí)候在加載微應(yīng)用的時(shí)候有進(jìn)度條顯示,這里用到了官方的幾個(gè)api

          1. registerMicroApps:包含兩個(gè)參數(shù),第一個(gè)參數(shù)是微應(yīng)用的一些注冊(cè)信息,第二個(gè)參數(shù)是全局的微應(yīng)用生命周期鉤子。
          2. addGlobalUncaughtErrorHandler:全局的未捕獲異常處理器,微應(yīng)用發(fā)生報(bào)錯(cuò)的時(shí)候亦可以用這個(gè)api捕捉。
          3. start:我們用來(lái)啟動(dòng)qiankun的方法,包含一個(gè)參數(shù),具體的參數(shù)用途不再詳述。

          以上詳細(xì)的api請(qǐng)點(diǎn)擊這里:

          // app.js

          const apps = [  
            {    
              name"vue-micro-app",    
              entry"http://localhost:8081",    
              container"#micro-container",    
              activeRule"#/vue2-micro-app",  
            },
          ];
          export default apps;

          app.js導(dǎo)出的是上面registerMicroApps的第一個(gè)參數(shù),是一個(gè)對(duì)象數(shù)組,其中數(shù)組每個(gè)字段的作用如下:

          1. name:微應(yīng)用的名稱,后面改造微應(yīng)用的時(shí)候一定要與這個(gè)name對(duì)應(yīng)
          2. entry:微應(yīng)用運(yùn)行的域名加端口,我用的是本地8081端口
          3. container:?jiǎn)?dòng)微應(yīng)用需要一個(gè)dom容器,里面就是這個(gè)dom容器的id,用class應(yīng)該也是可以的
          4. activeRule:觸發(fā)啟動(dòng)微應(yīng)用的規(guī)則,當(dāng)檢測(cè)到url中含有activeRule的值時(shí),將啟動(dòng)微應(yīng)用

          添加完上述兩個(gè)js后,我們回到main.js,目前的main.js應(yīng)該是這樣的

          import { createApp } from 'vue'
          import App from './App.vue'
          import router from './router'
          import store from './store'
          import '@/assets/main.css'

          createApp(App).use(store).use(router).mount('#app')

          改造也非常簡(jiǎn)單,把上面micros中的index.js引入,然后運(yùn)行一下start函數(shù)就大功告成了

          import { createApp } from 'vue'
          import App from './App.vue'
          import router from './router'
          import store from './store'
          import '@/assets/main.css'
          import start from '@/micros'


          createApp(App).use(store).use(router).mount('#app')

          start()

          刷新一下瀏覽器,發(fā)現(xiàn)主應(yīng)用和改造前并無(wú)差異!

          主應(yīng)用添加微應(yīng)用容器和微應(yīng)用菜單

          目前主應(yīng)用app的菜單代碼結(jié)構(gòu)如下

          <div class="nav" v-if="token">    
            <div class="menu">      
              <router-link to="/">Parent Home</router-link>    
            </div>
              
            <div class="menu">      
              <router-link to="/about">Parent About</router-link>    
            </div>

          </div>

          現(xiàn)在我們添加兩個(gè)菜單,分別對(duì)應(yīng)子應(yīng)用的homeabout

          <div class="nav" v-if="token">    
            <div class="menu">      
              <router-link to="/">Parent Home</router-link>    
            </div>
              
            <div class="menu">      
              <router-link to="/about">Parent About</router-link>    
            </div>

              <!--- 新添加 --->
            <div class="menu">      
              <router-link to="/vue2-micro-app">Child Home</router-link>    
             </div>
              
            <div class="menu">      
              <router-link to="/vue2-micro-app/about">Child About</router-link>    
             </div>

          </div>

          <div class="container">   
            <div class="header" v-if="token">Child Header</
          div>   
            <div class="router-view">      
              <router-view />      
              <!-- 新添加,微應(yīng)用的容器 -->      
              <div id="micro-container"></div>   
            </div>

          </div>

          相信你也發(fā)現(xiàn)了,to中多了上面app.jsactiveRule字段中對(duì)應(yīng)的值(去掉了#號(hào)),因?yàn)?/vue2-micro-app正是觸發(fā)啟動(dòng)微應(yīng)用的條件

          這是刷新我們的微應(yīng)用,然后點(diǎn)擊一下Child Home菜單,你會(huì)發(fā)現(xiàn)有兩個(gè)報(bào)錯(cuò)

          第一個(gè)是跨域報(bào)錯(cuò),因?yàn)槲覀冎鲬?yīng)用運(yùn)行在8080端口,微應(yīng)用是8081端口,后面用nginx做一下代理就好

          第二個(gè)報(bào)錯(cuò)就是源自于我們的微應(yīng)用還未改造,所以還等什么,趕緊改造微應(yīng)用

          微應(yīng)用改造

          官網(wǎng)寫了,微應(yīng)用入口必須導(dǎo)出 bootstrapmountunmount 三個(gè)生命周期鉤子,以供主應(yīng)用在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用

          這是微應(yīng)用改造前的main.js

          import Vue from 'vue'
          import App from './App.vue'
          import router from './router'
          import store from './store'
          import '@/assets/main.css'
          Vue.config.productionTip = false
          new Vue({  
            router,  
            store,  
            renderh => h(App)
          }).$mount('#app')

          下面我們來(lái)改造一下main.js

          import Vue from 'vue'
          import App from './App.vue'
          import router from './router'
          import store from './store'
          import '@/assets/main.css'
          Vue.config.productionTip = false

          // 新增:用于保存vue實(shí)例
          let instance = null

          // 新增:動(dòng)態(tài)設(shè)置 webpack publicPath,防止資源加載出錯(cuò)
          if (window.__POWERED_BY_QIANKUN__) {  
            // eslint-disable-next-line no-undef  
            __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
          }

          /** * 新增: * 渲染函數(shù) * 兩種情況:主應(yīng)用生命周期鉤子中運(yùn)行 / 微應(yīng)用單獨(dú)啟動(dòng)時(shí)運(yùn)行 */
          function render({  
          // 掛載應(yīng)用  
            instance = new Vue({    
            router,    
            store,    
            render(h) => h(App),  
          }).$mount("#micro-app");}


          /** 
          * 新增: 
          * 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("VueMicroApp bootstraped");
          }

          /** 
          * 新增: 
          * 應(yīng)用每次進(jìn)入都會(huì)調(diào)用 mount 方法,通常我們?cè)谶@里觸發(fā)應(yīng)用的渲染方法 
          */

          export async function mount(props{  
            console.log("VueMicroApp mount", props);  
            render(props);
          }
          /** 
          * 新增: 
          * 應(yīng)用每次 切出/卸載 會(huì)調(diào)用的方法,通常在這里我們會(huì)卸載微應(yīng)用的應(yīng)用實(shí)例 
          */

          export async function unmount({  
            console.log("VueMicroApp unmount");  
            instance.$destroy();  
            instance = null;
          }

          // 新增:獨(dú)立運(yùn)行時(shí),直接掛載應(yīng)用
          if (!window.__POWERED_BY_QIANKUN__) {  
            render();
          }

          // 這是原本啟動(dòng)的代碼
          // new Vue({
          //   router,
          //   store,
          //   render: h => h(App)
          // }).$mount('#app')
          ?

          請(qǐng)注意,render方法中我把$mount后的參數(shù)改為了#micro-app,這是為了區(qū)分主應(yīng)用和微應(yīng)用中index.html的根id,所以微應(yīng)用中的public文件夾的index.html也要改為micro-app

          ?

          然后還要對(duì)webpack配置進(jìn)行改造,微應(yīng)用根目錄添加vue.config.js文件

          const path = require("path");

          module.exports = {
          devServer: {
          // 監(jiān)聽端口
          port: 8081,
          // 關(guān)閉主機(jī)檢查,使微應(yīng)用可以被 fetch
          disableHostCheck: true,
          // 配置跨域請(qǐng)求頭,解決開發(fā)環(huán)境的跨域問(wèn)題
          headers: {
          "Access-Control-Allow-Origin": "*",
          },
          },
          configureWebpack: {
          resolve: {
          alias: {
          "@": path.resolve(__dirname, "src"),
          },
          },
          output: {
          // 微應(yīng)用的包名,這里與主應(yīng)用中注冊(cè)的微應(yīng)用名稱一致
          library: "vue-micro-app",
          // 將你的 library 暴露為所有的模塊定義下都可運(yùn)行的方式
          libraryTarget: "umd",
          // 按需加載相關(guān),設(shè)置為 webpackJsonp_VueMicroApp 即可
          jsonpFunction: `webpackJsonp_vue-micro-app`,
          },
          },
          };

          然后還要改造一下我們的路由

          if (window.__POWERED_BY_QIANKUN__) {  
          microPath = '/vue2-micro-app'
          }

          const routes = [
          {
          path: microPath + '/login',
          name: 'login',
          component: Login
          },
          {
          path: microPath + '/',
          redirect: microPath + '/home'
          },
          {
          path: microPath + '/home',
          name: 'Home',
          component: Home
          },
          {
          path: microPath + '/about',
          name: 'About',
          component: () => import( /* webpackChunkName: "about" */ '../views/About.vue')
          }
          ]

          router.beforeEach((to, from, next) => {
          if (to.path !== (microPath + '/login')) {
          if (store.state.token) {
          next()
          } else {
          next(microPath + '/login')
          }
          }
          else {
          next()
          }
          })
          ?

          路由主要的改動(dòng)就是每個(gè)path都添加了一個(gè)microPath變量,用于檢測(cè)是否由微前端改動(dòng),相應(yīng)的路由守衛(wèi)也要添加microPath變量,另外微應(yīng)用的login跳轉(zhuǎn)的時(shí)候也要加上microPath判斷

          ?

          最后重啟一下我們的微應(yīng)用,再去我們的主應(yīng)用點(diǎn)擊一下Child Home菜單,如無(wú)意外你就會(huì)得到和我下面截圖一樣的界面沒(méi)錯(cuò),你已經(jīng)成功了!vue2的項(xiàng)目已經(jīng)成功嵌入到vue3中去了

          但是,細(xì)心的你也發(fā)現(xiàn)了,我已經(jīng)登錄了一次了,為什么又要登錄一次呀,所以,接下來(lái)我們要利用通信去解決掉這個(gè)問(wèn)題。

          主應(yīng)用和微應(yīng)用通信

          應(yīng)用間的通信,我們要利用qiankun框架的initGlobalStateMicroAppStateActions api,相關(guān)的api介紹如下:

          setGlobalState:設(shè)置 globalState - 設(shè)置新的值時(shí),內(nèi)部將執(zhí)行淺檢查,如果檢查到globalState發(fā)生改變則觸發(fā)通知,通知到所有的觀察者函數(shù)。

          onGlobalStateChange:注冊(cè)觀察者函數(shù) - 響應(yīng)globalState變化,在globalState發(fā)生改變時(shí)觸發(fā)該觀察者函數(shù)。

          offGlobalStateChange:取消觀察者函數(shù) - 該實(shí)例不再響應(yīng)globalState變化。

          所以我們?cè)俅胃脑煲幌聝蓚€(gè)項(xiàng)目,首先是主應(yīng)用的micros/index.js

          import {  
          registerMicroApps,  
          addGlobalUncaughtErrorHandler,  
          start,  
          initGlobalState // 新增
          from "qiankun";

          const state = {} 
          const actions = initGlobalState(state);

          export {  actions }

          以上新增了并導(dǎo)出了actions,然后去到login.vue

          import { actions } from "@/micros"//新增

          const login = () => {      
            if (username.value && password.value) {  
              store.commit("setToken""123456");        
              // 新增
              actions.setGlobalState({globalToken"123456"});        
              router.push({path"/"});
            }
          };

          引入actions并新增了actions.setGlobalState方法

          然后是子應(yīng)用的main.js

          function render(props{  
            console.log("子應(yīng)用render的參數(shù)", props) 
            // 新增 
            props.onGlobalStateChange((state, prevState) => {    
              // state: 變更后的狀態(tài); prev 變更前的狀態(tài)    
              console.log("通信狀態(tài)發(fā)生改變:", state, prevState);    
              // 這里監(jiān)聽到globalToken變化再更新store
              store.commit('setToken''123456')  }, true); 
             // 掛載應(yīng)用  
            instance = new Vue({    
              router,    
              store,    
              render(h) => h(App),  
            }).$mount("#micro-app");}

          render方法中我們加上onGlobalStateChange,并且第二位參數(shù)置為true,這樣微應(yīng)用一啟動(dòng)的時(shí)候,我們馬上就可以看到剛剛設(shè)置的globalToken:123456

          好了已經(jīng)改造完畢,我們刷新重新登錄主應(yīng)用然后點(diǎn)擊微應(yīng)用的菜單,可以看到微應(yīng)用不需要再登錄了,如下圖:

          好像還是有點(diǎn)問(wèn)題喔,微應(yīng)用的菜單怎么展示出來(lái)了???

          別怕,最后一步,留給親愛的你去解決吧,思路就是在微應(yīng)用中利用window.__POWERED_BY_QIANKUN__去判斷是否通過(guò)qiankun啟動(dòng)的,是的話我們寫個(gè)變量使用v-if將微應(yīng)用的菜單和頭部隱藏,不就完事了?

          ?

          以上就是qiankun框架實(shí)戰(zhàn)的第一篇本地開發(fā)的全部?jī)?nèi)容,總體結(jié)構(gòu)上跟我做的項(xiàng)目遷移很相似了,其它還有些小細(xì)節(jié)不影響,其實(shí)本章有一個(gè)巨坑,下一篇將帶大家部署打包后的項(xiàng)目,并告訴大家這個(gè)巨坑在哪里。

          ?
          如果覺得這篇文章還不錯(cuò)
          點(diǎn)擊下面卡片關(guān)注我
          來(lái)個(gè)【分享、點(diǎn)贊、在看】三連支持一下吧

             “分享、點(diǎn)贊在看” 支持一波 

          瀏覽 104
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  三级视频在线看 | 北条麻妃60分钟 | 豆花视频免费 | 99老色批 | 五月天乱伦小说 |