splitChunk和spa初衷相背吗

23 阅读3分钟

在如今ssr大行其道的场景下,市面上还是有很多有需要维护和优化的陈旧SPA项目,今天我们就研究下这些项目的优化方案之一--splitChunk分包 我们知道,SPA设计的初衷是单页面应用体验,这理念主要包括:

  • 单次加载全部资源
  • 页面切换无刷新
  • 应用状态保持连贯

而在我们应用splitChunk分包之后:

  • 按需加载导致路由切换时需要加载新 chunk
  • 首次加载虽快,但后续导航可能出现延迟
  • 打破了"一次性加载所有资源"的特性

看似,这两个理念有冲突,这对开发来说,都是涉及用户体验的一种抉择,当然,最终的无缝单页面体验肯定是我们追求的,所以目前有几个优化方案可以兼容两者。以下以vue2为例。

1. 基于路由分包

牺牲部分体验:

  • 保持核心框架和常用组件在主包
  • 按路由分离非关键页面
  • 预加载后续路由资源(见下文)

2. 核心/非核心分离

// vue.config.js
module.exports = {
  configureWebpack: {
    optimization: {
      splitChunks: {
        chunks: 'all',
        cacheGroups: {
          core: {
            test: /[\\/]node_modules[\\/](vue|vue-router|vuex|core-js)/,
            name: 'core',
            priority: 10,
            chunks: 'initial'
          },
          vendors: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            priority: 5,
            chunks: 'initial'
          }
        }
      }
    }
  }
}

3. 预加载

<!-- 自动生成的preload标签 --> <link rel="preload" href="/js/chunk-about.js" as="script">

主动预加载策略:

// 路由配置中添加预加载
{
  path: '/about',
  component: () => import('./views/About.vue'),
  meta: { preload: true } // 自定义标记
}

// 路由守卫中实现预加载
router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.preload)) {
    const components = to.matched.map(record => record.components.default)
    components.forEach(component => {
      if (typeof component === 'function') component()
    })
  }
  next()
})

平衡点:用户体验优先

使用了上面的策略,我们的理想状态应达到:

  1. 首屏极快:主包控制在 100-200KB 以内

  2. 导航流畅

    • 关键路由预加载
    • 非关键路由按需加载+骨架屏
  3. 缓存优化

    • 长期缓存核心包(文件名带hash)
    • 共享公共依赖包

当然,如果项目实在太大,并且优化需求极高,我们还需要进阶优化,那么可以使用以下的方案

1. 按设备能力动态加载

// 根据网络条件动态加载不同资源
const isSlowNetwork = navigator.connection 
  ? navigator.connection.effectiveType.includes('2g')
  : false;

const loadVendor = isSlowNetwork 
  ? import('./vendor-light.js')
  : import('./vendor-full.js');

2. 渐进式hydration

// 对非关键组件延迟hydration
export default {
  components: {
    HeavyComponent: () => ({
      component: import('./HeavyComponent.vue'),
      delay: 2000 // 延迟2秒加载
    })
  }
}

3. Webpack 魔法注释优化(vue2为例)

// 组合使用webpack注释
const Home = () => import(
  /* webpackChunkName: "home" */
  /* webpackPrefetch: true */
  /* webpackPreload: true */
  './views/Home.vue'
)

结论

分包不会真正破坏 SPA 的初衷,当合理使用时:

  1. 保持了单页面体验:通过预加载和智能缓存
  2. 优化了实际性能:比单体大包有更好的用户体验
  3. 平衡了开发体验:仍然保持单代码库的优势

实验下来,优化的关键是要采用预测性加载策略,这提升最大,让用户在感知上仍然是无缝的单页面体验,同时获得分包带来的性能优势。