一次 SPA 架构下的性能优化实践

接收反馈

在最近我们陆续接到一些用户反馈:

"页面感觉卡卡的,转圈圈时间有点长。"

作为前端开发者,这类反馈再常见不过。但它看似模糊,背后却往往隐藏着架构层面的深层问题。这次我决定趁着业务相对空档,彻底剖析并优化我们系统的初始化性能问题。

为什么会 卡卡的?

我们的项目是一个标准的 SPA(Single Page Application) 应用,初期设计为了保持统一性,所有模块共用同一入口和配置初始化逻辑,包括给外部用户分享的仪表盘等子模块。

但经过实际分析,我们发现一个严重的问题

初始加载时,统一入口会发送几十甚至上百个配置类 HTTP 请求**,这些请求多数对当前模块其实是无效的!

具体来说:

  • 用户只打开一个嵌套的仪表盘页面,但整个 SPA 初始化后会全量请求所有模块配置
  • 造成网络资源浪费JS 主线程长时间阻塞
  • 仪表盘模块虽然功能简单,但加载体验却比主模块还"重"。

初步分析:想改?代价太高!

一开始我们试图直接对配置模块进行"精简改造",结果发现:

  • 配置模块与各业务模块深度耦合,理清依赖链工作量极大。
  • 配置项间有交叉依赖,难以简单裁剪或懒加载
  • 要做到彻底精细化请求,需要大规模重构初始化流程

彻底重构?显然不是当前阶段的最佳选择。

于是我们退而求其次,选择更务实的优化方案


优化方案:从 SPA 到 MPA 的渐进式重构

在权衡收益与开发成本之后,我制定了如下两步走的优化方案:

1. 独立模块拆分为多页入口(SPA → MPA)

  • 将原先作为 SPA 子路由存在的"仪表盘模块",抽离为 独立页面入口
  • 借助 webpack配置,将其打包为独立 JS Chunk,不再加载与主模块无关的代码。

这一步实现了两个好处:

  • 首次加载资源明显减少(只加载当前模块所需 JS 和 CSS)。
  • 页面逻辑更加清晰隔离,便于按模块优化

具体webpack配置:

js 复制代码
应用入口:
const ENTRY_JS = './src/app/*/*.js'
const path = require('path')

// 获取多应用入口文件
function getEntries(globPath) {
  const files = glob.sync(globPath);

  const entries = {};
  let dirname;
  let basename;
  let pathname;
  let extname;

  files.forEach((item) => {
    dirname = path.dirname(item); // 当前目录
    extname = path.extname(item); // 后缀
    basename = path.basename(item, extname); // 文件名
    pathname = path.join(dirname, basename); // 文件路径
    if (extname === '.html') {
      entries[pathname] = item;
    } else if (extname === '.js') {
      entries[basename] = item;
    }
  });

  return entries;
}
js 复制代码
// htmlPlugin插件
function buildHtmlPlugins(globPath) {
  const htmlPlugins = [];
  const files = glob.sync(globPath);

  const pageChunk = {
    main:['vendors', 'common-vendor', 'chunk-main']
    dashboard:'vendors', 'common-vendor', 'chunk-dashboard']
  }

  files.forEach((item) => {
    const filename = path.basename(item, path.extname(item));
    const conf = {
      filename: `${filename}.html`,
      template: './src/index.html',
      hash: true,
      chunks: [...pageChunk[filename] || [], filename],
      minify: {
        removeAttributeQuotes: true,
        removeComments: true,
      }
    };
    htmlPlugins.push(new HtmlWebpackPlugin(conf));
  });
  return htmlPlugins;
}
js 复制代码
module.exports = {
  entry: getEntries(ENTRY_JS),
  output: {
    path: 'dist',
    filename: 'js/[name]_[chunkhash].js'
  },
  pugins: [
    ...buildHtmlPlugins(ENTRY_JS),
  ],
  optimization: {
      splitChunks: {
          cacheGroups: {
            // 具体chunk拆分参考https://webpack.docschina.org/configuration/optimization/
          }
      }
  }
}

应用入口:index.js

js 复制代码
------index.js---
export default (pageComponent) => {
  new Vue({
    el: '#root',
    template: '<pageComponent/>',
    components: { pageComponent },
  });
};

dashboard入口:

js 复制代码
--dashboard.js--
import init from '../../index.js';
import page from './dashboard.vue';

init(page);

---dashboard.vue--
<template>
  <router-view />
</template>

<script>
  export default {
    name: 'dashboard'
  };
</script>

main入口:

js 复制代码
--main.js
import init from '../../index.js';
import page from './main.vue';

init(page);

---main.vue
<template>
  <router-view />
</template>

<script>
  export default {
    name: 'main'
  };
</script>

最终目录结构:

js 复制代码
├── index.html
├── index.js
├── app
│   ├── dashboard
│   │   ├── xxx
│   │   ├── dashboard.js
│   │   ├── dashboard.vue
│   ├── main
│   │   ├── xxx
│   │   ├── main.js
│   │   └── main.vue

2. 配置信息按需请求(全量 → 精细化)

  • 抽象出"当前模块所需配置类型",页面初始化时仅请求对应内容。
  • 配置模块服务端也增加参数过滤支持,避免多余计算与网络传输。
  • 后续计划支持"配置缓存",进一步降低重复请求。

实体实现: 新增一个dashboard模块下对应的页面相关的配置id的API,当访问具体的仪表盘时按id获取对应页面的配置, 具体逻辑代码实现就不列举了

相关推荐
爱勇宝37 分钟前
鸿蒙生态的下半场:开发者不只要能开发,还要能赚钱
android·前端·程序员
IT_陈寒4 小时前
SpringBoot这个自动配置坑我跳了三次
前端·人工智能·后端
kyriewen4 小时前
我用 AI 一周写完了整个项目,上线第一天就崩了——这是我踩过最贵的 5 个坑
前端·javascript·ai编程
牧艺4 小时前
从零到协同:构建类飞书在线文档系统的五个技术重难点
前端·人工智能
红尘散仙5 小时前
想写一个像样的终端 App?试试把 React 的开发体验搬进 Rust TUI
前端·rust
袋鼠云数栈UED团队6 小时前
一套 Spec-First 的 AI 编程工作流
前端·人工智能
袋鼠云数栈前端6 小时前
一套 Spec-First 的 AI 编程工作流
前端·ai+
angerdream6 小时前
Android手把手编写儿童手机远程监控App之vue3 路由守卫
前端
不服老的小黑哥6 小时前
AI规范驱动编程-harness工程项目实战
前端
vivo互联网技术6 小时前
从 Web 到桌面:基于 Tauri 2.0 + Vue 3 打造 vivo 线下门店「大头贴」拍照体验系统
前端·rust