<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項目實戰(zhàn)(二)--踩坑與部署篇

          共 17395字,需瀏覽 35分鐘

           ·

          2021-06-18 07:09

          點擊上方 程序員成長指北,關注公眾號

          回復1,加入高級 Node 進階交流群


          作者:黑化程序員(作者授權轉載)

          鏈接:https://juejin.cn/post/6973111766767108103

          大家好,我是小黑。

          在上一篇《微前端框架qiankun項目實戰(zhàn)(一)--本地開發(fā)篇》發(fā)布后,感謝有網友提出了微應用的緩存問題,的確基于第一篇使用的registerMicroApps方式很難做到緩存,要做到應用緩存的方式使用手動加載管理微應用的方式是最好的,我將再寫一篇補充篇使用loadMicroApp手動管理微應用,本篇我會模擬部署一下主應用和微應用,并將揭開我上一篇所謂的巨坑是什么。

          貼上我建好的模板倉庫地址

          vue3模板:https://gitee.com/jimpp/vue3-main-app

          vue2模板:https://gitee.com/jimpp/vue2-micro-app

          在上一篇中,master分支都是未改造前能獨立運行的項目,dev分支是最終改造后的項目,本篇所有代碼會在新建的test分支修改

          隱藏微應用菜單和頭部

          在上篇的結尾,我們本地運行微前端的時候,發(fā)現微應用的菜單和頭部還是渲染出來了

          不知道親愛的你是否有思路如何實現隱藏,下面給出我的思路代碼

          // template
          <div class="nav" v-if="showMenu">
            <div class="menu">
              <router-link to="/">Child Home</router-link>
            </div>

            <div class="menu">
              <router-link to="/about">Child About</router-link>
            </div>

          </div>
          <div class="container">
            <div class="header" v-if="showHeader">Child Header</
          div>
            <div class="router-view">
              <router-view />
            </div>

          </div>

          /
          / js
          computed: {
              ...mapState(["token"]),
              /
          / 控制菜單顯示隱藏
              showMenu() {
                return this.token && !this.isMicroEnc
              },
              /
          / 控制頭部顯示隱藏
              showHeader() {
                return this.token && !this.isMicroEnc
              },
              isMicroEnc() {
                return window.__POWERED_BY_QIANKUN__
              }
            }

          利用computed根據token 和 window.POWERED_BY_QIANKUN 去控制顯示隱藏,效果如下

          token放進本地緩存

          這個過程中我們要不斷地修改項目,一刷新就要重新登錄實在太煩了,下面我們改造一下主應用,把登錄后的token存到localStorage中

          src/store/index.js

          mutations: {
              setToken(state, token) {
                state.token = token
                // 新增,登錄的時候同時把token存到localStorage
                localStorage.setItem('token', token)
              }
           },
           
           // 新增
           const storagePlugin = store => {
            const token = localStorage.getItem('token')
            if(token) {
              store.commit('setToken', token)
            }
          }

           plugins: [storagePlugin]

          這里在setToken方法中添加了把token存到localStorage的邏輯,并編寫了一個VuexstoragePlugin插件,該插件主要功能是在應用加載的時候去獲取localStorage中的token,如果有的話直接commit到我們的store中,這樣一來我們只要登錄了,再刷新也不需要重新登錄

          接下來,準備開始踩坑了

          坑1:樣式沖突問題

          首先遇到的樣式沖突,不是什么ui庫的沖突,而是iconfont的沖突,我是在改造兩個線上項目的時候遇到的

          首先去iconfont官網為兩個應用添加兩組圖標

          主應用的圖標

          微應用的圖標

          可以看到兩個應用的圖標命名是一致的,不過主應用是空心的,微應用是實心的

          下載好的圖標庫是這樣的

          我們只需要拷貝iconfont.css、iconfont.ttf、iconfont.woff、iconfont.woff2這幾個文件到src/assets目錄下,然后在main.css引入就可以了

          iconfont.css的代碼如下

          @font-face {
            font-family: "iconfont"/* Project id 2608947 */
            src: url('iconfont.woff2?t=1623503003854') format('woff2'),
                 url('iconfont.woff?t=1623503003854') format('woff'),
                 url('iconfont.ttf?t=1623503003854') format('truetype');
          }

          .iconfont {
            font-family: "iconfont" !important;
            font-size: 16px;
            font-style: normal;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
          }

          .icon-password:before {
            content"\ea41";
          }

          .icon-username:before {
            content"\e600";
          }

          main.css中引入

          @import url(./iconfont.css);

          兩個項目的引入方式是一樣的,最后的目錄結構如下:

          然后再分別去到兩個應用的views/Home.vue中添加兩個圖標

          <i class="iconfont icon-username"></i>
          <i class="iconfont icon-password"></i>

          刷新我們的瀏覽器

          可以看到,當點擊菜單切換時,都是空心圖標,這明顯有問題啊!我們明明一個有心有個無心!

          如何解決?

          當時在改造項目的過程中發(fā)現這個情況真的有點炸毛(fxxx = fine),不知道你是否有疑問,我為什么要把iconfont.css的代碼貼出來,因為我們解決這個問題的關鍵就在于

          font-family: "iconfont";

          大家可以看到兩個項目的iconfont.css都有這么一句話,然后引入的方式都是class="iconfont icon-xxx"的方式,我改造的項目也是如此,我猜測上面的問題跟這個有很大的關系,事實證明了我猜想是對的,下面我們來改造一下

          首先回到iconfont的官網,去到我們剛剛添加的圖標庫頁面,有個項目設置選項,點擊后會看到如下兩個選項

          沒錯,解決沖突的關鍵就是為兩個項目添加不同引用前綴和font-family,主應用前綴改為main-app-icon-,font-family改為main-app-iconfont,微應用相應改為micro-app-icon-micro-app-iconfont

          然后重新下載兩個圖標庫并重新引入,目前兩個iconfont.css的關鍵代碼如下

          // 主應用的iconfont.css
          @font-face {
            font-family"main-app-iconfont"/* Project id 2608947 */
            srcurl('iconfont.woff2?t=1623508357834'format('woff2'),
                 url('iconfont.woff?t=1623508357834'format('woff'),
                 url('iconfont.ttf?t=1623508357834'format('truetype');
          }

          .main-app-iconfont {
            font-family"main-app-iconfont" !important;
            font-size16px;
            font-style: normal;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
          }

          // 微應用的iconfont.css
          @font-face {
            font-family"micro-app-iconfont"/* Project id 2608945 */
            srcurl('iconfont.woff2?t=1623508587683'format('woff2'),
                 url('iconfont.woff?t=1623508587683'format('woff'),
                 url('iconfont.ttf?t=1623508587683'format('truetype');
          }

          .micro-app-iconfont {
            font-family"micro-app-iconfont" !important;
            font-size16px;
            font-style: normal;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
          }

          相應的我們引入圖標的方式也要改

          // 主應用中
          <i class="main-app-iconfont main-app-icon-username"></i>
          <i class="main-app-iconfont main-app-icon-password"></i>

          // 微應用中
          <i class="micro-app-iconfont micro-app-icon-username"></i>
          <i class="micro-app-iconfont micro-app-icon-password"></i>

          改造完畢后刷新瀏覽器

          可以看到,樣式沖突的問題已經解決了

          為什么會出現這個這個問題?

          官方提供了基于shadowDom的樣式隔離方案,不過似乎還是未做到完全的隔離,同類名的情況下可能還是會出現沖突,所以我們盡量通過不同類名添加前綴的方式去避免樣式沖突,或者是把類名降級放到一個父類中去避免樣式沖突

          什么意思呢?例如主微應用都有類名aaa,那么就可能會出現沖突 但是如果我們主應用改成這樣 .main-app > .aaa,微應用改成這樣.micro-app > .aaa,把原本處于根的aaa樣式用容器包裝起來,就可以避免樣式沖突,解決ui庫樣式沖突的方式也是這種思路,可以參考一下這篇文章

          部署微前端

          處理完樣式問題啦,貌似沒什么問題了,來打包部署一下吧

          部署前的改造

          還記得主應用micros/app.js如下:

          const apps = [
            /**
             * name: 微應用名稱 - 具有唯一性
             * entry: 微應用入口 - 通過該地址加載微應用
             * container: 微應用掛載節(jié)點 - 微應用加載完成后將掛載在該節(jié)點上
             * activeRule: 微應用觸發(fā)的路由規(guī)則 - 觸發(fā)路由規(guī)則后將加載該微應用
             */

            {
              name"vue_micro_app",
              entry"http://localhost:8081",
              container"#micro-container",
              activeRule"#/vue2-micro-app",
            },
          ];

          export default apps;

          目前entry是寫死的,我們可以部署的時候改,但是改來改去太麻煩啦,有沒有更好的方法

          if(process.env.NODE_ENV === 'development') {

          }else {

          }

          還記得這種判斷環(huán)境的代碼嗎,這里我們不用那么麻煩,vue-cli幫我們做好了,我們在根目錄添加.env.production.env.development文件,這兩個文件就是用來導出一些變量,顧名思義這些變量分別用在dev和pro環(huán)境下的,具體可以點擊這里了解

          .env.development中添加

          VUE_APP_MICRO_ENTRY="http://localhost:8081"

          至于.env.production中就添加服務器的域名就可以啦

          VUE_APP_MICRO_ENTRY="你的服務器域名"

          這里我正式環(huán)境用的是localhost:3001,稍后我會建本地服務器在3001端口部署微應用,3000端口部署主應用

          這里文件中的變量一定要以VUE_APP_ 開頭,否則是無效的

          相應的app.js要改成如下格式:

          // 新增
          const { VUE_APP_MICRO_ENTRY } = process.env

          const apps = [
            /**
             * name: 微應用名稱 - 具有唯一性
             * entry: 微應用入口 - 通過該地址加載微應用
             * container: 微應用掛載節(jié)點 - 微應用加載完成后將掛載在該節(jié)點上
             * activeRule: 微應用觸發(fā)的路由規(guī)則 - 觸發(fā)路由規(guī)則后將加載該微應用
             */

            {
              name"vue_micro_app",
              entry: VUE_APP_MICRO_ENTRY, // 修改
              container"#micro-container",
              activeRule"#/vue2-micro-app",
            },
          ];

          export default apps;

          然后重啟一下主應用,以后打包或者本地開發(fā)都不用再修改app.js啦

          開始部署

          接下來執(zhí)行npm run build 或者 yarn run build分別打包兩個項目

          然后可以新建一個項目名為mock-server,npm init 初始化一下后執(zhí)行npm install koanpm install koa-static,并添加兩個文件夾mian-appmicro-app,分別把打包后的主應用和微應用放進這兩個文件夾,再新建main-server.jsmicro-server.js

          這時mock-server的目錄結構如下

          然后為main-server.jsmicro-server.js添加如下代碼

          // main-server.js
          const Koa = require('koa')
          const path = require('path')
          const app = new Koa()
          const staticFiles = require('koa-static')

          const staticPath = path.join(__dirname + '/main-app')

          app.use(staticFiles(staticPath))

          app.listen(3000, () => {
            console.log('main server running at 3000')
          })

          --------------

          // micro-server.js
          const Koa = require('koa')
          const path = require('path')
          const app = new Koa()
          const staticFiles = require('koa-static')

          const staticPath = path.join(__dirname + '/micro-app')

          app.use(staticFiles(staticPath))

          app.listen(3001, () => {
            console.log('main server running at 3001')
          })

          代碼主要就是把打包出來的文件夾用koa分別在3000和3001端口跑起來,沒什么特別的

          然后訪問一下,主應用正常運行,微應用報錯了

          上篇在微應用render函數中有這么一段代碼:

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

          沒錯,當子應用獨立運行時,props是沒有onGlobalStateChange參數的,所以這里要添加判斷(添加的判斷還真不少的說),改成下面這個樣子:

          function render(props{
            console.log("子應用render的參數", props)
            // 新增判斷,如果是獨立運行不執(zhí)行onGlobalStateChange
            if(window.__POWERED_BY_QIANKUN__) { 
              props.onGlobalStateChange((state, prevState) => {
                // state: 變更后的狀態(tài); prev 變更前的狀態(tài)
                console.log("通信狀態(tài)發(fā)生改變:", state, prevState);
                store.commit('setToken''123456')
              }, true);
            }
            // 掛載應用
            instance = new Vue({
              router,
              store,
              render(h) => h(App),
            }).$mount("#micro-app");
          }

          重新build并放到mock-server中重新運行3001端口,刷新后可以看到微應用運行成功

          跨域問題

          當從主應用切換到微應用時

          沒錯,經典的跨域問題,因為部署的是本地,有兩個解決辦法

          第一個(不推薦)是作弊的方法

          新建一個chrome瀏覽器的快捷方式,然后右鍵,屬性

          在目標這一欄, --user-data-dir=E:\MyChromeDevUserData到末尾,注意--user前有空格,然后用這個新建的快捷方式可以訪問部署后的應用

          第二種,使用koa2-cors

          在mock-server中執(zhí)行npm install koa2-cors,然后修改一下micro-server.js

          const Koa = require('koa')
          const path = require('path')
          const app = new Koa()
          const staticFiles = require('koa-static')
          const cors = require('koa2-cors'); // 新增

          const staticPath = path.join(__dirname + '/micro-app')

          app.use(cors());// 新增
          app.use(staticFiles(staticPath))

          app.listen(3001, () => {
            console.log('main server running at 3001')
          })

          重啟micro-server.js并刷新瀏覽器,可以看到切換菜單已經正常啦

          第三種,利用nginx做代理(建議)

          貼上nginx.conf

          #user  nobody;
          worker_processes 1;

          #error_log  logs/error.log;
          #error_log  logs/error.log  notice;
          #error_log  logs/error.log  info;

          #pid        logs/nginx.pid;
          events {
           worker_connections 1024;
          }


          http {
           include mime.types;
           default_type application/octet-stream;
           sendfile on;
           keepalive_timeout 65;

           server {
                          # 監(jiān)聽的端口
            listen 3001;
            server_name localhost;
            location / {
                          #允許跨域訪問
             add_header Access-Control-Allow-Origin *;
             add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
             add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

             if ($request_method = 'OPTIONS') {
              return 204;
             }
                                  
                                  # 代理的文件夾
             root E:\project\vue-project\vue2-micro-app\dist;
             autoindex on;
            }
           }

          }

          使用nginx后,我們的micro-appmicro-server.js已經不需要了,因為nginx已經做了代理,允許nginx,刷新瀏覽器,可以看到切換菜單已經正常啦

          坑2:頁面無法跳轉問題

          這個問題就是我上一節(jié)所說的巨坑,因為這個頁面無法跳轉,在本地是沒有任何問題的!然而部署到測試環(huán)境后,100%復現,本地環(huán)境100%沒問題,你看一步步走到現在也沒發(fā)現這個問題,這就是程序員經典場景----我本機是好的呀o(╥﹏╥)o

          注意,即使是使用nginx代理后在本地部署依然無法在本地復現這個問題,我會配合gif圖來還原這個問題

          場景還原(以下全部假設運行在測試服務器)

          本地也部署跑過感覺沒問題了,開開心心部署到測試服務器,然后一訪問,瞬間傻眼了

          為什么會這樣呀??可以看到無論是本地還是測試服務器都是沒有任何報錯的,然后這個問題我搞了幾乎3天

          如何解決?

          到了第三天的時候,我差不多想放棄微前端改造方案了,突然我發(fā)現,我們點擊菜單的時候,url是有變化的,但是頁面沒有跳轉,所以我又大膽猜測,是不是路由的問題,而且可以看到,每次我們在主微應用之間切換的時候,都會執(zhí)行微應用main.js中導出的mount和unmount函數,然后注意到unmount有這么一段代碼

          export async function unmount({
            console.log("VueMicroApp unmount");
            // 注意這里
            instance.$destroy();
            instance = null;
          }

          而微應用的routerindex.js是這樣的

          微應用main.js中的render函數是這樣的

          可以看到,由始至終,router都是同一個實例!然后每次unmount都會執(zhí)行應用卸載,會不會就是這個問題導致的呢

          接下來改造微應用的router.js,不再導出router而是導出routes數組

          然后改造main.js

          import VueRouter from 'vue-router'
          import routes from './router'

          Vue.use(VueRouter)

          // 新增:用于保存router實例
          let router = null;
          let microPath = ''

          // 新增:動態(tài)設置 webpack publicPath,防止資源加載出錯
          if (window.__POWERED_BY_QIANKUN__) {
            // eslint-disable-next-line no-undef
            __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
            microPath = '/vue2-micro-app'
          }

          function render(props{
            console.log("子應用render的參數", props)
            if(window.__POWERED_BY_QIANKUN__) {
              props.onGlobalStateChange((state, prevState) => {
                // state: 變更后的狀態(tài); prev 變更前的狀態(tài)
                console.log("通信狀態(tài)發(fā)生改變:", state, prevState);
                store.commit('setToken''123456')
              }, true);
            }
            // 新增
            router = new VueRouter({
              routes
            })
            // 新增
            router.beforeEach((to, from, next) => {
            if (to.path !== (microPath + '/login')) {
              if (store.state.token) {
                next()
              } else {
                next(microPath + '/login')
              }
            } else {
              next()
            }
          })
            // 掛載應用
            instance = new Vue({
              router,
              store,
              render(h) => h(App),
            }).$mount("#micro-app");
          }

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

          修改后的main.js,router不再是同一個實例,而是每次mount的時候都會新獲取一個實例,相應的路由守衛(wèi)也要搬遷出來,然后npm run serve看到本地運行微應用沒問題,好npm run build重新打包并重新運行nginx

          可以看到,這次部署是真的成功了

          PS:在vue3中如果直接監(jiān)聽整個route對象,也會出現頁面無法跳轉的情況

          歡迎指出不足和交流,踩坑不易,如果對你有幫助的話,點個贊吧~(#^.^#)

          參考文獻

          明源云的qiankun教程:https://github.com/a1029563229/blogs/blob/master/BestPractices/qiankun/Communication.md

          qinkun官網:https://qiankun.umijs.org/zh/api#initglobalstatestate

          如果覺得這篇文章還不錯
          點擊下面卡片關注我
          來個【分享、點贊、在看】三連支持一下吧

             “分享、點贊在看” 支持一波 

          瀏覽 57
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  少妇做受 高潮10在线 | 黄片在线免费网站 | 精品色婷婷| 日本黄色视频大全 | 欧美美综合网 |