微前端那些事

微前端出如今咱們的視線的次數愈來愈多,由於to B 的發展愈來愈迅猛,致使中後臺應用需求激增,如何將多項目集合成一個web主體就成爲一個問題,固然也有很多童鞋們還會有疑惑🤔,究竟微前端是什麼東西呢?css

1.什麼是微前端

微前端本質是是一種項目架構方案,是爲了解決前端項目太過龐大,致使項目管理維護難、團隊協做亂、升級迭代困難、技術棧不統一等等問題,有點相似微服務的概念,是將微服務理念擴展到前端開發的一種應用,講到這裏你可能仍是一臉懵逼~,咱們接着講html

舉個例子:七某雲平臺前端

本質上應該就是一個微前端應用,左側的菜單就是各個子應用的入口,切換菜單的同時就是在切換子應用,而整個主容器就是一個portal門戶(可能包含用戶登陸機制 、菜單權限獲取 、 全局異常處理等)vue

2.微前端的落地方式

微前端它將微服務的理念應用於瀏覽器端,即將 Web 應用由單一的單體應用拆分爲爲多個小型前端應用匯集爲一體的應用。react

2.1 iFrame

iFrame 是微前端集成的最簡單方式之一。能夠說iFrame 裏的頁面是徹底獨立的,並且iFrame 頁面中的靜態資源(js、css)都是相互隔離的,互相不干擾,至關於一個獨立的環境,具有沙箱隔離,可讓前端應用之間能夠相互獨立運行webpack

//經過切換url來切換不一樣業務項目

 <iframe v-show="url" frameborder="0" id="contentIframe"></iframe>
 createFrame(url) {
      const iframe = document.getElementById('contentIframe');
      const deviceWidth = document.body.clientWidth;
      // const deviceHeight = document.body.clientHeight;
      iframe.style.width = `${Number(deviceWidth) - 10}px`;
      iframe.style.height = `${800}px`;
      iframe.src = url;
}

複製代碼

固然iFrame 也有侷限性👇:ios

  • 子項目需調整,須要隱藏自身頁面中的導航(公共區域)
  • iFrame嵌入的視圖控制難,有侷限性
  • 刷新沒法保存記錄,也就意味着當瀏覽器刷新狀態將消失,後退返回無效
  • iframe 阻塞主頁面加載

2.2 路由分發方式

路由分發是指經過路由將不一樣業務拆分的子項目,結合反向代理的方式實現nginx

路由分發方式也是比較簡單的一種實現微前端的方式,將多個子項目聚合成一體,能夠經過ngxin來配置不一樣路由的轉發代理,以下git

http {
  server {
    listen       80;
    server_name  192.168.0.1
    location /web/monitor {
      proxy_pass http://192.168.0.2/web/monitor;
    }
    location /web/admin {
      proxy_pass http://192.168.0.3/web/admin;
    }
    location / {
      proxy_pass /;
    }
  }
}
複製代碼

經過不一樣的路由請求,轉發到不一樣的項目域名服務器下,這種方式好處在於團隊協做方便、框架無關、項目獨立部署維護github

固然路由分發也有侷限性:

  • web應用之間的複用性差
  • 每一個獨立的項目之間切換,須要從新加載,容易出現白屏影響用戶體驗

2.3 Single-SPA

官方號稱「一個用於前端微服務化的JavaScript前端解決方案」,single-spa 聽起來很高大上,它能兼容各類技術棧,而且在同一個頁面中可使用多種技術框架(React, Vue, Angular等任意技術框架),不用考慮因新的技術框架而去重構舊項目的代碼,官方文檔🚀

大概的原理是,首先須要一個主應用(容器應用),須要先註冊子應用,而後當url匹配到相應的子應用路由後,將會先請求子應用的資源,而後掛載子應用,同理,當url切換出該子應用路由時,將卸載該應用,以此達到切換子應用的效果,經過子應用生命週期boostrap(獲取輸出的資源文件) 、 mount、unmount的交替

聊聊Single-SPA 的優勢:

  • 各項目獨立開發、部署、迭代,互不影響效率高
  • 開發團隊能夠選擇本身的技術並及時更新技術棧。
  • 相互之間的依賴性大大下降
  • 有利於CI/CD,更快的交付產品

因爲要把時間留給終極大boss(乾坤qiankun-螞蟻金服微前端框架),Single-SPA的實踐在這裏不作大篇幅介紹,有興趣的童鞋能夠看下面幾篇文章

前端微服務化解決方案2 - Single-SPA

2.4 qiankun (螞蟻金服微前端框架)

qiankun 是一個基於 single-spa 的微前端實現庫,旨在幫助你們能更簡單、無痛的構建一個生產可用微前端架構系統。官方文檔🚀

qiankun這名字起得溜啊,本質上就是在上一節提到得Single-SPA上作一些封裝,讓咱們前端開發者用得更上手

  • 主應用下安裝 qiankun
yarn add qiankun
複製代碼
  • 如何在主應用中註冊子應用

官方文檔介紹得是react的方式,而樹醬是基於vue開發的,因此這裏介紹下vue的方式,本質上就是註冊子項目應用(按需加載子項目編譯好的靜態資源),當子應用加載完以後,瀏覽器的 url 發生變化時,便會自動觸發 qiankun 的路由匹配邏輯,去執行子應用的生命週期函數,如下是具體實現👇,有點長當心頭頂

// main.js 入口文件修改
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import axios from 'axios';
import api from "./service";
import ViewUI from 'view-design';
import cacheKeys from '@/const/cacheKey';
import globalMixins from '@/mixin/global';
import Bus from '@/utils/bus';
import 'view-design/dist/styles/iview.css';
import './theme/customTheme.less';
import '@/icons';

Vue.config.productionTip = false;
// 導入乾坤函數
import {
  registerMicroApps,
  runAfterFirstMounted,
  setDefaultMountApp,
  start
} from "qiankun";

// 導入路由監聽函數
import { genActiveRule } from "./utils";
// 導入主應用工具類庫
import LibraryJs from "./library/js";
// 導入主應用須要下發的emit函數
import * as childEmit from "./utils/childEmit"
// 定義傳入子應用的數據
Vue.mixin(globalMixins);
Vue.use(ViewUI);
Vue.use(api);
Vue.use(Bus);
Vue.prototype.$axios = axios;
Vue.prototype.$cacheKeys = cacheKeys;
Vue.config.productionTip = false;

// 定義傳入子應用的數據
let msg = {
  data: store,         // 從主應用倉庫讀出的數據
  // components: LibraryUi,       // 從主應用讀出的組件庫
  utils: LibraryJs,            // 從主應用讀出的工具類庫
  emitFnc: childEmit,           // 從主應用下發emit函數來收集子應用反饋
  prototype: [
    {name: '$axios', value: axios },
    {name: 'isQiankun', value: true },//是否qiankun啓用
  ]
};

// 主應用渲染函數
let app = null;
function render({ appContent, loading } = {}) {
  if (!app) {
    app = new Vue({
      el: "#container",
      router,
      store,
      data() {
        return {
          content: appContent,
          loading
        };
      },
      render(h) {
        return h(App, {
          props: {
            content: this.content,
            loading: this.loading
          }
        });
      }
    });
  } else {
    app.content = appContent;
    app.loading = loading;
  }
  window.vue = app;
};
render();

//註冊子應用
registerMicroApps(
  [
    {
      name: "monitor",
      entry: "http://10.0.0.110:8081/",
      render,
      activeRule: genActiveRule("/monitor"),
      props: msg
    },
    {
      name: "portalAdmin",
      entry: "http://183.62.46.202:8082/",
      render,
      activeRule: genActiveRule("/admin"),
      props: msg
    }
  ],
  {
    beforeLoad: [
      app => {
        console.log("before load", app);
      }
    ],
    beforeMount: [
      app => {
        console.log("before mount", app);
      }
    ],
    afterUnmount: [
      app => {
        console.log("after unload", app);
      }
    ]
  },
);

// 設置默認子應用
setDefaultMountApp("/portal");
// 第一個子應用加載完畢回調
runAfterFirstMounted(() => {
  // console.log(app)
});
// 啓動微服務
start({ prefetch: true });

/* new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app"); */

複製代碼
  • 子應用適配

子應用不須要額外安裝任何其餘依賴便可接入 qiankun 主應用,只需向主應用暴露相應的生命週期鉤子

import Vue from 'vue';
import VueRouter from 'vue-router';
import routes from './router';
import './public-path';


// 聲明變量管理vue及路由實例
let router = null;
let instance = null;

// 導出子應用生命週期 掛載前
export async function bootstrap(props = {}) {
  Vue.prototype.isQiankun = props.isQiankun;
}

// 導出子應用生命週期 掛載前 掛載後
export async function mount({data = {}} = {}) {
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/portal' : '/',
    mode: "history",
    routes
  });
  window.vue = instance = new Vue({
    router,
    store,
    render: h => h(App, {props: data})
  }).$mount("#app");
};

// 導出子應用生命週期 掛載前 卸載後
export async function unmount() {
  instance.$destroy();
  instance = null;
  router = null;
}

// 單獨開發環境
window.__POWERED_BY_QIANKUN__ || mount();

複製代碼

還需main.js 中引入 public-path.js 文件

// public-path.js 
if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
複製代碼

配置好生命週期鉤子函數後,爲了讓主應用能獲取子應用暴露的資源文件,子應用的打包工具須要增長以下配置:

// vue.config.js

module.exports = {
  resolve: {
            alias: {
                '@': resolve('src'),
            },
    },
   configureWebpack: {
    output: {
      library: `${name}-[name]`,
      filename: '[name].js',
      libraryTarget: 'umd',
      globalObject: 'this',
    },
  },
}
複製代碼

以上即完成了子父應用的微前端適配,過程當中會有可能會遇到一些奇奇怪怪的問題,能夠查看官方的見問題 以及 github 上面的 issue

完成以上步驟,當你想部署到測試環境或者生產環境的時候,還須要配置nginx。

首先先聊聊子應用的nginx配置

events {
    worker_connections  1024;
}
http{
    server {
        listen       80;
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Headers X-Requested-With;
        add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
        location / {
            try_files $uri $uri/ /index.html;
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }
    }
}
複製代碼
  • vue路由模式 :子項目的路由模式爲history,所以須要再nginx配置try_files $uri $uri/ /index.html; 不然當你刷新路由時會報404 vue 官方文檔
  • 資源跨域問題:須要設置容許訪問的來源,若是沒有配置,主應用將由於跨域沒法正常獲取子應用的資源(js、css)

配置完子應用,主應用的ngnix也不能漏

http{
    server {
        listen       80;
        location / {
            try_files $uri $uri/ /index.html;
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }
        location /monitor {
           try_files $uri $uri/ /index.html;
           proxy_pass http://10.0.0.110:8081;
        }
        location /admin {
            try_files $uri $uri/ /index.html;
            proxy_pass http://183.62.46.202:8082;
         }
    }
}

複製代碼

大功告成

3 總結

3.1 一些優質的微前端文章分享

多是你見過最完善的微前端解決方案

微前端架構選型指南

每日優鮮供應鏈前端團隊微前端改造

alili.tech 微前端

Micro-frontend Architecture in Action-微前端的那些事兒

歡迎指出問題

相關文章
相關標籤/搜索