微前端在vue二、vue3中的實戰開發

爲了業務在原有系統中,集成新項目,咱們常常會使用qiankun這套微前端解決方案,來解決集成訴求。javascript

vue2,vue3在qiankun中的使用實際差異不大,主要路由處理上有些區別,也會在文中指出,會以【vue3】做爲標識。html

經過這邊文章,能夠了解到,如何在vue(2/3)技術棧裏使用微前端集成新老項目,主要內容包括:前端

  1. 不一樣的路由模式(history, hash),主、子應用分別在開發、生產環境的代碼改造、部署;
  2. 在進入子應用前,如何處理鑑權邏輯;
  3. 主、子應用樣式隔離的處理;
  4. 主、子應用間的通訊;
  5. 在應用某個路由下加載微應用;
  6. QA,開發過程當中遇到的問題彙總。

經常使用API

下面是經常使用的兩個qiankun API,已給出連接,能夠在官網查看具體用法:vue

registerMicroApps: 主應用註冊微前端應用java

start: 啓動微前端應用webpack

一、 不一樣的路由模式,主、子應用分別在開發、生產環境的代碼改造、部署;

a. 主、子應用路由都爲hash模式的改造

開發環境,主應用改造:

import { registerMicroApps, start } from 'qiankun';
registerMicroApps(
  [
    {
      // 微前端應用名
      name: 'app1',
      
      // 微前端啓動地址
      entry: '//localhost:8100/',
      
      // 微前端掛載dom
      container: '#app',
      
      // 微前端觸發路由
      activeRule: '#/app1',
      
      // 主應用向子應用傳遞的靜態值
      props: {
        name: 'yuxiaoyu',
      },
    },
  ],
);

start();
複製代碼

開發環境,子應用改造:

// 入口文件main.js
// 子應用並不用引入qiankun,只要暴露響應的聲明週期鉤子給主應用使用就ok
// 掛載實例
function render(props: any = {}) {
  const { container } = props;
  app = createApp(App);
  app.use(router);
  app.use(store);
  router.isReady().then(() => {
    app.mount(container ? container.querySelector('#container') : '#container');
  });
}

// 微應用在主應用運行時,主應用會在微應用中掛載window.__POWERED_BY_QIANKUN__,能夠用於判斷環境
// 官方提供了下面這個webpack注入publicPath的方法, 開發環境咱們這麼使用,生產改到vue.config.js中,後面再介紹。
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

// 若是是獨立運行 window.__POWERED_BY_QIANKUN__=undefined 直接render
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

// 最後暴露的三個方法是固定的,加載渲染以及銷燬
export async function bootstrap() { }

export async function mount(props: any) {
  render(props);
}
export async function unmount() {
  app.unmount();
  app._container.innerHTML = "";
  app = null;
  
  // 這裏reload的緣由: 由於在項目中,微應用和主應用沒有共用導航等信息
  // 至關於兩個獨立的頁面,因此就共用了<div id="app">
  // 在卸載微應用後,爲了再把主應用渲染出來,就從新reload了一遍。
  location.reload();
}
複製代碼
// router改造
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

// 由於主應用在激活子應用時,有一個activeRule前綴,因此在hash模式下,咱們須要給每一個一級路由都添加activeRule的前綴,主應用爲'#/app1',那麼子應用前咱們就加'/app1'就能夠了。
export default [
  {
    path: '/app1/fujidaohang',
    redirect: '/app1/fujidaohang/zijidaohang',
    component: BothLayout,
    name: 'fujidaohang',
    meta: {
      title: '父級導航',
      navPosition: 'top',
    },
    children: [
      {
        path: 'zijidaohang',
        component: () => import('@/apps/fujidaohang/views/zijidaohang.vue'),
        name: 'zijidaohang',
        meta: {
          title: '子級導航',
          navPosition: '',
        },
      },
    ],
  },
  {
    path: '/app1/course',
    component: Course,
    name: 'course',
    children: [],
  }
];
複製代碼
// vue.config.js改造
const packageName = require('./package.json').name;

module.exports = {
  ...
  
  // 用於主應用識別子應用,固定寫法
  configureWebpack: {
    output: {
      library: 'app1',
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${packageName}`,
    },
  }
}
複製代碼

生產構建部署:

構建部署能夠選擇兩種方式:web

同域名、不一樣域名部署。vue-router

同域名部署:

1.能夠考慮將子應用打包放在static路徑下,做爲主應用的靜態資源引入。這種處理方式的適用場景包含:子應用不須要獨立訪問、子應用不須要獨立部署。這種方式的好處在於,不用運維配合去Nginx配置,本地打包,並使用原有的部署方式。固然,它同時也失去了微前端,主、子獨立部署的優勢。vue-cli

做爲靜態資源引入.png

2.還有一種方式,能夠將打包後的靜態資源在同一臺服務器進行部署,而後針對於項目新增部署和Nginx相關配置。在主應用訪問的時候能夠經過域名加載,也能夠經過相對路徑進行加載(由於咱們同臺機器部署)。這個相對於上面的方案,增長了運維成本,但保留了主子獨立發佈的特性。json

不用域名部署

這裏的方案和上面的2差很少,區別在於資源可能放在不一樣的服務器上,因此只能經過域名進行資源的加載(也就是registerMicroApps中配置的entry須要是域名,不能是相對路徑了,和開發環境差很少),記得處理跨域。

固然也能夠根據須要選用相應的部署方式,官方都有給出詳細的介紹: 如何部署

生產構建,主應用改造:

// main.js
// 註冊微應用
registerMicroApps(
  [
    {
      name: 'app1',
      
      // 路徑改成部署後,微應用要存放在主應用的目錄,其他不變
      entry: '/static/index.html',
      container: '#app',
      activeRule: '#/app1',
      props: {
        name: 'kuitos',
      },
    },
  ],
);
複製代碼

生產構建,微應用改造:

// main.js
// 去掉qiankun的__webpack_public_path__注入
// if (window.__POWERED_BY_QIANKUN__) {
// __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
// }

// vue.config.js
module.exports = {
  ...
  
  // 這裏新增打包資源存放路徑,與上面主應用相對應
  publicPath: '/static'
}
複製代碼

b. 主、子應用路由都爲history模式的改造

開發模式:

這裏咱們和hash-dev模式進行對比,只列舉差別的部分。

// 主應用
// main.js, 註冊微應用有變化
registerMicroApps(
  [
    {
      name: 'app1',
      entry: '//localhost:8101',
      container: '#app',
      
      // 改變點:激活路由由hash模式變爲history模式
      activeRule: '/app1',
      props: {
        name: 'yuxiaoyu',
      },
    },
  ],
);

// router.js 

const router = new VueRouter({

  // 由hash改成history模式
  mode: 'history',
  base: process.env.BASE_URL,
  routes,
});
複製代碼
// 子應用
// router/index.js
// 【vue3】由於子應用爲vue3版本,這裏是vue2和vue3自己路由處理上的區別,咱們只是使用了vue3的寫法加上了須要的前綴app1
const router = createRouter({
  
  // 改成history模式,而且加activeRule前綴
  history: createWebHistory('/app1'),
  routes,
});

// router.js

// 去掉這裏的app1前綴
export default [
  {
    path: '/fujidaohang',
    redirect: '/fujidaohang/zijidaohang',
    component: BothLayout,
    name: 'fujidaohang',
    meta: {
      title: '父級導航',
      navPosition: 'top',
    },
    children: [
      {
        path: 'zijidaohang',
        component: () => import('@/apps/fujidaohang/views/zijidaohang.vue'),
        name: 'zijidaohang',
        meta: {
          title: '子級導航',
          navPosition: '',
        },
      },
    ],
  },
  {
    path: '/course',
    component: Course,
    name: 'course',
    children: [],
  }
];
複製代碼

生產構建部署:

這裏與hash模式改動點是一致的,只描述不一樣點。

主應用改造:

// main.js
// 註冊微應用
registerMicroApps(
  [
    {
      // 路徑改成部署後,微應用要存放在主應用的目錄,其他不變
      entry: '/static/index.html',
    },
  ],
);
複製代碼

微應用改造:

// main.js
// 去掉qiankun的__webpack_public_path__注入
// if (window.__POWERED_BY_QIANKUN__) {
// __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
// }

// vue.config.js
module.exports = {
  ...
  
  // 這裏新增打包資源存放路徑,與上面主應用相對應
  publicPath: '/static'
}
複製代碼

二、在進入子應用前,如何處理鑑權邏輯

當進入微應用時,咱們鑑定微應用是否登陸時,咱們能夠考慮在微應用作鑑權,也能夠在主應用作鑑權。

這裏介紹一種在主應用內鑑權的方案。

qiankun裏在註冊微應用時,registerMicroApps提供了第二個參數,lifeCycles- 可選,全局的微應用生命週期鉤子

registerMicroApps(
    [{...}], {
    
    // 這裏採用在進入微前端以前進行鑑權,確保進入微前端時,已經登陸。
    beforeLoad: [
      () => {
        if (!auth.check()) {
            vm.$router.push(loginPath);
        }
      },
    ],
  },);
複製代碼

三、主、子應用樣式隔離的處理

a. 主、子應用都使用antd

這裏官方提供了修改antd前綴的方法,如將ant改成dida-ant, 如何確保主應用跟微應用之間的樣式隔離

這裏ant-design-vue文檔裏雖然沒有提供prefixCls的參數,可是可使用的,源碼裏相應的處理。

b. 主應用自定義樣式與子應用衝突

這裏咱們能夠採用官方提供的start(options?),將微應用放入瀏覽器所支持的shadow dom中。

start({
  sandbox: {
    // 主應用 & 子應用樣式隔離
    strictStyleIsolation: true, // 放入shadow dom中
  }
});
複製代碼

image.png

這裏咱們能夠看到微前端被放入了shadow-root裏,對於shadow dom能夠經過這裏進行了解。

這樣隔離後,在咱們使用ant design這種外部庫時會有一些問題,例如popup組件,本來實現是掛在document.body中的,咱們將子應用放到了shadow dom中,那就須要將popup也掛進去。ant design官方提供了方法,搜索getPopupContainer;

四、 主、子應用間的通訊

a. 靜態值傳遞

咱們能夠經過註冊微應用時,經過props參數進行數據的傳遞,在mountedrender生命週期中,能夠拿到props數據。

registerMicroApps(
  [
    {
      name: 'app1',
      entry: '//localhost:8100',
      container: '#app',
      activeRule: '/app1',
      props: {
        id: 1,
      },
    },
  ],
);
複製代碼

b. 通訊機制

qiankun官方提供了建立主、子應用通訊的定義方法。initGlobalState(state)文檔連接,初始化後會返回三個方法,onGlobalStateChangesetGlobalStateoffGlobalStateChange,分別是監聽,設置和移除。

// 主應用
// 初始化 state
// 調用initGlobalState後,會掛在props中進行傳遞
const initialState = {
  userInfo: {}, // 用戶信息
};
const actions = initGlobalState(initialState);
actions.onGlobalStateChange((newState, oldState) => {

  // newState: 變動後的狀態; oldState 變動前的狀態
  console.log('mainapp: global state changed', newState, oldState);
});
actions.setGlobalState({
  userInfo: {
    name: 'Zhangsan',
  },
});
actions.offGlobalStateChange();
複製代碼
export async function mount(props: any) {
  render(props);
  
  // props 會注入onGlobalStateChange、setGlobalState方法。
  console.log('props :>> ', props);

  app.config.globalProperties.$onGlobalStateChange =props.onGlobalStateChange;
  app.config.globalProperties.$setGlobalState = props.setGlobalState;
  props.onGlobalStateChange((newState: any, oldState: any) => {

    // newState: 變動後的狀態; oldState 變動前的狀態
    console.log('microapp: global state changed', newState, oldState);
  });
  
  window.document.addEventListener('click', () => {
    props.setGlobalState({
      userInfo: {
        name: Math.random(),
      },
    });
  });
}
複製代碼

五、在應用某個路由下加載微應用

背景:

vue3做爲主應用, 原系統做爲子應用(vue2)。也就是vue3(主)接vue2(子)。在已有路由或組件中引入子應用。 如圖:

172*36.png

須要將子應用嵌套在layout組件內

官方提供了一種在應用某個路由下面接入方法

官方提供的方法是在vue2爲主應用時接入。因此這裏寫法上有會一些區別。

{
  path: '/huodongguanli',
  redirect: '/trace_micro/project/projectList',
  component: SideLayout,
  name: 'huodongguanli',
  meta: {
    title: '活動管理',
    navPosition: 'side',
    icon: () => {
      return h(ContactsOutlined);
    },
  },
  children: [
    {
      path: '/trace_micro/project/projectList', // 須要嵌套在二級菜單,且路徑不須要一級的path拼接的,這裏寫絕對路徑(這裏爲業務邏輯,不用關注)
      component: { default: '' }, // 由於這裏只是爲了聲明一個menu項,並不須要組件,(組件內容在子應用中),這裏給個含有default的空對象
      name: 'lianluliebiao',
      meta: {
        title: '鏈路列表',
        navPosition: 'side',
      },
    },
    {
      path: 'chuangjianjiangzuo', // 主應用中的路由 正常寫就能夠
      name: 'chuangjianjiangzuo',
      meta: {
        title: '建立講座',
        navPosition: 'side',
      },
    },
  ],
},

// 針對於不用在主應用裏菜單顯示出來的路由,都進行模糊匹配
// 【vue3】
// 由於vue3中,vue-router4.x[取消了通配符的寫法](https://next.router.vuejs.org/zh/guide/migration/index.html#%E5%88%A0%E9%99%A4%E4%BA%86-%EF%BC%88%E6%98%9F%E6%A0%87%E6%88%96%E9%80%9A%E9%85%8D%E7%AC%A6%EF%BC%89%E8%B7%AF%E7%94%B1),因此在匹配的`path`的寫法上會有一些區別:
{
  path: '/trace_micro/:pathMatch(.*)*', // 【重要】這裏咱們採用router4.x中提供的寫法去匹配子應用的路由
  hidden: true,
  component: SideLayout,
  name: 'projectOperate',
  meta: {
    title: '建立鏈路',
    navPosition: 'side',
  },
}
複製代碼

其他的使用方法(start掛載節點等)與官方提供的一致。

六、常見問題

官方有提供常見問題文檔,遇到問題時,能夠先去這裏查看一下是否有相應的匹配解決方案。

這裏也列舉幾個,我在接入項目時候遇到的其它的坑:

一、與主應用共用同一節點,如#app。在返回主應用時,主應用不顯示。

這裏由於咱們在初始化微前端時,把#app中的內容替換成了子應用的數據,在返回時,並無從新掛載,目前能夠經過reload簡單處理。

二、與主應用共用同一節點,如#app。dom看起來掛載成功了,可是樣式都沒有。

這個和第一個又是不一樣的狀況,主要是由於咱們把子應用的dom結構直接掛載到了主應用上,微前端這一套都沒有生效。在mount生命週期中,咱們能夠拿到props,這裏面掛載了一個container屬性,就是掛載微前端的dom節點,能夠經過props.container.querySelector('#app')去掛載子應用的vue

三、子應用跳轉主應用,$router.push不生效

是由於咱們主、子應用使用的router並非一個實例,註冊的路由也不同,因此子應用並不能匹配到主應用的路由,可使用history.push或者location.href進行跳轉。

四、在打開主應用打開子應用時,會報js加載不到,或者<等語法不對的問題

通常因爲主應用引入子應用的路徑不對,檢查路徑是否能引到子應用資源。

五、主應用引入babel-polyfill時,有時候會babel-polyfill引入兩次的問題。

這個先檢查一下是否子應用也單獨引入babel-polyfill了,若是引入了就都統一放到一處去引用。

如今大部分新的應用都是用vue-cli進行生成,並不會單獨引入babel-polyfill,那若是報這個錯了,就檢查一下是否是,子應用的路徑有問題,引入的資源不對。報了引兩次的問題,可能也是因爲子應用沒有引到的問題。和babel-polyfill自己引入沒有關係。

六、關於樣式隔離

上面也有給出樣式隔離的方案,但方案自己仍是有一些問題的。好比咱們提到的popup掛在document.body上的問題,須要處理。可能還會有一些其它的問題要處理。

最簡單有效的方法仍是在主、子應用中,儘可能使用style scoped進行組件級別的樣式隔離。對於使用同一組件庫,不一樣配置,能夠經過修改組件、樣式前綴解決。

以上就是微前端(qiankun)在vue二、3中的實戰開發,但願對各位有所幫助。

相關文章
相關標籤/搜索