微前端出如今咱們的視線的次數愈來愈多,由於to B 的發展愈來愈迅猛,致使中後臺應用需求激增,如何將多項目集合成一個web主體就成爲一個問題,固然也有很多童鞋們還會有疑惑🤔,究竟微前端是什麼東西呢?css
微前端本質是是一種項目架構方案,是爲了解決前端項目太過龐大,致使項目管理維護難、團隊協做亂、升級迭代困難、技術棧不統一等等問題,有點相似微服務的概念,是將微服務理念擴展到前端開發的一種應用,講到這裏你可能仍是一臉懵逼~,咱們接着講html
舉個例子:七某雲平臺前端
本質上應該就是一個微前端應用,左側的菜單就是各個子應用的入口,切換菜單的同時就是在切換子應用,而整個主容器就是一個portal門戶(可能包含用戶登陸機制 、菜單權限獲取 、 全局異常處理等)vue
微前端它將微服務的理念應用於瀏覽器端,即將 Web 應用由單一的單體應用拆分爲爲多個小型前端應用匯集爲一體的應用。react
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
路由分發是指經過路由將不一樣業務拆分的子項目,結合反向代理的方式實現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
固然路由分發也有侷限性:
官方號稱「一個用於前端微服務化的JavaScript前端解決方案」,single-spa 聽起來很高大上,它能兼容各類技術棧,而且在同一個頁面中可使用多種技術框架(React, Vue, Angular等任意技術框架),不用考慮因新的技術框架而去重構舊項目的代碼,官方文檔🚀
大概的原理是,首先須要一個主應用(容器應用),須要先註冊子應用,而後當url匹配到相應的子應用路由後,將會先請求子應用的資源,而後掛載子應用,同理,當url切換出該子應用路由時,將卸載該應用,以此達到切換子應用的效果,經過子應用生命週期boostrap(獲取輸出的資源文件) 、 mount、unmount的交替
聊聊Single-SPA 的優勢:
因爲要把時間留給終極大boss(乾坤qiankun-螞蟻金服微前端框架),Single-SPA的實踐在這裏不作大篇幅介紹,有興趣的童鞋能夠看下面幾篇文章
qiankun 是一個基於 single-spa 的微前端實現庫,旨在幫助你們能更簡單、無痛的構建一個生產可用微前端架構系統。官方文檔🚀
qiankun這名字起得溜啊,本質上就是在上一節提到得Single-SPA上作一些封裝,讓咱們前端開發者用得更上手
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;
}
}
}
複製代碼
try_files $uri $uri/ /index.html;
不然當你刷新路由時會報404 vue 官方文檔配置完子應用,主應用的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;
}
}
}
複製代碼
大功告成
Micro-frontend Architecture in Action-微前端的那些事兒
歡迎指出問題