likes
comments
collection
share

巨石瓦解🎸!我把Vue3巨石应用拆成了12个微应用~

作者站长头像
站长
· 阅读数 23

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

📖阅读本文,你将

  • 可能什么也学不到。(毕竟本文以经验分享为主,代码偏少)

  • 了解一个 vue3 巨石项目落地为 react 容器 + vue3 子应用微应用方案的落地全过程。

你问我为啥要切换到 React 技术栈?公司决定统一技术路线,选了 React,冒办法啊...

  • 了解一些已经实际落地的工程化方案;

一、背景

复杂的方案。是为了解决复杂的问题。

假设某产品具备 “Sass化”、“多业态支持”的模式。

在此模式指导下,可以设想的使用场景为:在同一套后台管理平台中,不仅可以按需给用户分配可用模块、还必须支持同一模块在不同业态下的“区分性”和“定制性”。如图:

巨石瓦解🎸!我把Vue3巨石应用拆成了12个微应用~

上述描述的方案,如果继续采用 SPA 显然已经不在合适,需要将前端项目进行一些改造,以适配最新的业务形态。

SPA 不适合的原因:

  • 业务代码杂糅在一个仓库内,多业态/纯订制需求会导致代码仓库异常臃肿,难以维护;

  • 有任何模块更新都会导致项目全量更新,可能导致无关业务;

所以我们需要前端项目具备一种新的形态:

  • 业态与业态、项目与项目之间独立构建、发布,可增量部署;
  • 业态与业态、项目与项目之间的差异性代码独立管理;
  • 公共代码依然可以复用;

因此,经过讨论论证,我们选择了 “微前端” 方案来进行前端调整。

二、微前端是什么?

关于微前端,qiankun.js 官网有一段非常清晰的描述。 qiankun.umijs.org/zh/guide

Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. -- Micro Frontends

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

微前端架构具备以下几个核心价值:

  • 技术栈无关 主框架不限制接入应用的技术栈,微应用具备完全自主权

  • 独立开发、独立部署 微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新

  • 增量升级 在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略

  • 独立运行时 每个微应用之间状态隔离,运行时状态不共享

三、技术选型

决定采用微前端架构后,我做了如下技术选型的对比:

  • single-spa(11.2k star) 仓库地址:github.com/single-spa/… 微前端实践的先行者,它定义了一套微前端生命周期,并提供了维护整套声明周期的方法。

  • qiankun.js(12.6K star) 官网:qiankun.umijs.org/ 阿里巴巴开源框架,在 single-spa 框架的基础上进行了二次封装,让使用变得更加容易。

  • mirco-app(2.6K star) 官网:micro-zoe.github.io/micro-app/ 来自京东零售团队的开源框架,基于类WebComponent进行渲染。

  • webpack5 Module Federation 文档地址:webpack.docschina.org/concepts/mo… webpack 5.0 推出的重大更新之一,它提供了一套应用间互相依赖的加载规范。

通过权衡,最终选择了 qiankun.js 作为微前端框架。

理由如下:

  • single-spa 本身做的事情太少,需要从 0 - 1地做太多工作。
  • micro-app 的社区热度相对较低,踩坑可能性较大。
  • webpack 5 模块联邦方案需要构建的每个微应用都以模块联邦形式构建,存量代码成本较高;
  • 公司推行的 Antd 脚手架采用 umijs 构建项目,与 qiankun.js 之间生态吻合度较高;
  • qiankun.js 使用者较多,且社区活跃,封装程度较高,适合在没有太多深度订制的情况话采用;

四、容器与微应用拆分

qiankun.js 的框架中,有两个基础概念:“容器” 与 “微应用”;

  • 容器:指加载网页后会首先加载和渲染的内容,负责后续管理微应用声明周期、全局状态 等工作。
  • 微应用:指具备独立生命周期、独立状态、独立构建的子应用;

巨石瓦解🎸!我把Vue3巨石应用拆成了12个微应用~

微前端的基本结构如上图所示。(实际情况可能比这要复杂,因为微应用与微应用之间也是可以互相引用的)

因此,在正式开始编码之前,我们从 "功能"、"业务"、"实现" 等多个方面进行考量后按如下粒度进行业务拆分:

  • 容器具备以下能力:

    1. 微应用管理
    2. 菜单、导航 等管理
    3. 全局用户状态管理
    4. 全局请求拦截
    5. 修改密码、消息中心、样式选择 等全局功能
  • 微应用则拆分为四大类:

    1. 超管微应用
    2. 基础应用 2.1 登录 2.2 系统管理
    3. 核心业务应用 3.1 核心业务1 3.2 核心业务2 ...
    4. 非核心业务应用

五、在 umi 脚手架上使用 @umi/plugin-qiankun

本节为具体迁移步骤:

5.1 按官网文档进行umi构建时配置

.umirc.ts

{
  ...,
  mountElementId: 'root', // 指定根元素
  qiankun:  {
    master: {
      apps: [] // 无需在配置时增加,为了动态加载,均采用运行时添加微应用
    }
  }
}

5.2 在 app.tsx 导出 qiankun 选项

app.tsx


const fetchQiankunConfig = async () => {
  return {
    apps: [
      {
        name: 'system',
        entry: '/micro-apps/system/',
      },
      {
        name: 'login',
        entry: '/micro-apps/login/',
        props: {
          onLoginSuccess: async (
          ) => {
          // 登录后跳转逻辑
          },
        },
      },
    ],
    routes: [
    {
      path: '/login',
      microApp: 'login',
      exact: true,
    },
    {
      path: '/',
      component: PageWithHeaderAndSider,
      routes: [
        {
          path: '/system',
          microApp: 'system',
        }
      ]
    }
    ],
    prefetch: false, // 关闭预加载
  }
}

export const qiankun = fetchQiankunConfig();

5.3 微应用调整(以 system 子应用为例)

  • 修改 package.json 属性 namesystem;
  • 修改 webpack 配置,增加如下内容,使构建为 umd 格式:
const pkg = require(resolve('./package'))
{
  output: {
    library: `${pkg.name}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${pkg.name}`
  }
}
  • 修改 webpackpublicPath 为: '/micro-apps/system/'

  • 修改项目的 route 模式为 history 模式(vue项目);其根路径修改为:/system

const history = createWebHistory('/system')

createRouter({
    history,
    ...
})

5.4 给子应用增加生命周期

main.js


const { bootstrap, mount, unmount } = initMicroApp({
  entry: App, // 入口
  beforeMount, // 初始化之前的逻辑
  beforeUnmount, // 卸载前逻辑
  afterMount // 挂载后逻辑
})

export { bootstrap, mount, unmount }

代码略;

5.7 子应用使用容器的 axios 实例

其实思路很简单,容器初始化 axios 实例后,挂载到 window.SDK.request 上。

然后在子应用 src/utils/request.js 里定义:

export default request = window.SDK.request

这样就能让子应用的请求被统一拦截处理了。

5.8 其他逻辑细节

由于迁移逻辑细节太多,不在此赘述,大概陈列一下所做的工作:

  • 登录状态、token 管理
  • 菜单、路由、权限等管理
  • 全局用户信息管理
  • 全局权限状态提供
  • 项目 vuex 状态管理拆分
  • 页面拆分
  • 等等...

六、通过代理层获取资源

qiankun.js 官方用例使用端口区分 微应用容器,在实际开发中这显然开销过大。

开发期可能需要起一堆应用🤣。(如图)

巨石瓦解🎸!我把Vue3巨石应用拆成了12个微应用~

当然,不必惊慌,方法总比困难多。

当你需要 “容器” 或 “其他微应用” 的资源时,完全不必局限在本地端口中获取,也可以在某个开发环境上直接拉取。

此时,一个独立轻量的 “代理层” 可能就会显得 恰如其分

巨石瓦解🎸!我把Vue3巨石应用拆成了12个微应用~

虽然也可以在每个微应用内设置代理,但是很显然,一个轻量的代理层会让整个结构变得更加便捷,灵活。

为此,我专门写了另一篇实践文章:《充分且简单!打造专属“轻量代理神器”》,地址:juejin.cn/post/709406…

七、“微应用拆分” 与 “代码共用”

SPA 项目里,因为所有代码都在一个项目里,代码共用是一件简单且自然的事情。

但是一旦把业务拆分成 N 个微应用之后,就变得复杂起来。

如果把所有公共代码打包成 npm 组件显然是无法接受的,因为成本太高。

因此我们采取了如下方案:

yarn workspace + git submodule

  1. 新建一个名为 smart-vue3 的仓库,其 package.jsonname 命名为 @chunge/smart-vue3

  2. 在业务仓库内执行:

git submodule add <url> src/smart-vue3 

这样就可以创造一个软连接指向目标仓库。

  1. 在业务仓库内的pacage.json 里添加属性:
{
  "workspaces": [
    "src/smart-vue3"
  ],
}
  1. 执行 yarn 这样就可以在 node_modules 里创造一个名为 @chunge/smart-vue3 的项目。

通过这种方式,我们可以将需要复用的代码存放其中,完成低成本的“跨应用代码复用”;

通过以下方式,可轻松引入复用代码:

import { xxx } from '@chunge/smart-vue3/components/table'

代码略;

八、使用 webpack externals 特性进行微应用瘦身

本次拆分中,因为几乎所有子应用都是用了 vue/element-plus/echarts 等依赖。所以子应用大量的体积是可以优化的。

在容器的 src/pages/document.ejs 文件中,增加以下标签到 head 中:

<script src="//unpkg.com/vue@3.2.33"></script>
<script src="//unpkg.com/element-plus@1.1.0-beta.24"></script>
<link rel="stylesheet" href="//unpkg.com/element-plus@1.1.0-beta.24/dist/index.css" />
<script src="//unpkg.com/echarts@4.2.1/dist/echarts.min.js"></script>

然后在子应用的 webpack 配置中增加如下内容:

  chainWebpack(config) {
    config.externals([
      {
        vue: 'Vue',
        'element-plus': 'ElementPlus',
        echarts: 'echarts'
      },
      /^(echarts|\$)$/i
    ])
  }

这样可以达到让每个微应用的体积降低 1.2M 左右;

九、构建脚本

微前端的构建通常有两种思路:

  1. 直接构建 这种思路是在一次构件中,将所有需要的微应用依次构建,然后按需要组织构建物,最终形成一个前端制品提供给用户。

  2. 先构建制品,再组装制品 这种方式是分别给每个容器、微应用设置版本号,打包成前端制品。在按需拉取打包后的制品,按需组装成最终的前端制品。

方案2显然是更适合大型项目的管理方法,也可以达到“制品级”复用的效果的。

但也有很多管理上成本的开销,一般在项目较为成熟时使用。

在产品成熟之前,采用方案1开销更小,少造轮子。

具体实施方案如下:

  1. 新增一个 builder 项目。
  2. builder 通过 git submoudle 关联到所有微应用和容器。
  3. 通过脚本依次构建
  4. 通过脚本搬运构建物,组成制品。

代码略;

十、展望

“背景”介绍了本项目“微前端”化的背景,是 SASS 化思路下的产物,也是巨石应用的救星。

展望:在企业信息化、服务化发展进程中,“微前端”方案具备非常大的竞争力与优势,“制品级复用”、“巨石项目拆分” 等都是提效和降低风险的有效举措。