「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,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;
}
}
复制代码
目前初版微前端已经上线,下个版本有可能会有嵌套多层子应用。如下问题须要解决
这个版本,脚手架完成了基础的功能,能根据模块去生成咱们的子应用。后期加强以后,能生成主应用和子应用,会分享这块。以上是微前端落地这块总结的一些问题,若有问题,欢迎指正。