如何系统性拆解前端“项目粒度”:从 SPA 到微前端

59 阅读2分钟

一、背景:当项目越来越大,单体架构变成技术负担

广告平台经历多个阶段:

  • 初期为单 SPA(Single Page Application),业务快速集中开发
  • 中期模块增多、接口冲突、构建缓慢、协作困难
  • 后期我推动将其演进为“可拆分、可独立部署、可跨项目复用”的模块架构

这也是走向微前端体系的自然过程。


二、判断是否该拆的三个信号

  1. 多团队并行开发 + 发布耦合严重
  2. 每次打包时间超过 30 秒
  3. 不同模块间基础依赖版本不一致,频繁冲突

三、拆分的三种方式对比

拆分方式特点适用场景
按功能模块划分各模块目录独立,内部协作好中大型 SPA 项目
按子项目部署独立构建部署,主项目载入高自治业务模块
微前端(MF)架构独立技术栈、热更新、嵌套部署多团队并行 & 复杂系统

四、广告平台拆分演进路线

  1. 初始结构:所有功能一个 Vue 项目,目录如下:
src/
  views/
    ad/
    material/
    audit/
  1. 拆出公共模块为包(组件库、工具库)
packages/
  ui-lib/
  utils/
  1. 独立打包业务模块(如 report)并通过主项目注册:
// main.ts
loadRemoteModule('report', 'https://cdn.xxx.com/report.js')

五、Module Federation 实战(vite + vue3)

使用 vite-plugin-federation 构建:

// vite.config.ts
import federation from '@originjs/vite-plugin-federation'

export default defineConfig({
  plugins: [
    federation({
      name: 'report',
      filename: 'remoteEntry.js',
      exposes: {
        './ReportPage': './src/pages/Report.vue'
      },
      shared: ['vue', 'pinia']
    })
  ]
})

主应用加载:

import('report/ReportPage').then(mod => {
  app.use(mod.default)
})

六、拆分时常见问题与解法

1. 路由冲突

解决:子应用使用子路由前缀 + 独立 router 实例

2. 样式污染

解决:每个子应用打包为 scope 包或使用沙箱隔离(如 qiankun)

3. 状态同步困难

解决:使用 shared pinia store 或 postMessage 通信桥


七、构建与部署策略

  • 每个子模块独立构建(CI),产物上传 CDN:/cdn/{module}/{version}/
  • 主项目使用远程引用 + 异步加载:
const loadModule = (name) => import(`https://cdn.xxx.com/${name}/latest/remoteEntry.js`)
  • 保留主项目兜底页,防止子模块失败后空白

八、子项目本地调试与开发体验

通过 proxy + 环境变量模拟:

// .env.local
VITE_REPORT_URL=http://localhost:9001

主项目动态挂载:

const reportEntry = import.meta.env.VITE_REPORT_URL || 'https://cdn...'
import(`${reportEntry}/remoteEntry.js`)

九、总结

SPA 架构并不等于“不能拆”,而是要有“按需拆解 + 合理组合”的能力。

我在广告平台项目中,结合业务规模、团队组织和部署流程,逐步推进模块解耦 → 独立构建 → 微前端落地,实现了真正意义上的前端架构升级。