项目实战-UmiJS开发(附带qiankun)
前言
Coder 是怎么样提高自己的实力?一件事情做坚持一万遍,唯手熟尔。
不知道各位有没有体会,总感觉之前的代码写的很 low 逼,想抽点空来折腾折腾自己还是很有乐趣的。重构还是很有乐趣的。手工狗头镇楼!
搭建基础框架
一般来说,中小型团队的中台项目都是前端自己主导样式,而样式、布局、路由、权限等等一系列的通用性很强的基础框架,自研比较花时间,投入的回报率不高,最好的方法就是在比较成熟的方案上进行一定的个性化定制,性价比会很高,所以我们也采用了 UmiJS + ANT DESIGN PRO 的架构来进行项目升级(之前的老项目基于 Umi2.0 与 ANT DESIGN PRO 3.0 开发)
UmiJS 是什么?
UmiJS,中文可发音为乌米,是可扩展的企业级前端应用框架。Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。
ANT DESIGN PRO 是什么?
Ant Design Pro 是一个企业级中后台前端/设计解决方案,基于 Ant Design 的设计规范和基础组件的基础上,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。
快速搭建基础框架
我们直接采用 UmiJS 3.0 + Ant Design Pro 4.0 来构建一个基础的模板。
yarn create umi
Select the boilerplate type (Use arrow keys)
❯ ant-design-pro - Create project with an layout-only ant-design-pro boilerplate, use together with umi block.
app - Create project with a simple boilerplate, support typescript.
block - Create a umi block.
library - Create a library with umi.
plugin - Create a umi plugin.
Ant Design Pro 脚手架将会自动安装,安装完依赖之后,一个基础框架就完成了,此章终结。
自定义模块
删除全球化
一般来说,Ant Design Pro 的国际化在大部分团队意义不大,所以可以先删除,后期有需求再进行添加。
先删除 @umijs/plugin-locale 插件,其次删除项目中所有 formatMessage 相关的动态代码,移除 i18n 的相关文件,修改成正常文案即可。
如果你是按照上述的模板下来,再删除 @umijs/plugin-locale 的配置之后,项目就会报错,按照报错的内容修正就行。
添加开发环境
常规开发环境都需要有 3 套,分别是 dev(开发环境),test(测试环境),prod(生产环境)。如果资源富余的话,可以添加 pre(预发环境)。
Umi 通过环境变量 UMI_ENV 可以区分不同环境来指定配置。
// .umirc.js 或者 config/config.js
export default { a: 1, b: 2 };
// .umirc.local.js 或者 config/config.local.js 开发环境
export default { c: 'local' };
// .umirc.test.js 或者 config/config.test.js
export default { b: 'cloud', c: 'cloud' };
在配置多环境文件的时候,切记 config.js 一定要存在,不然会失效
同时修改命令启动命令
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none umi dev",
"build:test": "cross-env UMI_ENV=test umi build",
"build:prod": "cross-env UMI_ENV=prod umi build",
这样即可在不同的环境,运行不同的命令,生成对应的版本。举个栗子,测试环境与线上环境的接口不一致,或者有些功能是需要在测试环境使用。
毕竟 UmiJS + Ant Design Pro 双双结合之后的基础框架非常成熟,简单加一些功能即可进行业务开发,如果需要定制的话,可以自行增减功能。
qiankun 微前端引入
之前掘金有专门的微前端专题,也有很多掘友发过相关的文章与 demo。这里就结合改造之后的项目来介绍下使用。
在使用微前端之前建议看看之前的博文-传送地址: 你为什么需要微前端。确定你团队确实存在这些情况之后,再根据需要业务的定制与之匹配开发模板。
首先,我们需要将前面的基础模板,改造成 2 套,分别作为主子项目模板。
主应用配置
// 注册子应用
export default {
qiankun: {
master: {
apps: [
{
name: 'app1', // 唯一 id
entry: '//localhost:7001', // html entry
},
{
name: 'app2', // 唯一 id
entry: '//localhost:7002', // html entry
},
],
},
},
};
// 装载子应用
export default {
routes: [
{
path: '/',
component: '../layouts/index.js',
routes: [
{
path: '/app1',
component: './app1/index.js',
routes: [
{
path: '/app1/user',
component: './app1/user/index.js',
},
],
},
{
path: '/',
component: './index.js',
},
],
},
],
}
image
把 Layout 的模板删删减减,改造一下就如上图的界面。大体就是将导航菜单改成 Nav 模式,将下面的内容模块全部让出给子应用,这样可以将主体发挥空间全部交给各个子应用来管理。
子应用配置
export default {
qiankun: {
slave: {}
}
}
子应用的配置就相对简单,只要加入上述代码注册插件即可。
子应用的模板,将 Header 模块全部删除,保留侧边路由模块,所以子应用单独开发的时候,可以拥有自己最大的自定义性,可以独立开发。
image子应用在加载到主应用的时候,则如下图所示:
image主应用加载了子应用之后,后续的界面渲染可以全部交给子应用自己管理。
配置开发、生产环境
既然是微前端,不可能同时启动主子两个系统,那样不符合微前端的概念,所以引入 localhost 肯定是不科学的,但是本地引入项目的时候,有的时候存在跨域问题。
export default {
qiankun: {
master: {
apps: [
{
name: 'devops', // 唯一 id
entry: '/qiankun/devops/index.html', // html entry
},
],
},
},
}
proxy: {
dev: {
'/qiankun/': {
target: 'https://www.cookieboty.com',
changeOrigin: true,
pathRewrite: { '^': '' },
},
},
}
将所有的子应用都发布在 qiankun 网关下面,再加上子应用自己的标识即可通过代理的方式,主应用在本地开发的时候可以正常加载子应用并访问。
至于其他的细节问题,可以参考官网 qiankun 插件去制定符合自己业务的模板,这里就不多阐述,有不清楚的地方欢迎留言讨论。
路由权限改造
UmiJS 自带的路由权限校验是会将不符合用户的权限的页面引导到空白或者其他页面,我们这里稍微改造一下,引入动态路由与菜单,如果用户没有该权限的时候将不会暴露菜单与路由出去。
// app.ts
function Routes(routes, userinfo) {
let routesObject = {};
routes.forEach(route => {
if (route.childs && route.childs.length > 0) {
routesObject = Object.assign(routesObject, Routes(route.childs, userinfo));
}
});
return routesObject;
}
export async function render(oldRender) {
window.oldRender();
}
const getRoutes = (routes, newRoutesList) => {
const routesList = [];
routes.forEach(route => {
if (route.routes && route.routes.length > 0) {
route.routes = getRoutes(route.routes, newRoutesList);
}
if (newRoutesList[route.path]) {
routesList.push(route);
}
});
return routesList;
};
export async function patchRoutes(routes) {
const userinfoData = await getUserInfo()
const userinfo = userinfoData.data
const {data} = await getTree()
newRoutes = Routes(data.childs, userinfo);
if (newRoutes) {
routes[0].routes = getRoutes(routes[0].routes, newRoutes);
window.g_routes = routes;
}
}
简单来说就是,动态从后台获取路由然后匹配本身的路由进行一次过滤,筛选出匹配的路由在 patchRoutes 重新修改 g_routes,并非真正意义上的动态路由。也算是一种将就的解决方案吧。
写在最后
同学们看到示例图,有没有联想到 DevOps 系列。
image没错,我胡汉三又回来了,继续推进,敬请期待。
全系列博文目录
后端模块
- DevOps - Gitlab Api使用(已完成,点击跳转)
- DevOps - 搭建 DevOps 基础平台 基础平台搭建上篇 | 基础平台搭建中篇 | 基础平台搭建下篇
- DevOps - Gitlab CI 流水线构建
- DevOps - Jenkins 流水线构建
- DevOps - Docker 使用
- DevOps - 发布任务流程设计
- DevOps - 代码审查卡点
- DevOps - Node 服务质量监控
前端模块
- DevOps - H5 基础脚手架
- DevOps - React 项目开发