「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!」css
產品提出系統要模塊化,經過不一樣的模塊組合構建咱們的應用。這不是最近很火的微前端嗎,和組內的小夥伴通過一番溝通,帶着壓力開始搞起來了,通過三個月的迭代開發,在業務開發的同時,把兩個模塊遷移成了子應用。系統上線了,懸着的心終於落下了。html
現有的系統是一個獨立的項目,採用umi框架開發,經過文件夾區分各大業務模塊。公共組經過npm包的方式去管理。因爲系統迭代時間比較久,架構上遺留的幾個問題須要解決前端
子應用採用ant4.x
(antd用的3.x版本,升級antd4.x難度比較大,主要是Form變更比較大)hooks替換redux部分state
(redux stroe中的state差很少有一百多個,一些本不該該放在Redux裏的狀態也放到了裏面)統一封裝requst
(拆分紅獨立http和socket請求庫,支持hooks)腳手架
升級
,支持antd4.x
通訊
共享
一些實例
供子應用使用部分數據
須要共享
,怎麼作限制動態加載
跳轉
父應用微前端框架用qiankun, 因爲咱們用的框架是umi, 對應提供了插件 @umijs/plugin-qiankun。webpack
qiankun: {
master: {},
},
複製代碼
function microApp(entryPrefix) {
return [
{
name: 'app1', //惟一 id
entry: entryPrefix ? `${entryPrefix}/app1` : '//localhost:3000',
},
{
name: 'app2', //惟一 id
entry: entryPrefix ? `${entryPrefix}/app2` : '//localhost:3001',
},
... //其它子應用
];
}
export default microApp;
複製代碼
const router = [
{
path: '/main/app/rouer/app1', // (/main/app/rouer/)主應用部分路由
microApp: 'app1',
microAppProps: {
autoSetLoading: true,
className: 'appClassName',
wrapperClassName: 'wrapperClass',
},
},
{
path: '/main/app/rouer/app2',
microApp: 'app2',
microAppProps: {
autoSetLoading: true,
className: 'appClassName',
wrapperClassName: 'wrapperClass',
},
}
... //其它子應用
];
export default router;
複製代碼
app.js
導出qiankun對象import microApp from './microApp';
import router from './router';
export const qiankun = new Promise((resolve) => {
const entryPrefix = process.env.NODE_ENV === 'production' ? window.location.origin : null;
const res = microApp(entryPrefix);
resolve(res);
}).then(apps => {
return {
apps,
routes: router,
};
});
複製代碼
qiankun: {
slave: {},
},
複製代碼
app.js
導出qiankun的生命週期export const qiankun = {
// 應用加載以前
async bootstrap(props) {
init(props);
},
// 應用 render 以前觸發
async mount(props) {
},
// 應用卸載時觸發
async unmount(props) {
},
};
複製代碼
基於qiankun和umi提供的qiankun插件仍是比較方便的運行起來。下面咱們一塊兒看下上面的問題怎麼解決。nginx
咱們採用施漸進式
重構,咱們須要一種增量升級
的能力,先讓新舊代碼和諧共存
,再逐步轉化舊代碼,直到整個重構完成。目前咱們把基於antd3.x
版本的組件庫升級到4.x
。兩個版本同時更新一段時間,兩個版本能夠共存
,遷出來的子應用使用antd 4.x
,保證新的特性咱們能使用web
umi qiankun plugin
提供了主子應用通訊的方式npm
useQiankunStateForSlave
方法export function useQiankunStateForSlave() {
const [masterState, setMasterState] = useState({});
return {
SlaveSDK,
getStore,
// masterState,
setMasterState,
};
}
複製代碼
import { useModel } from 'umi';
function MyPage() {
const masterProps = useModel('@@qiankunStateFromMaster');
return <div>{JSON.stringify(masterProps)}</div>;
}
複製代碼
該方式主子應用通訊沒問題,嵌套多層子應用使用不是很方便,後期咱們會對它進行改造redux
在微前端架構當中,不推薦應用共享實例,由於這樣子應該很難作到獨立開發,獨立部署,應用會有耦合。但因爲咱們是對系統作拆分,一些公用的功能,好比彈框查看日誌,數據預覽等功能會先用父應用的功能,須要共享。這裏咱們封裝了SDK供子應用使用。後期若是要改造這塊成獨立開發,獨立部署。咱們實現對應的接口,動態注入,就能夠實現。bootstrap
class Log {
show = (params) =>
// 具體業務邏輯
}
showOther = (params) => {
// 具體業務邏輯
}
}
export default new Log();
複製代碼
SlaveSDK
實例import intl from 'utils/intl';
import { history } from 'umi';
import { getParams } from 'utils/util';
import Log from './log';
class SlaveSDK {
constructor() {
this.log = Log;
this.intl = intl;
this.router = {
history, // 主應用的history對象
getParams, // 獲取主應用的參數
};
... 其它實例共享
}
}
export default new SlaveSDK();
複製代碼
import SlaveSDK from './SlaveSDK';
export function useQiankunStateForSlave() {
return {
SlaveSDK,
};
}
複製代碼
export const qiankun = {
// 應用加載以前
async bootstrap(props) {
props?.SlaveSDK && setSDK(props.SlaveSDK);
},
};
複製代碼
子應用業務有可能須要Redux Store的部分數據
,但咱們不能把整個Store都暴露給子應用
,在應用使用的時候作些限制,這裏咱們用到了代理
對象去實現後端
const allowGet = { auth: '', user: '' }; // 須要共享的key
const state = getReduxStore();
const proxy = new Proxy(state, {
get(target, property) {
if (property in allowGet) {
return target[property];
}
return undefined;
},
});
return proxy;
};
複製代碼
咱們經過共享實例,經過SDK的方式後去獲取國際化實例intl
,在子應用初始化的時候去加載國際化。
/**
* 國際化初始化
*/
const intlInit = async () => {
// load語言 文件
const result = await import('./locales');
const intl = await import('./utils/intl');
try {
moment.locale(intl?.default?.getIntlLang?.() || 'zh_CN');
intl?.default?.load(result.default);
} catch (error) {
console.error('intl error', error);
}
};
複製代碼
在子應用中跳轉父應用的路由,也是經過把父應用的router的history
對象傳遞給子應用
import { history } from 'umi';
class SlaveSDK {
constructor() {
this.router = {
history, // 主應用的history對象
getParams, // 獲取主應用的參數
};
}
}
複製代碼
樣式隔裏子應用咱們用的是cssModule
,編譯的時候會自動生成惟一的key。主要問題是antd,由於咱們既加載了antd3,又加載了antd4,致使樣式會有衝突。我在antd4的編譯的時候改前綴。配置以下
import { ConfigProvider } from 'antd';
// 彈框的前綴配置
ConfigProvider.config({
prefixCls: 'my-ant',
});
// 組件的配置
<ConfigProvider prefixCls="my-ant">
</ConfigProvider>
複製代碼
系統採用nginx部署,是在同一個Server下經過不一樣的path來處理。配置以下
server {
listen 8080;
server_name localhost;
location / {
root html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /app1 {
root html;
index index.html index.htm;
try_files $uri $uri/ /app1/index.html;
}
location /app2 {
root html;
index index.html index.htm;
try_files $uri $uri/ /app2/index.html;
}
}
複製代碼
目前初版微前端已經上線,下個版本有可能會有嵌套多層子應用。如下問題須要解決
這個版本,腳手架完成了基礎的功能,能根據模塊去生成咱們的子應用。後期加強以後,能生成主應用和子應用,會分享這塊。以上是微前端落地這塊總結的一些問題,若有問題,歡迎指正。