在前端,每每由一個前端團隊建立並維護一個 Web 應用程序,使用 REST API 從後端服務獲取數據。這種方式若是作得好的話,它可以提供優秀的用戶體驗。但主要的缺點是單頁面應用(SPA)不能很好地擴展和部署。中後臺應用因爲其應用生命週期長 (動輒 3+ 年) ,因爲參與的人員、團隊的增多、變遷,等特色從一個普通應用演變成一個巨石應用 ( Frontend Monolith ) 後,隨之而來的應用不可維護的問題。這類問題在企業級 Web 應用中尤爲常見。javascript
在一個大公司裏,單前端團隊可能成爲一個發展瓶頸。前端
首先,必須先了解什麼是微前端架構。 微前端架構是一種相似於微服務的架構,它將微服務的理念應用於瀏覽器端,即將 Web 應用由單一的單體應用轉變爲多個小型前端應用聚合爲一的應用。vue
single spa是一個javascript庫,容許許多小應用程序在一個頁面應用程序中共存。這個https://single-spa.surge.sh/ 網站是一個演示應用程序,展現了什麼單一應用。spa的理念是讓獨立的、獨立的應用程序組成一個完整的頁面。單spa並無長期依賴於單個框架和每一個特性,而是幫助您在開發新框架時採用它們。 簡單來講就是一個萬能的粘合劑,使用這個庫可讓你的應用可使用多個不一樣的技術棧(vue、react、angular等等)進行同步開發,最後使用一個公用的路由便可實現完美切換。固然了,也可使用同樣的技術棧,分不一樣的團隊進行開發,只須要最後使用這個庫將其整合在一塊兒,設置不用的路由名稱便可。java
修改app.vue 文件 添加node
<div id=「vue」></div>
容器react
引入安裝包文件webpack
npm install single-spa --save –d
ios
Main.js 引入配置文件git
import './single-spa-config.js’
github
引入裝包文件
npm install single-spa-vue --save -d
首先了解一下singleSpa 主要的API
registerApplication is the most important api your root config will use. Use this function to register any application within single-spa. --- 註冊子項目的方法
appName: 子項目名稱
applicationOrLoadingFn: 子項目註冊函數,用戶須要返回 single-spa 的生命週期對象。後面咱們會介紹single-spa的生命週期機制
activityFn: 回調函數入參 location 對象,能夠寫自定義匹配路由加載規則。 調用方法
singleSpa.registerApplication('appName’,
() => System.import('appName’),
location => location.pathname.startsWith('appName’)
)
複製代碼
修改父容器的single-spa-config 文件
// single-spa-config.js
import * as singleSpa from 'single-spa'; //導入single-spa
import axios from 'axios’
/*runScript:一個promise同步方法。能夠代替建立一個script標籤,而後加載服務*/
const runScript = async (url) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
const firstScript = document.getElementsByTagName('script')[0];
firstScript.parentNode.insertBefore(script, firstScript);
});
};
singleSpa.registerApplication( //註冊微前端服務
'singleDemo',
async () => {
await runScript('http://127.0.0.1:3000/js/chunk-vendors.js');
await runScript('http://127.0.0.1:3000/js/app.js');
return window.singleVue;
},
location => location.pathname.startsWith('/vue-antd') // 配置微前端模塊前綴
);
singleSpa.start(); // 啓動
複製代碼
修改父項目的main.js
import Vue from 'vue'
import App from './App.vue'
import singleSpaVue from "single-spa-vue"
const vueOptions = {
el: "#vue",
render: h => h(App),
}
// new Vue().$mount('#app')
// singleSpaVue包裝一個vue微前端服務對象
const vueLifecycles = singleSpaVue({
Vue,
appOptions: vueOptions
});
// 導出生命週期對象
export const bootstrap = vueLifecycles.bootstrap; // 啓動時
export const mount = vueLifecycles.mount; // 掛載時
export const unmount = vueLifecycles.unmount; // 卸載時
export default vueLifecycles;
複製代碼
生命週期函數共有4個:bootstrap、mount、unmount、update。生命週期能夠傳入 返回Promise的函數也能夠傳入 返回Promise函數的數組。
export default {
// app啓動
Bootstrap: [() => Promise.resolve()],
// app掛載
Mount: [() => Promise.resolve()],
// app卸載
Unmount: [() => Promise.resolve()],
// service更新,只有service纔可用
update: [() => Promise.resolve()]
}
複製代碼
在上面父項目加載子項目的代碼中,咱們能夠看到。咱們要註冊一個子服務,須要一次性加載2個JS文件。若是須要加載的JS更多,甚至生產環境的 bundle 有惟一hash, 那咱們還能寫死文件名和列表嗎?咱們的實現思路,就是讓子項目使用 stats-webpack-plugin 插件,每次打包後都輸出一個 只包含重要信息的manifest.json文件。父項目先ajax 請求 這個json文件,從中讀取出須要加載的js目錄,而後同步加載。
子項目添加vue.config.js ,並進行如下修改,生成manifest.json 文件
const StatsPlugin = require('stats-webpack-plugin');
module.exports = {
….
output: {
library: "singleVue", // 導出名稱
libraryTarget: "window", //掛載目標
},
/**** 添加開頭 ****/
plugins: [
new StatsPlugin('manifest.json', {
chunkModules: false,
entrypoints: true,
source: false,
chunks: false,
modules: false,
assets: false,
children: false,
exclude: [/node_modules/]
})
]
/**** 添加結尾 ****/
….
};
複製代碼
固然,父項目中的單runScript已經沒法支持使用了,寫個getManifest方法,處理一下。
const getManifest = (url, bundle) => new Promise(async (resolve) => {
const { data } = await axios.get(url);
// eslint-disable-next-line no-console
const { entrypoints, publicPath } = data;
const assets = entrypoints[bundle].assets;
for (let i = 0; i < assets.length; i++) {
await runScript(publicPath + assets[i]).then(() => {
if (i === assets.length - 1) {
resolve()
}
})
}
});
複製代碼
咱們首先ajax到 manifest.json 文件,解構出裏面的 entrypoints publicPath字段,遍歷出真實的js路徑,而後按照順序加載。
async () => {
let singleVue = null;
await getManifest('http://127.0.0.1:3000/manifest.json', 'app').then(() => {
singleVue = window.singleVue;
});
return singleVue;
},
複製代碼
Vue-child 引入 ant UI Vue-child-two 引入 element
一樣可使用多個不一樣的技術棧(vue、react、angular等等)進行同步開發
Single-Spa + Vue Cli 微前端落地指南 (項目隔離遠程加載,自動引入)
項目完整地址:github.com/zhaiyy/sing…