微前端應(yīng)用及基于qiankun的微前端實踐

示例代碼倉庫:
yl-qiankun-base:https://gitee.com/dongche/yl-qiankun-base.git
yl-qiankun-child-vue:https://gitee.com/dongche/yl-qiankun-child-vue.git

微前端概念起源

微前端概念最早其實是借鑒了微服務(wù)的概念,最早是出現(xiàn)在2016年的ThoughtWorks Technology Radar(ThoughtWorks技術(shù)雷達(dá))

什么是微前端

MicroFrontends 官方解釋:用來構(gòu)建能夠讓 多個團(tuán)隊 獨(dú)立交付項目代碼的 現(xiàn)代web app 技術(shù),策略以及實踐方法
MicroFrontends 官網(wǎng):https://swearer23.github.io/micro-frontends/
所以微前端并不是一個單純的技術(shù)點(diǎn),而是一個為了解決復(fù)雜應(yīng)用,特別時便于以后維護(hù)的思路和方法。
核心思路:根據(jù)功能模塊拆解應(yīng)用,然后根據(jù)需求組裝,應(yīng)用之間可通信
特點(diǎn): 1.模塊可以使用不同技術(shù)棧
2.應(yīng)用隔離,各應(yīng)用可獨(dú)立部署,可獨(dú)立運(yùn)行
3.應(yīng)用之間松耦合
4.可漸進(jìn)式遷移

用圖做個簡單說明:


qiankun 圖示.png

擴(kuò)展理解:組件化的延伸,由項目內(nèi)的組件化擴(kuò)展到項目之間的組件化

微前端的應(yīng)用場景

1.老系統(tǒng)的迭代:復(fù)雜的老系統(tǒng),可能由于各種原因,代碼風(fēng)格不一,代碼冗雜,質(zhì)量參差不齊,然后還要不斷加入新功能。
2.復(fù)雜應(yīng)用功能模塊的拆分和細(xì)化,便于各團(tuán)隊同步進(jìn)行開發(fā)和維護(hù)
3.有比較大功能模塊需要在不同的項目中使用
4.需求不定,經(jīng)常有對項目進(jìn)行拆分和融合
5.平臺型應(yīng)用。方便刪減功能模塊和外部接入

目前市面上的主流微前端方案及框架

微前端模式

  • 自由組織模式
    沒有形式,自由嵌套
  • 基座模式
    有一個父級主應(yīng)用為容器基座,其他應(yīng)用需要注冊接入
  • 去中心模式
    各應(yīng)用之間各自為政又可以彼此分享資源,沒有基座不存在主次關(guān)系

微前端方案

  • iframe 方案

    • 優(yōu)點(diǎn):html 提供的標(biāo)簽,天然隔離,任意嵌套,能加載任意web應(yīng)用。適合自由組織自由嵌套模式,無需配置,簡單易用

    缺點(diǎn):參考qiankun技術(shù)圓桌Why Not Iframe

    1. url 不同步。瀏覽器刷新 iframe url 狀態(tài)丟失、后退前進(jìn)按鈕無法使用。
    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)用進(jìn)入都是一次瀏覽器上下文重建、資源重新加載的過程。
  • web Components 方案
    Web Components 是一套 瀏覽器原生組件,由google發(fā)起。
    web Components 三大技術(shù):

    • Custom elements(自定義元素):一組JavaScript API,允許您定義custom elements及其行為,然后可以在您的用戶界面中按照需要使用它們。
    • Shadow DOM(影子DOM):一組JavaScript API,用于將封裝的“影子”DOM樹附加到元素(與主文檔DOM分開呈現(xiàn))并控制其關(guān)聯(lián)的功能。通過這種方式,您可以保持元素的功能私有,這樣它們就可以被腳本化和樣式化,而不用擔(dān)心與文檔的其他部分發(fā)生沖突。
    • HTML templates(HTML模板): <template> 和 <slot> 元素使您可以編寫不在呈現(xiàn)頁面中顯示的標(biāo)記模板。然后它們可以作為自定義元素結(jié)構(gòu)的基礎(chǔ)被多次重用

    具備實現(xiàn)微前端的特點(diǎn):
    1.技術(shù)棧無關(guān):瀏覽器原生支持,和框架沒有關(guān)系
    2.獨(dú)立運(yùn)行,應(yīng)用隔離:Shadow DOM 特性

    1. web Componets組件獨(dú)立開發(fā),相互之間沒有依賴關(guān)聯(lián),但是又可以作為組件自由引入組裝

    最大缺點(diǎn):目前瀏覽器兼容性差
    參照 can i use

    image.png

web Components 偏向基座模式。
目前國內(nèi)京東的 micro-app 借鑒了web components 的思想

  • 基座模式的基于single-spa 的路由劫持方案
    這是目前主流的微前端方案,single-spa 也是最早成熟的微前端方案,特別適用于單頁面應(yīng)用。

    single-spa是通過監(jiān)聽 url change 事件,在路由變化時匹配到渲染的子應(yīng)用并進(jìn)行渲染,這個思路也是目前實現(xiàn)微前端的主流方式。同時single-spa要求子應(yīng)用修改渲染邏輯并暴露出三個方法:bootstrap、mount、unmount,分別對應(yīng)初始化、渲染和卸載,這也導(dǎo)致子應(yīng)用需要對入口文件進(jìn)行修改。single-spa 最大問題是配置麻煩
    qiankun出自阿里,基于single-spa進(jìn)行封裝,繼承single-spa特性,但是使用簡單,上手相對容易

  • 去中心化模式的 ESM 方案
    ESM 是 ES Module 的縮寫,是 Ecma script 2015 中提出的一種前端模塊化手段。ESM方案webpack5模塊聯(lián)邦,多個應(yīng)用可以互相嵌套,可以深入到組件導(dǎo)入導(dǎo)出
    ESM 方案可視作 ES6 模塊化的擴(kuò)展,使用遠(yuǎn)程加載ESM模塊方式。

    成熟方案參考:
    EMP官網(wǎng)
    EMP github

基座模式:主應(yīng)用基于vue及qiankun 的微前端實踐

qiankun 基于 single-spa 封裝,是目前市面上最主流的微前端方案,出自阿里。
qiankun 官網(wǎng):https://qiankun.umijs.org/zh/guide
qinkun 技術(shù)圓桌:https://www.yuque.com/kuitos/gky7yw/nwgk5a

image.png

簡單說下qiankun

這里我們不對qiankun做什么深入點(diǎn)剖析,想詳細(xì)了解qiankun的可去官網(wǎng),或者研究下源碼。
我們拿一個做好的微前端項目,用瀏覽器打開控制臺,查看ajax請求,會發(fā)現(xiàn)有個請求子應(yīng)用的ajax請求,這個請求返回了子頁面內(nèi)容


image.png

這時候我們點(diǎn)擊能訪問子應(yīng)用頁面的按鈕或菜單,能看到如下內(nèi)容

image.png

查看html


簡單來說,qiankun就是拿到子應(yīng)用的訪問路徑(entry配置),并且給每個子應(yīng)用都起一個獨(dú)一無二的名字(name配置),然后通過ajax請求子應(yīng)用的數(shù)據(jù),再解析到主應(yīng)用設(shè)定好的容器里面(比如#micro-app)。至于具體拿到數(shù)據(jù)后是如何進(jìn)行解析,如何劫持路由的,這個就不做深討論,主要是我也不知道。

下面我們來做基于qiankun 的 微前端實踐

項目搭建

按照 qiankun 的說法,接入qiankun時,react,vue等單頁面應(yīng)用使用history模式最好,所以我們這里主應(yīng)用和子應(yīng)用暫時都使用 history模式

  • 創(chuàng)建主應(yīng)用
    這里的主應(yīng)用我們用vue-ant-design-pro 創(chuàng)建,應(yīng)用名 yl-qiankun-base
    git clone --depth=1 https://github.com/vueComponent/ant-design-vue-pro.git yl-qiankun-base
    
  • 創(chuàng)建vue子應(yīng)用,也是基于vue-ant-design-pro 創(chuàng)建,子應(yīng)用名 yl-qiankun-child-vue
    git clone --depth=1 https://github.com/vueComponent/ant-design-vue-pro.git yl-qiankun-child-vue
    

主應(yīng)用改造

下載qiankun

npm i qiankun --save

首先將主應(yīng)用的id="app"改掉,以免和子應(yīng)用沖突,將public/index.html 里面的id="app"改為獨(dú)有的id。這里用 ylQiankunBase


image.png

第二,我們這里假設(shè)業(yè)務(wù)確定為希望從菜單加載子應(yīng)用,即點(diǎn)擊菜單才加載子應(yīng)用。那么這里把項目中的 BasicLayout改掉。ant-design-pro 有些東西封裝得比較死,不符合要求的就去掉。
這里首先說下基本思路:主應(yīng)用除了加載子應(yīng)用,還要加載自己本身應(yīng)用里面的路由,所以BasicLayout是不能滿足需求的。除了改造BasicLayout外,我們還要加個裝載子應(yīng)用的容器組件ChildAppLayout。
1)改造BasicLayout
A.重寫B(tài)asicLayout,理由是我們需要在點(diǎn)擊菜單時做更多的事情,而pro-layout沒有提供菜單點(diǎn)擊api,并且a-menu @select 事件 返回的數(shù)據(jù)也不符合要求,我們可能希望得到點(diǎn)擊的整個菜單數(shù)據(jù),而@select事件只是給了key。
B.考慮到菜單組件以后有可能會重用,這里吧菜單組件抽離除了,命名 Slider

改造后的BasicLayout 如下:

<template>
  <a-layout id="components-layout-demo-fixed-sider">
   <slider/>
    <a-layout :style="{ marginLeft: '200px' }">
      <a-layout-header :style="{ background: '#fff', padding: 0 }" />
      <a-layout-content :style="{ margin: '24px 16px 0', overflow: 'initial' }">
       <router-view/>
      </a-layout-content>
    </a-layout>
  </a-layout>
</template>
<script>
import Slider from "../components/slider/slider"
export default {
  components:{
    Slider
  },
}
</script>

Slider 菜單組件如下:

<template>
  <a-layout-sider :style="{ overflow: 'auto', height: '100vh', position: 'fixed', left: 0 }">
    <div class="logo" >
      yl-qiankun-base
    </div>
    <a-menu theme="dark" mode="inline" :default-selected-keys="['4']">
      <a-sub-menu v-for='(menu,index) in menus' :key='menu.name'>
        <span slot="title"><a-icon :type="menu.meta.icon" /><span>{{ menu.meta.title }}</span></span>
        <a-menu-item v-for='(child,index) in menu.children' :key='child.name' @click.native='select(child)'>
          <a-icon :type="child.meta.icon" />
          <span class="nav-text">{{ child.meta.title }}</span>
        </a-menu-item>
      </a-sub-menu>
    </a-menu>
  </a-layout-sider>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'Slider',
  data() {
    return {
      menus:[],//菜單
    }
  },
  computed: {
    ...mapState({
      // 動態(tài)主路由
      mainMenu: state => state.permission.addRouters
    }),
  },
  mounted() {
    const routes = this.mainMenu.find(item => item.path === '/')
    this.menus = (routes && routes.children) || []
  },
  methods: {
    /**
     * 選中菜單
     * @param menuInfo
     */
    select(menuInfo){
      console.log(menuInfo)
      const { name } = menuInfo
      this.$router.push({
        name
      })
    },
  }
}
</script>

<style>
#components-layout-demo-fixed-sider .logo {
  height: 32px;
  background: rgba(255, 255, 255, 0.2);
  margin: 16px;
  font-size: 20px;
  color: #ffffff;
  font-weight: bold;
  display: flex;
  justify-content: center;
  align-items: center;
}
</style>

ChildAppLayout.vue,這里我們將注冊微應(yīng)用的代碼抽成函數(shù)registerChildApp放到util中

<template>
  <div>
    <div id="micro-app"></div>
  </div>
</template>
<script>
import Slider from '@/components/slider/slider'
import {registerChildApp} from '@/utils/util'

export default {
  components:{
    Slider
  },
  created () {
    registerChildApp()
  },
}
</script>

在util.js中寫registerChildApp函數(shù)。
這里說下幾個參數(shù):
name: 子應(yīng)用名稱,這個注意必須和 子應(yīng)用package.json里面的name一致,生產(chǎn)環(huán)境打包后,qiankun將會根據(jù)這個name查找子應(yīng)用
entry:子應(yīng)用入口地址。實際使用時本地開發(fā)可使用localhost,生產(chǎn)環(huán)境需要更改為子應(yīng)用的實際訪問路徑,這里可以使用Node環(huán)境變量根據(jù)環(huán)境賦值。在本示例中,子應(yīng)用訪問不僅不是根路徑,是yl-qiankun-child-vue下,因此entry要攜帶/yl-qiankun-child-vue
container: 子應(yīng)用加載容器,全局唯一id值
activeRule: 加載子應(yīng)用的跟路徑。這里確保和子應(yīng)用訪問路徑的yl-qiankun-child-vue一致。否則有坑

import { initGlobalState, registerMicroApps, runAfterFirstMounted, start } from 'qiankun'
import Vue from 'vue'
/**
 * 注冊子應(yīng)用
 */
export const registerChildApp = () => {
  //       // 注冊子應(yīng)用
  registerMicroApps(
    [
      {
        name: 'ylQiankunChildVue', // 微應(yīng)用應(yīng)用名稱,同微應(yīng)用的package.json中的name一致
        entry: '//localhost:6532/yl-qiankun-child-vue/',
        container: '#micro-app', //父級應(yīng)用裝載子應(yīng)用的容器id
        activeRule: '/yl-qiankun-child-vue', //加載子應(yīng)用的跟路徑
      },
    ],
    {
      beforeLoad: [
        app => {
          console.log('beforeLoad');
        }
      ],
      beforeMount: [
        app => {
          console.log('beforeMount');
        }
      ],
      beforeUnmount: [
        app => {
          console.log('beforeUnmount');
        }
      ],
      afterUnmount: [
        app => {
          console.log('afterUnmount');
        }
      ]
    }
  )
// setDefaultMountApp(firstApp)
// 第一個子應(yīng)用加載完畢后回調(diào)
  runAfterFirstMounted(()=>{
    console.log('第一個子應(yīng)用加載完畢后的回調(diào)')
  })
// 通訊
// 將action對象綁到Vue原型上,為了項目中其他地方使用方便
  Vue.prototype.$qiankunActions = initGlobalState({menuClick:'init',})
  start({prefetch :'all'})
}

子應(yīng)用改造

子應(yīng)用不需要安裝qiankun

- 基于vue 的子應(yīng)用改造

注意這里的子應(yīng)用基于 vue-ant-design-pro創(chuàng)建,所以帶有些默認(rèn)的文件。另外子應(yīng)用訪問路徑放在 yl-qiankun-child-vue 下,這里需要配合配置vue.config.js

基于vue的子應(yīng)用的改造主要有:
添加public-path.js 文件;改寫router路由導(dǎo)出文件;權(quán)限文件permission.js;main.js文件;組件容器BasicLayout.vue文件

首先添加public-path.js。在和main.js同級處新建public-path.js,內(nèi)容如下

if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

如果這段代碼引發(fā)了你的eslient報錯,請將相關(guān)規(guī)則關(guān)閉,我這邊就將camelcase設(shè)為了off

第二,改造main.js
這里說下 main.js改造的關(guān)鍵地方:
1)頂部引入 public-path.js
2)將路由注冊移植到main.js中。這里影響可能比較大,這樣的話,以前引入router文件的地方,都得改造,比如permission.js
3)增加qiankun 需要導(dǎo)出的三個生命周期函數(shù) bootstrap,mount,unmount

詳細(xì)代碼如下:(這里ant-design-pro創(chuàng)建攜帶的代碼無關(guān)的已忽略刪除)

// with polyfills
import './public-path' // 引入公共路徑
import Vue from 'vue'
import App from './App.vue'
import store from './store/'
import i18n from './locales'
import Router from 'vue-router'
import antBootstrap from './core/bootstrap'

import { constantRouterMap } from '@/config/router.config'
import routerEach from '@/permission'

const originalPush = Router.prototype.push
Router.prototype.push = function push (location, onResolve, onReject) {
  if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
  return originalPush.call(this, location).catch(err => err)
}
Vue.use(Router)
Vue.config.productionTip = false

let router = null
let instance = null
function render() {
  router = new Router({
    base: '/yl-qiankun-child-vue/',
    routes:[...constantRouterMap],
    mode: 'history',
  })
  routerEach(router) // 路由監(jiān)聽要在 new Vue 之前
  instance = new Vue({
    store,
    router,
    i18n,
    created: antBootstrap,
    render:h=>h(App),
  }).$mount('#app')

}

// 生命周期 - 掛載前
export async function bootstrap (props) {
  console.log('one bootstrap')
  console.log(props)
}
// 生命周期 - 掛載后
export async function mount(props) {
  // console.log('one mount');
  props.onGlobalStateChange((state,prev)=>{
    console.log(state)

  })
  // 渲染
  render()
}
// 生命周期 - 解除掛載
export async function unmount(){
  instance.$destroy()
  instance.$el.innerHTML = ""
  instance = null
  router = null
}

// 本地調(diào)試
if(!window.__POWERED_BY_QIANKUN__){
  render()
}


關(guān)于base: '/yl-qiankun-child-vue/'這行,如果不使用qiankun和使用qiankun,路徑不一致,則需判斷環(huán)境,可以寫成base: window.POWERED_BY_QIANKUN ? '/yl-qiankun-child-vue/' : '/' 類似

關(guān)于routerEach,由于permission.js 已經(jīng)無法直接引入router文件獲取router對象,這里改為導(dǎo)出函數(shù),傳遞router方式。詳見permission.js改造

第三,改造permission.js(無關(guān)的忽略)


import store from './store'
import storage from 'store'
import NProgress from 'nprogress' // progress bar
import '@/components/NProgress/nprogress.less' // progress bar custom style
import notification from 'ant-design-vue/es/notification'
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { i18nRender } from '@/locales'

NProgress.configure({ showSpinner: false }) // NProgress Configuration

const allowList = ['login', 'register', 'registerResult'] // no redirect allowList
const loginRoutePath = '/user/login'
const defaultRoutePath = '/list'

const routerEach = (router) => {
  router.beforeEach((to, from, next) => {
   // 全局路由守衛(wèi)邏輯代碼,太占位置,省略。。。
  })

  router.afterEach(() => {
    NProgress.done() // finish progress bar
  })
}
export default routerEach

重點(diǎn)就是將router.beforeEach和afterEach部分抽成一個導(dǎo)出的函數(shù)routerEach,并且傳入router對象參數(shù)

第四,改造vue.config.js
這里改造的重點(diǎn):
配置輸出

 config.output.library = name
    config.output.libraryTarget = 'umd' // 把微應(yīng)用打包成 umd 庫格式
    config.output.jsonpFunction = `webpackJsonp_${name}`

允許跨域

  headers: {
      'Access-Control-Allow-Origin':'*'
    },

完整代碼:

const path = require('path')
const webpack = require('webpack')
const {name} = require('./package') //引入包名

module.exports = {
  publicPath:'/yl-qiankun-child-vue/',
  configureWebpack: config => {
    // preview.pro.loacg.com only do not use in your production;
    if (process.env.VUE_APP_PREVIEW === 'true') {
      // add `ThemeColorReplacer` plugin to webpack plugins
      config.plugins.push(createThemeColorReplacerPlugin())
    }
    // 為了讓主應(yīng)用能正確識別微應(yīng)用暴露出來的一些信息,微應(yīng)用的打包工具需要增加如下配置
    config.output.library = name
    config.output.libraryTarget = 'umd' // 把微應(yīng)用打包成 umd 庫格式
    config.output.jsonpFunction = `webpackJsonp_${name}`
    // if prod, add externals
    config.externals = isProd ? assetsCDN.externals : {}
  },
  devServer: {
    port:6532, // 子應(yīng)用的端口最好設(shè)置特別點(diǎn),不容易被覆蓋
    // 允許被主應(yīng)用跨域fetch請求到
    headers: {
      'Access-Control-Allow-Origin':'*'
    },
    open: false, //配置自動啟動瀏覽器
    https: false,
    hotOnly: false,
  },
}

第五,改造BasicLayout.vue
由于本示例中,子應(yīng)用是點(diǎn)擊菜單加載的,也就是說在微前端模式下運(yùn)行,子應(yīng)用是不需要菜單和頂部的,我們根據(jù)qiankun環(huán)境變量window.POWERED_BY_QIANKUN來做更改。微前端模式下,去掉菜單和頂部,獨(dú)自運(yùn)行時需要菜單和頂部。
改造代碼如下(這里其實改動很小,只需要一個v-if 和 v-else根據(jù)環(huán)境判斷,為了不浪費(fèi)篇幅,其他代碼省略)

<template>
  <router-view v-if="isQiankun"/>
  <pro-layout
    :menus="menus"
    :collapsed="collapsed"
    :mediaQuery="query"
    :isMobile="isMobile"
    :handleMediaQuery="handleMediaQuery"
    :handleCollapse="handleCollapse"
    :i18nRender="i18nRender"
    v-bind="settings"
    v-else
  >
  <!--里面代碼省略-->
  </pro-layout>
</template>

<script>
//引用及其他代碼均省略,只展示isQiankun變量

export default {
  name: 'BasicLayout',
  data () {
    return {
      isQiankun:window.__POWERED_BY_QIANKUN__,// 是否時微前端qiankun環(huán)境
    }
  },
}
</script>

當(dāng)然,如此改造比較粗糙,你會發(fā)現(xiàn)子應(yīng)用獨(dú)立運(yùn)行和在微應(yīng)用下運(yùn)行時界面表現(xiàn)可能會有些不同。想要保持一致,還是得去掉 pro-layout,自己重構(gòu)這個容器

一些報錯和坑及注意事項及小知識

下面列舉一些實踐過程中可能出現(xiàn)的報錯和坑

  • 下面是子應(yīng)用為 vue項目的報錯

1.按照qiankun官網(wǎng)配置 webpack 的 output,報錯 Invalid options in vue.config.js: "output" is not allowed。官網(wǎng)配置示例是這樣的
[圖片上傳失敗...(image-7f071a-1649416618198)]
這是因為比較高版本的webpack已經(jīng)不允許這種寫法,這個配置要放在configureWebpack中

const {name} = require('./package')
module.exports = {
    configureWebpack: config=>{
        // console.log(config)
        config.output.library=name
        config.output.libraryTarget= 'umd'
        config.output.jsonpFunction=`webpackJsonp_${name}`
    },
}

2.Error: single-spa minified message #20
[圖片上傳失敗...(image-b71d38-1649416618198)]
這個問題多半是主應(yīng)用 注冊 子應(yīng)用時,即調(diào)用 registerMicroApps 時第一個參數(shù)穿得不對,對比官方demo檢查下第一個參數(shù)數(shù)據(jù)

3.跨越 Access to fetch at 'http://localhost:6532/' from origin 'http://localhost:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Uncaught (in promise) TypeError: application 'ylQiankunChildVue' died in status LOADING_SOURCE_CODE: Failed to fetch
ncaught TypeError: application 'ylQiankunChildVue' died in status LOADING_SOURCE_CODE: Failed to fetch

[圖片上傳失敗...(image-8c9ada-1649416618198)]
1)配置子應(yīng)用的時候,要運(yùn)行父級應(yīng)用跨域訪問子應(yīng)用。否則父級應(yīng)用拿不到數(shù)據(jù)無法讀取子應(yīng)用文件。這個在qiankun快速上手部分沒有說,但是在 qiankun官網(wǎng) 項目實戰(zhàn)部分有配置。要加上devServer 配置 headers配置,設(shè)置為 Access-Control-Allow-Origin: '*'

const { name } = require('./package');
module.exports = {
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
};

2)檢查子應(yīng)用是否有public-path文件,main.js里面是否引入pablic-path.js
[圖片上傳失敗...(image-721b16-1649416618198)]
main.js 最頂部,記得要放最頂部

import './public-path' // 引入公共路徑

如果做了兩點(diǎn)配置還有跨越報錯,那就很可能是主應(yīng)用的entry url 設(shè)置不對

4.開發(fā)環(huán)境下,子應(yīng)用反向代理無效
因為在微前端模式下,子應(yīng)用的資源是主應(yīng)用通過http請求獲取后在主應(yīng)用解析的,所以開發(fā)環(huán)境下的反向代理,只有主應(yīng)用的配置生效。如果子應(yīng)用的方向代理配置和主應(yīng)用原本的不一致,需要在主應(yīng)用配置子應(yīng)用需要的代理

5.application 'ylQiankunChildVue' died in status NOT_MOUNTED: [qiankun]: Target container with #micro-app not existed after ylQiankunChildVue mounted!
[圖片上傳失敗...(image-4d11f5-1649416618198)]
這個報錯的意思是注冊的時候,容器 #micro-app 不存在,子應(yīng)用死亡。這個錯誤真的也很常見,造成的原因可能不止一個。反正也很坑。
1)對于主應(yīng)用是vue的項目,等dom加載完成后再注冊,this.$nextTick里面注冊

  1. 由于vue-cli項目創(chuàng)建的時候,都會有個 id="app",這個是個坑,記得把父級的所有id="app"去掉。否則一直報這個錯誤讓你無解。有些項目并不是直接用vue-cli創(chuàng)建的,而是基于一些做好的布局框架,比如本文的主應(yīng)用就是基于vue-ant-design-pro 創(chuàng)建點(diǎn),這個庫創(chuàng)建的項目在App.vue里面還有個id="app",記得去掉,或者改為和main.js里面使用的一致。否則坑的讓你找不著北
    [圖片上傳失敗...(image-c1f435-1649416618198)]
    3)上面兩點(diǎn)都檢查過了,那就看看 容器id ,比如本文的 #micro-app 是否存在重復(fù)

    總的一點(diǎn)就是確保各注冊應(yīng)用id唯一,子應(yīng)用容器id唯一

更多問題解決參考 qiankun 常見問題,里面有很多踩過的坑,會有不少幫助

6.主應(yīng)用和子應(yīng)用使用同一套開發(fā)框架,比如都用基于vue 的element-ui,如何保持主題一致?

答案就是如果主應(yīng)用設(shè)定了自己的主題(非element-ui默認(rèn)主題),那么子應(yīng)用則不需要設(shè)置主題,那么子應(yīng)用就會使用主應(yīng)用的主題樣式。當(dāng)然這個時候如果子應(yīng)用單獨(dú)運(yùn)行,那主題就是element-ui的默認(rèn)主題了,想要單獨(dú)運(yùn)行時不使用默認(rèn)主題,接入微應(yīng)用時使用主應(yīng)用的主題,可以根據(jù)window.POWERED_BY_QIANKUN判斷環(huán)境來加載主題

7.從子項目頁面跳轉(zhuǎn)到主項目自身的頁面時,主項目頁面的 css 未加載
在全局路由守衛(wèi)router.beforeEach處做處理,示例:

/**
 * 解決從子項目頁面跳轉(zhuǎn)到主項目自身的頁面時,主項目頁面的 css 未加載的 bug
 * 產(chǎn)生這個問題的原因是:在子項目跳轉(zhuǎn)到父項目時,子項目的卸載需要一點(diǎn)點(diǎn)的時間,在這段時間內(nèi),父項目加載了,插入了 css,但是被子項目的 css 沙箱記錄了,然后被移除了。父項目的事件監(jiān)聽也是一樣的,所以需要在子項目卸載完成之后再跳轉(zhuǎn)。
 *解決方案:先復(fù)制 HTMLHeadElement.prototype.appendChild 和 window.addEventListener ,路由鉤子函數(shù) beforeEach 中判斷一下,如果當(dāng)前路由是子項目,并且去的路由是父項目的,則還原這兩個對象
 */
const rawAppendChild = HTMLHeadElement.prototype.appendChild
const rawAddEventListener = window.addEventListener
router.beforeEach((to, from, next) => {
 
  if(/^\/yl-qiankun-child-vue\//.test(from.path) && !/^\/yl-qiankun-child-vue\//.test(to.path)){
    // 用路徑來判斷是否是從子項目跳轉(zhuǎn)到主項目
    HTMLHeadElement.prototype.appendChild = rawAppendChild
    window.addEventListener = rawAddEventListener
  }
  next()
  })

你真的需要微前端嗎

最后,還是想談下,我們真的要使用微前端嗎?
個人覺得技術(shù)是要選則最合適自身業(yè)務(wù)的,不是為了使用技術(shù)而選技術(shù),也不是為了追隨潮流而使用技術(shù)。必須得考慮目前業(yè)務(wù),技術(shù)團(tuán)隊情況,以及以后業(yè)務(wù)和技術(shù)團(tuán)隊可能的發(fā)展。拒絕教科書式照搬,還是得實事求是。
參照qiankun技術(shù)圓桌:你可能并不需要微前端中的總結(jié):

滿足以下幾點(diǎn),你可能就不需要微前端

  1. 你/你的團(tuán)隊 具備系統(tǒng)內(nèi)所有架構(gòu)組件的話語權(quán)
    簡單來說就是,系統(tǒng)里的所有組件都是由一個小的團(tuán)隊開發(fā)的。
  2. 你/你的團(tuán)隊 有足夠動力去治理、改造這個系統(tǒng)中的所有組件
    直接改造存量系統(tǒng)的收益大于新老系統(tǒng)混雜帶來的問題。
  3. 系統(tǒng)及組織架構(gòu)上,各部件之間本身就是強(qiáng)耦合、自洽、不可分離的
    系統(tǒng)本身就是一個最小單元的「架構(gòu)量子」,拆分的成本高于治理的成本。
  4. 極高的產(chǎn)品體驗要求,對任何產(chǎn)品交互上的不一致零容忍
    不允許交互上不一致的情況出現(xiàn),這基本上從產(chǎn)品上否決了漸進(jìn)式升級的技術(shù)策略

文章參考:
《對比多種微前端方案》
MicroFrontends

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,743評論 2 370

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