随心而记
1713 字
9 分钟
罗技项目作为子系统嵌入的实现
2024-01-12
发布时间
分类
标签

为什么要改造成为子系统#

由于现在的罗技项目采用Angular框架进行开发的,且该项目Bug非常多,不仅如此,考虑到未来罗技有开发二期的需求,加上目前前端团队对于Angular框架都不太熟悉。基于以上原因最合适的方案是把这个项目改造成子系统,未来二期在另个子系统使用前端团队都熟悉的React框架进行开发。

微前端技术选型#

icestark 是一个面向大型系统的微前端解决方案,适用于以下业务场景:

  • 后台比较分散,体验差别大,因为要频繁跳转导致操作效率低,希望能统一收口的一个系统内
  • 单页面应用非常庞大,多人协作成本高,开发/构建时间长,依赖升级回归成本高
  • 系统有二方/三方接入的需求

icestark 在保证一个系统的操作体验基础上,实现各个微应用的独立开发和发版,主应用通过 icestark 管理微应用的注册和渲染,将整个系统彻底解耦。

整体设计#

罗技项目嵌入实现.png

主应用设计#

使用2023年年中时重构的罗技项目将其改造成主应用,主要在这里实现登陆、权限、布局、菜单等一些全局配置

技术选型

接入

  1. 安装 @ice/stark
npm i --save @ice/stark
  1. 注册子应用
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项目接入流程操作

问题

按照上方的操作正常接入并在主应用上添加对应的菜单,会发现有两个问题

  1. 图片、字体、图标等静态资源加载失败
  2. 通过菜单跳转切换页面不成功

静态资源加载失败问题解决方案

通过浏览器控制台->网络中可以看到,出现这个问题的原因是因为加载这些静态资源的路径不正确,它错误的以主应用为基准进行了请求

罗技项目嵌入.png

尝试的解决手段

  1. 配置webpack的publicPath属性(未解决)
  2. 配置index.html的base标签的href属性(未解决)

可以解决的方案

  1. 将项目中的静态资源上传到OSS对象存储中,在修改引用方式(可解决,但是项目中的静态资源非常多,修改起来非常耗时)
  2. 修改项目中的地址,改为绝对路径

最终选择方案2进行修改,具体修改如下

  • 对于模版中的静态资源使用自定义创建的管道进行修改
  • 对于组件中的静态资源使用自定义全局函数进行修改
  • 对于样式文件中的静态资源使用scss变量拼接控制修改
  • 对于使用到的 Ant Design of Angular 组件库的icon采用全局导入则能正常显示

模版

  1. 创建通道

在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 函数在解决组件的静态资源问题时有介绍

  1. 创建 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 { }
  1. 在模版用使用管道

进入需要使用管道的页面的module中,导入SharedModule

import { SharedModule } from "@app/shared/shared.module";

在模版中使用管道

<img [src]="'/assets/images/1520.png' | assetUrl" alt="">

组件

  1. 创建全局函数

在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}`;
}
  1. 使用全局函数

进入需要使用管道的页面的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 {
}

效果展示#

图片都加载成功

罗技项目嵌入图.png

菜单跳转解决方案

未知原因导致的路由跳转失败调试中发现路由第一次跳转 NavigationCancel 时中断并保留在当前页面,报错信息如下

{
    "id": 3,
    "url": "/businessOverview",
    "reason": "Navigation ID 3 is not equal to the current navigation id 4"
}

路由跳转触发的完整事件流

  1. NavigationStart: 在路由开始导航时触发。
  2. RouteConfigLoadStart: 在 Router 对一个异步路由进行懒加载时触发。
  3. RouteConfigLoadEnd: 在路由配置加载完成时触发。
  4. RoutesRecognized: 在 Router 解析完 URL,并识别出了相应的一组针对该 URL 的路由配置时触发。
  5. GuardsCheckStart: 在 Router 开始运行 CanActivate 和 CanDeactivate 守卫时触发。
  6. ChildActivationStart: 当 Router 开始激活某个路由的子路由时触发。
  7. ActivationStart: 当 Router 开始激活某个路由时触发。
  8. GuardsCheckEnd: 当 Router 完成运行 CanActivate 和 CanDeactivate 守卫时触发。
  9. ResolveStart: 当 Router 开始解析路由时触发。
  10. ResolveEnd: 当路由解析完成时触发。
  11. ChildActivationEnd: 当 Router 完成激活某个路由的子路由时触发。
  12. ActivationEnd: 当 Router 完成激活某个路由时触发。
  13. NavigationEnd: 在路由导航结束时触发。
  14. NavigationCancel: 在路由导航被取消时触发(这可能是由于一个 guard 返回了 false)。
  15. NavigationError: 在路由导航失败时触发。

如果在此基础上重新进入这个路由则能正常进入,通过这个机制,可以在路由跳转时使用setTimeout延时再进入一次,这时路由正确进入,但是页面并没有正常渲染(前一页面的内容仍然在页面上,当前页面的内容在这下方渲染,或着干脆就没有渲染),这时可以通过让页面刷新解决,但是会牺牲一些用户体验

navigate(`/${menuInfo.key}`);
// 如果路径以/logitech/开头,就加载logitech的子应用
if (menuInfo.key.startsWith('logitech/')) {
  setTimeout(() => {
    navigate(`/${menuInfo.key}`);
    window.location.reload();
  }, 50);
}

子应用设计(二期React项目)#

直接新建React项目即可

最终效果展示#

罗技项目嵌入 (1).png

罗技项目嵌入实现 (1).png

罗技项目作为子系统嵌入的实现
https://www.promises.top/posts/implementing-logitech-projects-as-embedded-subsystems/
作者
发布于
2024-01-12
许可协议
CC BY-NC-SA 4.0