微前端 從 0到 1搭建
微前端
微前端
Single-SPA
?微服務(wù)是面向服務(wù)架構(gòu)(SOA)的一種變體,把應(yīng)用程序設(shè)計(jì)成一系列松耦合的細(xì)粒度服務(wù),并通過(guò)輕量級(jí)的通信協(xié)議組織起來(lái) 具體地,將應(yīng)用構(gòu)建成一組小型服務(wù)。這些服務(wù)都能夠獨(dú)立部署、獨(dú)立擴(kuò)展,每個(gè)服務(wù)都具有穩(wěn)固的模塊邊界,甚至允許使用不同的編程語(yǔ)言來(lái)編寫不同服務(wù),也可以由不同的團(tuán)隊(duì)來(lái)管理
?
概念
官網(wǎng) :
2018年 Single-SPA誕生了, single-spa是一個(gè)用于前端微服務(wù)化的JavaScript前端解決方案 ?(本身沒(méi)有處理樣式隔離、js執(zhí)行隔離) ?實(shí)現(xiàn)了路由劫持和應(yīng)用加載;
Alibaba -
springboot
- sofaboot
Single-SPA 搞了個(gè)入口 ?--> ?qiankun
2019年 qiankun基于Single-SPA, 提供了更加開(kāi)箱即用的 API ?(single-spa + sandbox + import-html-entry),它 做到了技術(shù)棧無(wú)關(guān),并且接入簡(jiǎn)單(有多簡(jiǎn)單呢,像iframe一樣簡(jiǎn)單)。
總結(jié):子應(yīng)用可以獨(dú)立構(gòu)建,運(yùn)行時(shí)動(dòng)態(tài)加載,主子應(yīng)用完全解耦,并且技術(shù)棧無(wú)關(guān),靠的是協(xié)議接入(這里提前強(qiáng)調(diào)一下:子應(yīng)用必須導(dǎo)出 bootstrap、mount、unmount三個(gè)方法)。
micro front ends single spot
應(yīng)用量龐大,
實(shí)現(xiàn)上,關(guān)鍵問(wèn)題在于:
多個(gè) Bundle 如何集成?
子應(yīng)用之間怎樣隔離影響?
公共資源如何復(fù)用?
子應(yīng)用間怎樣通信?
如何測(cè)試?
當(dāng)然,這種架構(gòu)模式并非百益而無(wú)一害,一些問(wèn)題也隨之而來(lái):
導(dǎo)致依賴項(xiàng)冗余,增加用戶的流量負(fù)擔(dān)
團(tuán)隊(duì)自治程度的增加,可能會(huì)破壞協(xié)作
「.....」
簡(jiǎn)單來(lái)講,微前端的理念類似于微服務(wù):
?In short, micro frontends are all about slicing up big and scary things into smaller, more manageable pieces, and then being explicit about the dependencies between them.
?
將龐大的整體拆成可控的小塊,并明確它們之間的依賴關(guān)系。關(guān)鍵優(yōu)勢(shì)在于:
代碼庫(kù)更小,更內(nèi)聚、可維護(hù)性更高 松耦合、自治的團(tuán)隊(duì)可擴(kuò)展性更好 「漸進(jìn)地升級(jí)、更新甚至重寫部分前端功能成為了可能」
「微前端」




微前端就是將不同的功能按照不同的維度拆分成多個(gè)子應(yīng)用。通過(guò)主應(yīng)用來(lái)加載這些子應(yīng)用。
微前端的核心在于「拆」, 拆完后再「合」!
今天來(lái)一塊聊聊微前端 技術(shù)

一門前端語(yǔ)言的基礎(chǔ) Vue React
SingleSpa 實(shí)戰(zhàn)
構(gòu)建子應(yīng)用
首先創(chuàng)建一個(gè)vue子應(yīng)用,并通過(guò)single-spa-vue來(lái)導(dǎo)出必要的生命周期:
vue?create?spa-vue??
npm?install?single-spa-vue??
import?singleSpaVue?from?'single-spa-vue';
const?appOptions?=?{
???el:?'#vue',
???router,
???render:?h?=>?h(App)
}
//?在非子應(yīng)用中正常掛載應(yīng)用
if(!window.singleSpaNavigate){
?delete?appOptions.el;
?new?Vue(appOptions).$mount('#app');
}
const?vueLifeCycle?=?singleSpaVue({
???Vue,
???appOptions
});
//?子應(yīng)用必須導(dǎo)出以下生命周期:bootstrap、mount、unmount
export?const?bootstrap?=?vueLifeCycle.bootstrap;
export?const?mount?=?vueLifeCycle.mount;
export?const?unmount?=?vueLifeCycle.unmount;
export?default?vueLifeCycle;
const?router?=?new?VueRouter({
??mode:?'history',
??base:?'/vue',???//改變路徑配置
??routes
})
配置庫(kù)打包
//vue.config.js
module.exports?=?{
????configureWebpack:?{
????????output:?{
????????????library:?'singleVue',
????????????libraryTarget:?'umd'
????????},
????????devServer:{
????????????port:10000
????????}
????}
}主應(yīng)用搭建
?將子應(yīng)用掛載到
?id="vue"標(biāo)簽中
import?Vue?from?'vue'
import?App?from?'./App.vue'
import?router?from?'./router'
import?ElementUI?from?'element-ui';
import?'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
const?loadScript?=?async?(url)=>?{
??await?new?Promise((resolve,reject)=>{
????const?script?=?document.createElement('script');
????script.src?=?url;
????script.onload?=?resolve;
????script.onerror?=?reject;
????document.head.appendChild(script)
??});
}
import?{?registerApplication,?start?}?from?'single-spa';
registerApplication(
????'singleVue',
????async?()=>{
????????//這里通過(guò)協(xié)議來(lái)加載指定文件
????????await?loadScript('http://localhost:10000/js/chunk-vendors.js');
????????await?loadScript('http://localhost:10000/js/app.js');
????????return?window.singleVue
????},
????location?=>?location.pathname.startsWith('/vue')
)
start();
new?Vue({
??router,
??render:?h?=>?h(App)
}).$mount('#app')
?
動(dòng)態(tài)設(shè)置子應(yīng)用
if(window.singleSpaNavigate){
??__webpack_public_path__?=?'http://localhost:10000/'
}
前置條件
npm?install?-g?yarn
yarn?init
安裝 官方 React
Create React App是FaceBook的React團(tuán)隊(duì)官方出的一個(gè)構(gòu)建React單頁(yè)面應(yīng)用的腳手架工具。它本身集成了Webpack,并配置了一系列內(nèi)置的loader和默認(rèn)的npm的腳本,可以很輕松的實(shí)現(xiàn)零配置就可以快速開(kāi)發(fā)React的應(yīng)用。
# 全局安裝
npm install -g create-react-app
# 構(gòu)建一個(gè)my-app的項(xiàng)目
npx create-react-app my-app
cd my-app
# 啟動(dòng)編譯當(dāng)前的React項(xiàng)目,并自動(dòng)打開(kāi) http://localhost:3000/
npm start
構(gòu)建 React項(xiàng)目
npm
npm?init?react-app?my-appYarn
#?yarn?create?is?available?in?Yarn?0.25+
yarn?create?react-app?my-app
使用 qiankun 微前端構(gòu)建
官方文檔: https://qiankun.umijs.org/zh
?首先我們需要?jiǎng)?chuàng)建 3個(gè) 前端應(yīng)用, 微前端 ,就是 代表 一個(gè)小型應(yīng)用的獨(dú)立部署,獨(dú)立交互,需要 應(yīng)用之間進(jìn)行通信,這里我們使用qiankun來(lái)完成 微前端 應(yīng)用
?
創(chuàng)建 ?3 個(gè) react app
yarn?create?react-app?qiankun-base??--template?typescriptyarn?create?react-app?qiankun-micro-app1??--template?typescriptyarn?create?react-app?qiankun-micro-app2??--template?typescriptapp2 app1 基座 在 react app 應(yīng)用中 ?安裝 qiankun ?依賴
$?yarn?add?qiankun??#?or?npm?i?qiankun?-S分別創(chuàng)建 .env 文件來(lái)指定 項(xiàng)目 運(yùn)行的端口號(hào)
PORT=3010
PORT=3011
PORT=3012
在主應(yīng)用中index.tsx ? 注冊(cè)子應(yīng)用
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'react app one', // app name registered
entry: '//localhost:3011',
container: '#micro-app2',
activeRule: '/micro-app2',
props:{
nickname: "全棧小劉",
age:19
}
},
{
name: 'react app two', // app name registered
entry: '//localhost:3012',
container: '#micro-app1',
activeRule: '/micro-app1',
props:{
nickname: "全棧小劉",
age:18
}
},
]);
start();
ReactDOM.render(
,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
??
注:子應(yīng)用加載進(jìn)來(lái) ,需要有主應(yīng)用進(jìn)行掛載,現(xiàn)在我們已經(jīng)將 子應(yīng)用注冊(cè)在 了 主應(yīng)用當(dāng)中
name 應(yīng)用名稱 entry ?端口號(hào) container 掛載容器 activeRule 激活的規(guī)則 props: 父子屬性之間傳參
api文檔 : https://qiankun.umijs.org/zh/api
在 App.tsx 中創(chuàng)建掛載點(diǎn)
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
);
}
export default App;
在「所有」應(yīng)用中 添加 public-path.js 用于 加載靜態(tài) 資源
if?(window.__POWERED_BY_QIANKUN__)?{
????__webpack_public_path__?=?window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
??}
在子應(yīng)用中 添加 webpack 重寫項(xiàng)
添加
yarn?add?react-app-rewired??-D
「設(shè)置 子應(yīng)用」啟動(dòng),在 scripts
??"scripts":?{
????"start":?"react-app-rewired?start",
????"build":?"react-scripts?build",
????"test":?"react-scripts?test",
????"eject":?"react-scripts?eject"
??},
在webpack 中 進(jìn)行 overrides 重寫,重寫的 目的是 允許跨域
config-overrides.js
const?{?name?}?=?require('./package');
module.exports?=?{
??webpack:?(config)?=>?{
????config.output.library?=?`${name}-[name]`;
????config.output.libraryTarget?=?'umd';
????//?config.output.jsonpFunction?=?`webpackJsonp_${name}`;
????config.output.globalObject?=?'window';
????return?config;
??},
??devServer:?(_)?=>?{
????const?config?=?_;
????config.headers?=?{
??????'Access-Control-Allow-Origin':?'*',
????};
????config.historyApiFallback?=?true;
????config.hot?=?false;
????config.watchContentBase?=?false;
????config.liveReload?=?false;
????return?config;
??},
};
在不同的子應(yīng)用當(dāng)中 去加載 tsx 生命周期
app1 ?、app2 、 index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
export async function bootstrap() {
console.log('[react] react app bootstraped');
}
// @ts-ignore
export async function mount(props) {
console.log(props)
ReactDOM.render(
,
props.container
? props.container.querySelector('#root')
: document.getElementById('root'));
}
// @ts-ignore
export async function update(props){
console.log("update props",props)
}
// @ts-ignore
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(
props.container
? props.container.querySelector('#root')
: document.getElementById('root'));
}
// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
ReactDOM.render( ,document.getElementById("root"))
}
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
在 index.tsx 引入 public-path.js 解決靜態(tài)資源
import './public-path.js'
在 主 應(yīng)用 添加 訪問(wèn)
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
);
}
export default App;
在 主 應(yīng)用 index.tsx 中 傳遞 數(shù)據(jù)
props:{
nickname: "全棧小劉",
age:19
}
在 子應(yīng)用 ?周期中進(jìn)行打印
export async function mount(props) {
console.log(props)
ReactDOM.render(
,
props.container
? props.container.querySelector('#root')
: document.getElementById('root'));
}
在 主應(yīng)用中 監(jiān)聽(tīng)事件改變
import { initGlobalState, MicroAppStateActions } from 'qiankun';
const state ={
nickname: "全棧小劉"
}
// 初始化
const actions: MicroAppStateActions = initGlobalState(state);
actions.onGlobalStateChange((state,prev)=>{
console.log(state,prev)
})
// 2秒鐘后 改變
setTimeout(()=>{
actions.setGlobalState({...state,age:19})
},2000)
在子應(yīng)用中 監(jiān)聽(tīng)改變
export async function mount(props) {
console.log(props)
// @ts-ignore
props.onGlobalStateChange((state,prev)=>{
console.log(state,prev)
setTimeout(()=>{
props.setGlobalState({ ...state, age:20 });
},2000)
})
// @ts-ignore
ReactDOM.render(
,
props.container
? props.container.querySelector('#root')
: document.getElementById('root'));
}
安裝 NPM SCRIPT 插件 ,分別 啟動(dòng) 運(yùn)行
接入 Vue3
?通用 vue3
?
安裝最新的腳手架
npm?install?-g?@vue/cli
創(chuàng)建 項(xiàng)目 ?es6 js 模塊
vue?create?qiankun-vue-micro-app3
添加typescript ,轉(zhuǎn)換 ts ? ?-Y yes
vue?add?typescript
依次加入 public-path.js
/*?eslint-disable?*/?
if?(window.__POWERED_BY_QIANKUN__)?{
????__webpack_public_path__?=?window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
??}
安裝 qiankun
?yarn?add?qiankun
參考qiankun官網(wǎng)的示例main.js ,完成生命周期的鉤子函數(shù)
base 中 進(jìn)行注冊(cè) ?index.tsx
vue-config.js 設(shè)置啟動(dòng)端口
vue-config.js
/*?eslint-disable?*/?
const?{?name?}?=?require('./package');
module.exports?=?{
????devServer:{
???????port:?3013,
???????headers:{
????????'Access-Control-Allow-Origin':?'*',
???????}
????},
????configureWebpack:{
??????output:?{
????????library:?`${name}-[name]`,
????????libraryTarget:?'umd'
??????}
????}
??
};
微前端項(xiàng)目 實(shí)戰(zhàn)
https://github.com/a1029563229/micro-front-template

Reference Document : https://juejin.cn/post/6844903943873675271 https://zhuanlan.zhihu.com/p/96464401 https://single-spa.js.org/
