为什么要改造成为子系统
由于现在的罗技项目采用Angular框架进行开发的,且该项目Bug非常多,不仅如此,考虑到未来罗技有开发二期的需求,加上目前前端团队对于Angular框架都不太熟悉。基于以上原因最合适的方案是把这个项目改造成子系统,未来二期在另个子系统使用前端团队都熟悉的React框架进行开发。
微前端技术选型
icestark 是一个面向大型系统的微前端解决方案,适用于以下业务场景:
- 后台比较分散,体验差别大,因为要频繁跳转导致操作效率低,希望能统一收口的一个系统内
- 单页面应用非常庞大,多人协作成本高,开发/构建时间长,依赖升级回归成本高
- 系统有二方/三方接入的需求
icestark 在保证一个系统的操作体验基础上,实现各个微应用的独立开发和发版,主应用通过 icestark 管理微应用的注册和渲染,将整个系统彻底解耦。
整体设计
主应用设计
使用2023年年中时重构的罗技项目将其改造成主应用,主要在这里实现登陆、权限、布局、菜单等一些全局配置
技术选型
接入
- 安装 @ice/stark
npm i --save @ice/stark
- 注册子应用
import { AppRouter, AppRoute } from '@ice/stark';
function Layout() {
return (
<div className="Layout">
<AppRouter>
<AppRoute
activePath="/logitech"
title="罗技angular一期"
entry="http://127.0.0.1:4200"
/>
<div style={{ padding: 20 }}>
<Outlet />
</div>
</AppRouter>
</div>
)
}
export default Layout;
子应用设计(一期Angular项目)
使用 icestark 将罗技项目改造成子系统
接入
根据官方给出的Angular项目接入流程操作
问题
按照上方的操作正常接入并在主应用上添加对应的菜单,会发现有两个问题
- 图片、字体、图标等静态资源加载失败
- 通过菜单跳转切换页面不成功
静态资源加载失败问题解决方案
通过浏览器控制台->网络中可以看到,出现这个问题的原因是因为加载这些静态资源的路径不正确,它错误的以主应用为基准进行了请求
尝试的解决手段
- 配置webpack的publicPath属性(未解决)
- 配置index.html的base标签的href属性(未解决)
可以解决的方案
- 将项目中的静态资源上传到OSS对象存储中,在修改引用方式(可解决,但是项目中的静态资源非常多,修改起来非常耗时)
- 修改项目中的地址,改为绝对路径
最终选择方案2进行修改,具体修改如下
- 对于模版中的静态资源使用自定义创建的管道进行修改
- 对于组件中的静态资源使用自定义全局函数进行修改
- 对于样式文件中的静态资源使用scss变量拼接控制修改
- 对于使用到的 Ant Design of Angular 组件库的icon采用全局导入则能正常显示
模版
- 创建通道
在src->app文件夹下创建pipe文件夹,并创建 asset-url.pipe.ts 文件,内容如下
import { Pipe, PipeTransform } from '@angular/core';
import { assetUrl } from '@app/utils'
@Pipe({
name: 'assetUrl'
})
export class AssetUrlPipe implements PipeTransform {
transform(value: string, ...args: unknown[]): unknown {
return assetUrl(value);
}
}
导入的 assetUrl 函数在解决组件的静态资源问题时有介绍
- 创建 SharedModule
在src->app文件夹下创建shared文件夹,并创建 shared.module.ts 文件,内容如下
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AssetUrlPipe } from '../pipe/asset-url.pipe'; // 更新为 assetUrl 管道的实际路径
@NgModule({
declarations: [AssetUrlPipe],
imports: [
CommonModule
],
exports: [AssetUrlPipe] // 别忘了导出
})
export class SharedModule { }
- 在模版用使用管道
进入需要使用管道的页面的module中,导入SharedModule
import { SharedModule } from "@app/shared/shared.module";
在模版中使用管道
<img [src]="'/assets/images/1520.png' | assetUrl" alt="">
组件
- 创建全局函数
在src->app文件夹下创建utils文件夹,并创建 index.ts 文件,内容如下
export function assetUrl(url: string): string {
// @ts-ignore
const publicPath = __webpack_public_path__;
const publicPathSuffix = publicPath.endsWith('/') ? '' : '/';
const urlPrefix = url.startsWith('/') ? '' : '/';
return `${publicPath}${publicPathSuffix}${urlPrefix}${url}`;
}
- 使用全局函数
进入需要使用管道的页面的component中,导入这个assetUrl
import { assetUrl } from '@app/utils';
使用assetUrl
const list = [
{
icon: assetUrl('assets/images/salesYoy.png'),
},
];
样式文件
在样式文件下新增对应的地址,并使用
$assets-dev: 'http://127.0.0.1:4200/assets';
.toFullScreen{
background:url(#{$assets-content-dev}/images/fullscreen.jpeg);
}
Icon
import * as AllIcons from '@ant-design/icons-angular/icons';
const antDesignIcons = AllIcons as {
[key: string]: IconDefinition;
};
const icons: IconDefinition[] = Object.keys(antDesignIcons).map(key => antDesignIcons[key])
@NgModule({
declarations: [
AppComponent
],
imports: [
NzIconModule.forRoot(icons)
]
bootstrap: [ AppComponent ]
})
export class AppModule {
}
效果展示
图片都加载成功
菜单跳转解决方案
未知原因导致的路由跳转失败调试中发现路由第一次跳转 NavigationCancel 时中断并保留在当前页面,报错信息如下
{
"id": 3,
"url": "/businessOverview",
"reason": "Navigation ID 3 is not equal to the current navigation id 4"
}
路由跳转触发的完整事件流
- NavigationStart: 在路由开始导航时触发。
- RouteConfigLoadStart: 在 Router 对一个异步路由进行懒加载时触发。
- RouteConfigLoadEnd: 在路由配置加载完成时触发。
- RoutesRecognized: 在 Router 解析完 URL,并识别出了相应的一组针对该 URL 的路由配置时触发。
- GuardsCheckStart: 在 Router 开始运行 CanActivate 和 CanDeactivate 守卫时触发。
- ChildActivationStart: 当 Router 开始激活某个路由的子路由时触发。
- ActivationStart: 当 Router 开始激活某个路由时触发。
- GuardsCheckEnd: 当 Router 完成运行 CanActivate 和 CanDeactivate 守卫时触发。
- ResolveStart: 当 Router 开始解析路由时触发。
- ResolveEnd: 当路由解析完成时触发。
- ChildActivationEnd: 当 Router 完成激活某个路由的子路由时触发。
- ActivationEnd: 当 Router 完成激活某个路由时触发。
- NavigationEnd: 在路由导航结束时触发。
- NavigationCancel: 在路由导航被取消时触发(这可能是由于一个 guard 返回了 false)。
- NavigationError: 在路由导航失败时触发。
如果在此基础上重新进入这个路由则能正常进入,通过这个机制,可以在路由跳转时使用setTimeout延时再进入一次,这时路由正确进入,但是页面并没有正常渲染(前一页面的内容仍然在页面上,当前页面的内容在这下方渲染,或着干脆就没有渲染),这时可以通过让页面刷新解决,但是会牺牲一些用户体验
navigate(`/${menuInfo.key}`);
// 如果路径以/logitech/开头,就加载logitech的子应用
if (menuInfo.key.startsWith('logitech/')) {
setTimeout(() => {
navigate(`/${menuInfo.key}`);
window.location.reload();
}, 50);
}
子应用设计(二期React项目)
直接新建React项目即可