如何利用 Vue CLI/Vite 实现代码分割(Code Splitting)和路由懒加载,优化首次加载性能?

各位靓仔靓女,晚上好!我是你们今晚的码农导师,代号“Bug终结者”,很高兴能和大家一起聊聊Vue项目性能优化的大杀器——代码分割(Code Splitting)和路由懒加载。

今天的讲座,咱们用最接地气的方式,把这些听起来高大上的概念掰开了揉碎了讲明白,保证你们听完就能上手,让你们的Vue项目起飞!

一、什么是代码分割?为什么要用它?

想象一下,你的Vue项目就像一个巨大的包裹,里面装着所有的代码、组件、库等等。当用户第一次访问你的网站时,浏览器需要下载这个巨型包裹,才能把你的网站展示出来。这就像你买了个几百斤重的快递,快递小哥吭哧吭哧搬上来,你才能打开看看里面是啥。

如果这个包裹太大,用户就需要等待很长时间,体验自然就差了。

代码分割就是要把这个巨型包裹拆分成多个小包裹。当用户只需要访问网站的一部分功能时,浏览器只需要下载对应的小包裹即可。这就大大减少了首次加载所需的代码量,提升了用户体验。

举个栗子,你的网站有首页、关于我们、联系我们三个页面。如果没有代码分割,用户访问首页时,浏览器需要下载所有三个页面的代码。有了代码分割,用户访问首页时,只需要下载首页的代码即可,其他页面的代码只有在用户访问它们时才会被下载。

二、Vue CLI/Vite 怎么实现代码分割?

Vue CLI 和 Vite 都内置了对代码分割的支持,让我们实现代码分割变得非常简单。

1. Vue CLI

Vue CLI 默认使用 webpack 作为构建工具,webpack 会自动进行代码分割。但是,我们需要做一些配置才能更好地利用代码分割。

  • 路由懒加载: 这是最常见的代码分割方式。

    在 Vue Router 中,我们可以使用 import() 语法来实现路由懒加载。

    // 传统的引入方式
    // import Home from './components/Home.vue'
    // import About from './components/About.vue'
    
    // 使用懒加载
    const Home = () => import('./components/Home.vue')
    const About = () => import('./components/About.vue')
    
    const routes = [
      { path: '/', component: Home },
      { path: '/about', component: About }
    ]

    这样,HomeAbout 组件的代码只有在对应的路由被访问时才会被下载。

  • 动态 import() 在组件内部,你也可以使用动态 import() 来加载模块。

    <template>
      <button @click="loadComponent">加载组件</button>
      <component :is="dynamicComponent" />
    </template>
    
    <script>
    import { defineAsyncComponent } from 'vue'
    
    export default {
      data() {
        return {
          dynamicComponent: null
        }
      },
      methods: {
        async loadComponent() {
          this.dynamicComponent = defineAsyncComponent(() => import('./components/MyComponent.vue'))
        }
      }
    }
    </script>

    在这个例子中,MyComponent.vue 的代码只有在 loadComponent 方法被调用时才会被下载。 defineAsyncComponent 是Vue3提供的异步组件,可以更优雅的处理加载状态。

  • webpackChunkName 我们可以使用 webpackChunkName 注释来指定代码分割后的 chunk 名称。

    const MyComponent = () => import(/* webpackChunkName: "my-component" */ './components/MyComponent.vue')

    这样,webpack 会把 MyComponent.vue 的代码打包到一个名为 my-component.js 的 chunk 中。这可以方便我们更好地管理代码分割后的 chunk。

  • splitChunks 配置 (webpack): vue.config.js 文件中,你可以自定义 webpack 的 splitChunks 配置,更细粒度地控制代码分割。

    // vue.config.js
    module.exports = {
      configureWebpack: {
        optimization: {
          splitChunks: {
            cacheGroups: {
              vendor: {
                test: /[\/]node_modules[\/]/,
                name: 'vendor',
                chunks: 'all'
              },
              common: {
                name: 'common',
                minChunks: 2,
                chunks: 'async',
                reuseExistingChunk: true
              }
            }
          }
        }
      }
    }

    这个配置会将 node_modules 中的代码打包到一个名为 vendor.js 的 chunk 中,并将公共的代码打包到一个名为 common.js 的 chunk 中。

2. Vite

Vite 使用 Rollup 作为构建工具,Rollup 同样支持代码分割。Vite 的代码分割策略更加激进,几乎所有的模块都会被分割成独立的 chunk。

  • 路由懒加载: 和 Vue CLI 一样,Vite 也支持路由懒加载。

    // 路由懒加载,与Vue CLI一致
    const Home = () => import('./components/Home.vue')
    const About = () => import('./components/About.vue')
    
    const routes = [
      { path: '/', component: Home },
      { path: '/about', component: About }
    ]
  • 动态 import() Vite 也支持动态 import()。用法与 Vue CLI 相同。

  • Vite 的自动代码分割: Vite 会自动分析你的代码,并将模块分割成独立的 chunk。你通常不需要手动配置代码分割。Vite 的默认配置已经足够优秀。

三、代码分割的策略和注意事项

  • 按需加载: 只加载用户需要访问的代码。这是代码分割的核心原则。

  • 公共模块提取: 将多个模块共享的代码提取到公共 chunk 中,避免重复加载。

  • 第三方库优化: 将第三方库打包到独立的 chunk 中,方便浏览器缓存。 对于特别大的第三方库,可以考虑按需引入,或者使用 CDN 加速。

  • 谨慎分割: 不要过度分割代码,否则会导致过多的 HTTP 请求,反而影响性能。

  • 缓存策略: 合理设置缓存策略,让浏览器缓存 chunk,减少重复下载。 确保你的服务器配置了正确的缓存头(Cache-Control, Expires)。

    • Cache-Control: public, max-age=31536000 (一年) 对于静态资源非常常见.
  • Gzip 压缩: 开启 Gzip 压缩,减小 chunk 的大小。

  • 预加载(Preload/Prefetch): 使用 <link rel="preload"><link rel="prefetch"> 提示浏览器提前加载资源。

    • preload 告诉浏览器立即下载资源,用于当前页面需要的关键资源。
    • prefetch 告诉浏览器在空闲时间下载资源,用于将来可能需要的资源。
    <link rel="preload" href="/js/home.js" as="script">
    <link rel="prefetch" href="/js/about.js" as="script">

    在 Vue CLI 中,可以使用 webpack-plugin-preload 插件。 Vite内置了对预加载的支持。

  • 分析工具: 使用 webpack-bundle-analyzer 或 Vite 的分析工具来分析代码分割后的 chunk,找到优化空间。

    • Vue CLI: 安装 webpack-bundle-analyzer 并配置 vue.config.js:

      const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
      
      module.exports = {
        configureWebpack: {
          plugins: [
            new BundleAnalyzerPlugin()
          ]
        }
      };

      运行 vue-cli-service build --report 会生成一个报告。

    • Vite: 运行 vite build --analyze 会生成一个报告。

四、代码示例:一个完整的 Vue CLI 项目代码分割示例

  1. 项目结构:

    my-vue-project/
    ├── src/
    │   ├── App.vue
    │   ├── components/
    │   │   ├── Home.vue
    │   │   ├── About.vue
    │   │   ├── Contact.vue
    │   ├── router/
    │   │   └── index.js
    │   └── main.js
    ├── vue.config.js
    ├── package.json
    └── ...
  2. src/router/index.js

    import { createRouter, createWebHistory } from 'vue-router'
    
    const Home = () => import(/* webpackChunkName: "home" */ '../components/Home.vue')
    const About = () => import(/* webpackChunkName: "about" */ '../components/About.vue')
    const Contact = () => import(/* webpackChunkName: "contact" */ '../components/Contact.vue')
    
    const routes = [
      {
        path: '/',
        name: 'Home',
        component: Home
      },
      {
        path: '/about',
        name: 'About',
        component: About
      },
      {
        path: '/contact',
        name: 'Contact',
        component: Contact
      }
    ]
    
    const router = createRouter({
      history: createWebHistory(),
      routes
    })
    
    export default router
  3. vue.config.js

    const { defineConfig } = require('@vue/cli-service')
    const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
    
    module.exports = defineConfig({
      transpileDependencies: true,
      configureWebpack: {
        optimization: {
          splitChunks: {
            cacheGroups: {
              vendor: {
                test: /[\/]node_modules[\/]/,
                name: 'vendor',
                chunks: 'all'
              },
              common: {
                name: 'common',
                minChunks: 2,
                chunks: 'async',
                reuseExistingChunk: true
              }
            }
          }
        },
        plugins: [
          new BundleAnalyzerPlugin() // 开启分析工具
        ]
      }
    })
  4. 运行 npm run buildyarn build

  5. 运行 vue-cli-service build --report,查看生成的报告。

    你会在报告中看到类似以下的 chunk:

    • app.js:主应用代码
    • vendor.js:第三方库代码
    • home.js:Home 组件代码
    • about.js:About 组件代码
    • contact.js:Contact 组件代码
    • common.js:公共代码

五、代码分割的常见问题与解决方案

  • Q:代码分割后,页面加载速度反而变慢了?

    • A:可能是因为过度分割导致了过多的 HTTP 请求。检查你的代码分割策略,避免过度分割。同时,确保你的服务器开启了 Gzip 压缩,并设置了合理的缓存策略。
  • Q:动态 import() 报错?

    • A:确保你的项目配置正确,支持动态 import() 语法。在 Vue CLI 中,你需要安装 @babel/plugin-syntax-dynamic-import 插件。 Vite 默认支持。
  • Q:如何判断代码分割是否生效?

    • A:在浏览器开发者工具的 Network 面板中,查看加载的 chunk。如果只有需要的 chunk 被加载,说明代码分割生效了。
  • Q:预加载(Preload/Prefetch)应该怎么用?

    • A:preload 用于当前页面需要的关键资源,比如首屏图片、关键 CSS/JS 文件。prefetch 用于将来可能需要的资源,比如其他页面的组件代码。

六、总结

代码分割和路由懒加载是 Vue 项目性能优化的重要手段。通过合理的代码分割,我们可以减少首次加载所需的代码量,提升用户体验。

记住,代码分割不是银弹,需要根据实际情况进行调整。使用分析工具,找到优化空间,才能达到最佳效果。

希望今天的讲座对大家有所帮助。 祝大家早日成为代码分割大师,写出飞快的 Vue 应用! 如果大家在实践中遇到问题,欢迎随时提问。

下次有机会再见,各位靓仔靓女! 拜拜!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注