Common分离实践:提升前端项目可维护性的关键技术方案

一汉霖 优化 阅读 2,043
赞 9 收藏
二维码
手机扫码查看
反馈

优化前:卡得不行

上个月接手一个老项目,首页加载时间动不动就5秒往上飙,用户反馈“点进去以为页面挂了”。我本地跑起来也是一言难尽——白屏时间长,JS bundle 超过 3MB,首屏关键资源全塞在一个 vendor.js 里。最离谱的是,明明只是进个登录页,却把整个后台系统的组件、工具函数、甚至图表库全打包进来了。

Common分离实践:提升前端项目可维护性的关键技术方案

这哪是前端,简直是“前端负重训练”。

找到瓶颈了!

先用 Chrome DevTools 的 Performance 面板录了一次加载,发现主线程被一大块 JS 解析和执行占满,持续 2.8 秒。接着打开 Network 看资源,vendor.js 3.2MB(gzip 后也有 980KB),而且是 render-blocking 的。再用 Webpack Bundle Analyzer 一分析,好家伙,Lodash、Moment、ECharts、Axios、一堆公共组件全挤在同一个 chunk 里,不管哪个页面都得背这个大包。

问题很明显:**没有做合理的代码分割(Code Splitting),所有公共依赖一股脑打包进初始 bundle**。这就是典型的“Common 不分离”问题。

试了几种方案,最后这个效果最好

一开始我想着用 Webpack 的 SplitChunksPlugin 自动拆分,但默认配置太保守,只拆 node_modules 里大于 30KB 的包,结果 Lodash 和 Moment 还是混在一起。后来我手动配置 cacheGroups,把高频、大体积的公共依赖单独抽出来。

折腾了半天,发现光靠自动拆还不够,有些业务组件虽然被多个页面引用,但并不是首屏必需的,比如“用户设置弹窗”、“导出功能模块”,这些也应该动态加载。

最后定下来的策略是:核心运行时依赖 + 高频公共库 单独拆成 common.js,非首屏组件用动态 import 按需加载

核心代码就这几行

重点看 Webpack 的 optimization.splitChunks 配置。我直接上最终版配置:

// webpack.config.js
optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      // 抽离核心运行时和框架
      vendor: {
        name: 'vendor',
        test: /[\/]node_modules[\/](react|react-dom|react-router|axios)[\/]/,
        priority: 20,
        chunks: 'initial'
      },
      // 抽离大型工具库
      common: {
        name: 'common',
        test: /[\/]node_modules[\/](lodash|moment|echarts)[\/]/,
        priority: 10,
        chunks: 'initial'
      },
      // 公共业务组件(非 node_modules)
      shared: {
        name: 'shared',
        test: /[\/]src[\/](components|utils|hooks)[\/]/,
        minChunks: 2, // 至少被两个 chunk 引用
        priority: 5,
        reuseExistingChunk: true
      }
    }
  }
}

这里注意我踩过好几次坑:chunks: 'initial' 很关键,它确保这些公共包只在入口文件中加载,而不是每个异步 chunk 都重复打包。另外 priority 必须设清楚,否则 Webpack 会按默认规则合并,导致拆分失效。

对于非首屏组件,比如一个“高级筛选面板”,我直接改成动态导入:

// 优化前
import AdvancedFilter from '@/components/AdvancedFilter';

// 优化后
const AdvancedFilter = React.lazy(() => import('@/components/AdvancedFilter'));

配合 Suspense 使用,体验完全无感,但 bundle 体积立马减了 120KB。

踩坑提醒:这三点一定注意

  • 别过度拆分:我一开始把每个 npm 包都单独拆,结果 HTTP/1.1 下请求太多反而更慢。后来合并成 vendor + common 两个核心包,平衡了并行加载和请求数。
  • 缓存策略要跟上:拆出来的 common.js 如果内容不变,必须加 long-term cache(比如 [contenthash])。不然用户每次更新都要重新下载,白优化了。
  • 动态 import 别滥用:像 Header、Footer 这种首屏就要用的组件,千万别 lazy,否则会闪白屏。我吃过这个亏,改完后 FCP 反而变差了。

优化后:流畅多了

改完上线后,实测效果立竿见影。用 Lighthouse 测了一下:

  • 首屏加载时间从 5.2s 降到 820ms
  • 初始 JS bundle 从 3.2MB 降到 680KB(gzip 后)
  • TTI(可交互时间)从 4.1s 降到 1.1s

最关键的是,用户再也不用盯着白屏发呆了。现在进登录页,不到 1 秒就能点按钮,体验提升不是一点半点。

当然,也不是完美无缺。比如那个 shared chunk,因为包含了一些通用 hooks,偶尔会因为 tree-shaking 不彻底带进没用的代码。但影响不大,后续可以再细拆,目前这个方案已经够稳了。

性能数据对比

为了更直观,我整理了优化前后关键指标(本地 3G 网络模拟):

指标 优化前 优化后 降幅
首屏加载时间 5200ms 820ms 84%
初始 JS 体积 (gzip) 980KB 680KB 31%
最大内容绘制 (LCP) 4800ms 1200ms 75%
可交互时间 (TTI) 4100ms 1100ms 73%

数据不会骗人。虽然拆包不是银弹,但在这种“大杂烩”项目里,绝对是性价比最高的优化手段之一。

结尾碎碎念

其实 Common 分离说白了就是“别让所有页面背同一个大锅”。该拆的拆,该懒加载的懒加载,别怕麻烦。Webpack 的 SplitChunks 虽然配置有点绕,但一旦配对了,收益巨大。

以上是我这次优化的完整过程,有细节没写全的地方欢迎评论区交流。如果你也在搞老项目性能优化,不妨试试这套组合拳——亲测有效。

这个技巧的拓展用法还有很多,比如结合 prefetch、预加载关键路由,后续我会继续分享这类实战博客。毕竟,前端优化这事儿,永远在路上。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论