微前端架构实践经验总结

1,138 阅读6分钟

项目背景

从去年7月份至今,入职公司也有一年多了,前期忙于工作,没有时间做一些技术总结。趁着这会有闲空,写写我这段时间的工作内容吧,也当做记录一下,方便日后回顾。

公司的项目名为MDOS——多维空间科技服务的商业地产操作系统,也即是一个SAAS平台,主要面对B端,为商业地产公司提供数字化转型的解决方案。

项目属于初创阶段,我入职公司的时候项目已经进入研发阶段,开发了3个月的时间,有一定的技术债务。

旧项目痛点

旧项目采用了vuejs + element ui,并使用vue-cli3创建项目。根据业务设计——1个中心+N个服务的模式。前期为了快速迭代开发,前端将应用模块都放置在一个工程,每个应用使用一个目录的方式来管理。

这种方式存在许多弊端:

  1. 不利于多人协作开发,同一项目开发代码容易冲突。
  2. 不利于日后维护,随着时间的推移,项目越来越庞大,维护成本会急剧增加。
  3. 不利于打包部署,项目代码量增大,自然会导致每次发版时间长,容易出错等风险。

解决方案

为了项目的可持续开发、维护与管理,结合现阶段的业务的规划。首先需要做应用的拆分,将每个应用拆成独立的项目,有自己单独的git进行版本管理。

在公司内部统一采用gitlab进行代码版本的管理,每个应用创建单独的工程,如 mdos-front-main、mdos-front-isd、mdos-front-workorder等。

每个应用编译生成的静态资源,独立部署,并分配独立的子域名。

应用拆分之后需要对应用进行整合,这样看起来就像一个整体。

对比了目前兴起的微前端技术解决方案,最终选了 qiankun 做为微前端架构的技术选型。

image.png

qiankun 框架介绍

qiankun 是一个基于 single-spa 的微前端库。它简单易用,并在single-spa的基础上做了改进,增加了样式隔离、JS沙箱、资源预加载等新的特性。

对于什么是微前端架构不了解的,可以看下 qiankun官网 相应的介绍。

技术选型

微前端架构的优势在于无关你使用的技术栈,可以用来兼容旧项目,不管你在项目中使用的是vue,还是react或者angular,统统可以整合在一起。

考虑到整个前端组综合能力问题,最终选用原先大家熟悉的 vue + element ui技术栈进行开发,只是整合了qiankun,主应用和子应用都采用了vue来开发。

image.png

npm 私服搭建

在项目开发过程中,会提取一些公共的包,通过npm的包管理方式进行管理。

publish到公网 (npm官网) 并不合适,于是采用了 verdaccio 搭建了自己的npm私服。搭建过程也是简单,在Linux服务器上安装nodejs环境,然后使用npm进行安装。

npm install -g verdaccio

image.png

@mdos/components 组件库搭建

随着项目的不断迭代 element ui 已经无法满足业务方的需求,需要一些定制化的组件,于是创建了 @mdos/components 组件库项目。

element 项目中 fork 一个分支,在原有的基础上贴合业务开发了mdosPageListmdosAnchormdosFixedFootermdosFormmdosStatisticCardmdosSteps等组件。

@mdos/common 公共包项目搭建

在微前端的架构中,主应用作为基座改动量较小,大部分业务功能都放在子应用中,一个子应用对应一个应用模块,例如招商应用、租务应用、工单应用、停车场应用等。这些子应用中存在大量的重复性功能,例如,接口服务、公共全局常量、工具函数、全局样式表等。

对于这些公共的功能,统一采用npm包进行管理,如 @mdos/services@mdos/config@mdos/utils@mdos/theme@mdos/common等。

由于公共npm包比较多,为了方便包的管理,引入了 lerna 进行多包管理。

image.png

使用微前端架构过程中遇到的问题

1. 缓存问题

每次部署应用后,通过刷新浏览器发现更新的内容并无变化,一定要通过强刷(Ctrl+F5/command+shift+R)的方式才能呈现最新的内容。

尝试通过修改应用index.html 配置一个响应头:Cache-Control no-cache 和配置nginx

location = /index.html {
  add_header Cache-Control no-cache;
}

统统不起作用。

经过一步步排查,最终发现是qiankun内部使用fetch方法去获取子应用模块数据时默认缓存了旧应用的数据,重新自定义fetch方法,设置no-cache即可解决问题。

start({
    async fetch(url, ...args) {
        return window.fetch(url, { cache: 'no-cache' }, ...args)           
    }
});
    

2. 同时启动多个服务问题

由于主应用是项目的总入口,所以开发过程中要同时启动主应用和子应用两个服务甚至多个服务。

为了解决问题,于是使用 nodejs 开发了 @mdos/script 命令行工具.

通过一系列命令行简化开发流程。

mdos-script init // 初始化工程 拉取子应用基础模板

mdos-script create // 交互式创建模板页面

mdos-script serve // 一键启动主应用和子应用

mdos-script upload // 上传自定义模板

mdos-script doc // 输出各种开发文档的访问链接

mdos-script build // 编译项目

3、硬编码更新频繁的问题

在开发过程中,各种配置文件都是通过硬编码写在项目中,如微应用的一些注册信息、接口网关、iconfont 样式等

const apps = [
    /**
     * name: 微应用名称 - 具有唯一性
     * entry: 微应用入口 - 通过该地址加载微应用
     * container: 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
     * activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用
     */
    {
        name: 'PatrolMicroApp',
        entry: patrolHost,
        container: '#subapp-viewport',
        activeRule: '/microApp/patrol'
    },
    {
        name: 'GmvMicroApp',
        entry: gmvHost,
        container: '#subapp-viewport',
        activeRule: '/microApp/gmv'
    },
    // ......
]
const prefix = {
    MDOS_IOC: '/mdos_ioc',
    MDOS_RMS: '/mdos_rms',
    EQUIPMENT: '/mdos_wms_admin',
    EQUIPMENT_APP: '/mdos_wms_app',
    MDOS_ISD: '/mdos_isd',
    MDOS_ISDMGMT: '/mdos_isdmgmt',
    IMS_RENT: '/os_lms_rent',
    IMS_CONTRACT: '/os_lms_contract',
    KERNEL_APPSYSCTR: '/os_kernel_appsysctr',
    KERNEL_DATACTR: '/os_kernel_datactr',
    KERNEL_CLIENTCTR: '/os_kernel_clientctr',
    KERNEL_ASSETCTR: '/os_kernel_assetctr',
    KERNEL_MSGCTR: '/os_kernel_msgctr',
    OS_KERNEL_AUTHZCTR: '/os_kernel_authzctr',
    MDOS_WMSMGMT: '/mdos_wmsmgmt',
    // ......
}
@font-face {
  font-family: 'iconfont';  /* Project id 21244339 */
  src: url('//at.alicdn.com/t/font_2173529_3bbbl73w3z2.woff2?t=1639125351111') format('woff2'),
       url('//at.alicdn.com/t/font_2173529_3bbbl73w3z2.woff?t=1639125351111') format('woff'),
       url('//at.alicdn.com/t/font_2173529_3bbbl73w3z2.ttf?t=1639125351111') format('truetype');
}

每次配置改动都需要重新打包部署,费时费力。

于是创建了BFF项目(Backends For Frontends 服务于前端的后端)。

因为是前端自发的项目,开发责任落到前端,最终选型了阿里开源的企业级开发框架 eggjs,和 mongodb 数据库进行开发。

image.png

将所有配置数据添加至数据库,并提供数据调用接口,最后改造代码。

import { getMicroappConfig } from '@/services';

try {
    const res = await getMicroappConfig({ _id: '60f7e4c4535f2852d150442d' });
    if (res.data) {
        currAppInfo = res.data;
    }
} catch (error) {
    // ......
}

import styled from 'vue-styled-components'; // # CSS-in-JS

const divProps = { version: String };
const AppStyled = styled('div', divProps)`
    @font-face {
        font-family: 'iconfont'; /* project id 2173529 */
        src: url('//at.alicdn.com/t/${props => props.version}.eot');
        src: url('//at.alicdn.com/t/${props => props.version}.eot?#iefix')
                format('embedded-opentype'),
            url('//at.alicdn.com/t/${props => props.version}.woff2') format('woff2'),
            url('//at.alicdn.com/t/${props => props.version}.woff') format('woff'),
            url('//at.alicdn.com/t/${props => props.version}.ttf') format('truetype'),
            url('//at.alicdn.com/t/${props => props.version}.svg#iconfont') format('svg');
    }
`;
export default AppStyled;

总结

从微前端架构技术选型到实践开发,遇问题找问题并解决问题,一路披荆斩棘。不断的完善前端开发流程,提高整个团队的研发效率。在这个过程中,不论什么技术,什么框架,什么架构,它的诞生从来都不是偶然的,都是人们为了解决某些问题而必然形成的产物。