为什么要改造成为子系统
由于现在的罗技项目采用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项目即可