路由懒加载——Vue-Router异步组件+Webpack代码分割

504 阅读5分钟

🧑Vue+Webpack路由懒加载-HowieCong

1. 使用背景

graph LR
项目规模大 --> JS包体积会变大 --> 页面首次加载时间过长 --> 影响用户体验

2. 解决方法

  • 使用路由懒加载,结合Vue异步组件Webpack代码分割来优化

3. Vue异步组件基础

3.1 异步组件的概念

  • 一种定义组件的方式,它允许组件在需要时才被加载,而不是在应用初始化时就全部加载

3.2 基础语法

  • 同步组件定义

    1. import MyComponent from './MyComponent.vue';

    2. 然后在components选项中注册:components:{ MyComponent}

  • 异步组件定义

    1. const MyAsyncComponent = () => import('./MyComponent.vue')

    2. 此时这里是返回一个函数,函数内部使用import()语法,是一个动态导入函数,它返回一个Promise

    3. 当组件需要被渲染时,这个Promise才会被解析,从而加载组件的代码

3.3 错误处理与异步加载状态展示

  • 背景:异步加载时可能会出现网络问题或模块加载错误的情况,可以添加错误处理逻辑,例子:
const MyAsyncComponent = () =>{
    const AsyncView = import('./MyAsyncComponent.vue');
    return new Promise((reslove,reject) => {
        AsyncView.then(module => reslove(module)).catch(error => {
            // 可以在这里记录错误日志,或者显示一个友好的错误提示组件
            console.error('组件加载失败',error);
            reject(error);
        });
    });
};
  • 在加载组件的过程中,可以展示一个加载状态指示器(如加载动画),提升用户体验。在Vue中,可以在组件级别添加一个v-if指令来控制加载状态组件和异步加载组件的显示,例子:
<template>
  <div>
    <LoadingSpinner v - if="isLoading"></LoadingSpinner>
    <AsyncComponent v - else></AsyncComponent>
  </div>
</template>
<script>
    import LoadingSpinner from './LoadingSpinner.vue';
    const AsyncComponent = () => import('./MyComponent.vue');
    export default {
      components: {
        LoadingSpinner,
        AsyncComponent
      },
      data() {
        return {
          isLoading: true
        };
      },
      mounted() {
        AsyncComponent().then(() => {
          this.isLoading = false;
        });
      }
};
</script>

4. Webpack代码分割与路由懒加载的原理

4.1 代码分割的原理

  • Webpack允许将代码分割成多个chunks

  • 根据代码的依赖关系动态导入的情况,把不同的模块分别打包到不同的文件中

4.2 结合路由懒加载

  • Vue-Router中,路由配置可以使用异步组件来实现懒加载

  • eg:对于一个路由配置,关于Webpack注释是告诉Webpack将这个组件的代码打包到一个名为home单独的 chunk 文件中。用户访问/home路径时,对应的Home.vue组件代码才会被加载,而不是在应用启动时就加载所有路由组件的代码

const router = new VueRouter({
    routes:[
        {
            path: '/home',
            name:'Home',
            /* webpackChunkName: "name" */
            component: () => import(/* webpackChunkName: "name" */ './views/Home.vue' )
        }, 
        {
            path: '/about',
            name:'About', 
            /* webpackChunkName: "about" */
            component: () => import(/* webpackChunkName: "about" */ './views/About.vue' )
        }
    ]
});

4.3 代码分割的粒度控制

  • 背景:根据项目实际情况控制代码分割的粒度,如果每一个组件都单独分割成一个代码块,可能会导致过多的网络请求,增加加载开销。

  • 方法:通过合理设置WebpackChunkName来实现

  • Eg:对于一个用户管理模块,包括用户列表、用户详情和用户编辑组件,可以将它们打包到一个名为user-module的代码块中。

const router = new VueRouter({
  routes: [
    {
      path: '/user/list',
      name: 'UserList',
      component: () => import(/* webpackChunkName: "user - module" */ './views/UserList.vue')
    },
    {
      path: '/user/detail',
      name: 'UserDetail',
      component: () => import(/* webpackChunkName: "user - module" */ './views/UserDetail.vue')
    },
    {
      path: '/user/edit',
      name: 'UserEdit',
      component: () => import(/* webpackChunkName: "user - module" */ './views/UserEdit.vue')
    }
  ]
});

4.4 Webpack相关配置

  • 以上文章讲了配置import()语法和webpackChunkName注释

  • optimization.splitChunks来控制代码块的生成规则,例如设置最小代码块大小、共享模块的抽取等

  • 配置output.chunkFilename决定了生成的代码块文件名的格式

4.5 Vue-Router深度考察

  • Eg:如何在一个异步加载的组件有子路由,子路由组件如何进行懒加载?

  • Answer:子路由组件的懒加载方式与父路由类似。父路由可以使用chidren属性来配置子路由,子路由组件同样可以使用import()函数进行异步加载

  • Eg:

const router = new VueRouter({
    routes:[
        {
            path: '/parent',
            component: () => import('./Parent.vue'),
            children: [
                {
                    path:'child',
                    component: () => import('./Child.vue')
                }
            ]
        }
    ]
});      

5. 优点(实际场景下)

graph LR
用户导航到特定路由 --> 对应组件代码被加载 --> 减少初始加载JS包大小 --> 加快页面加载速度

6. 预加载Pre-loading策略(进一步性能优化)

  • 使用方法:使用vue-routerlink-exact-active类或者mouseenter事件来触发预加载

  • EG:通过在路由的meta字段设置preload属性来标记需要预加载的路由,当用户导航到其他路由时,如果符合预加载条件,就提前加载目标路由的组件。

// 利用vue-router的导航守卫进行预加载
router.beforeEach((to, from, next) => {
  const toRoute = router.resolve(to).route;
  const fromRoute = router.resolve(from).route;
  if (toRoute.meta.preload && fromRoute.name!== toRoute.name) {
    const component = () => import(/* webpackChunkName: "preload - component" */ toRoute.component);
    component().then(() => {
      console.log('预加载成功');
      next();
    }).catch(() => {
      console.log('预加载失败');
      next();
    });
  } else {
    next();
  }
});

7. 对比(其他性能优化方式)

  • Tree - Sharking(摇树优先):在Webpack中通过配置optimization.usedExports来启用,可以在打包时剔除没有被使用的代码

  • 代码压缩:例如使用 UglifyJS 或 Terser 等能有效减少包的大小

  • CDN加载第三方库:将不经常变化的库从主应用包中分离出来,加快加载速度

8. 如何合理选择路由懒加载(复杂场景下)

  • Eg:如果一个应用有多个不同权限得到用户角色,如何根据用户角色来进行更合理的路由懒加载?

  • Answer:可以在用户登录后,根据用户角色获取对应的路由配置。比如:管理员,可以加载包含管理功能的路由组件,这些组件可以通过懒加载方式加载。可以在路由守卫或者应用初始化的阶段,根据用户角色信息动态生成路由表,对于非必要的路由组件(根据用户的角色去判断)进行懒加载,减少不必要的资源加载。

❓其他

1. 疑问与作者HowieCong声明

  • 如有疑问、出错的知识,请及时点击下方链接添加作者HowieCong的其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong

  • 若想让作者更新哪些方面的技术文章或补充更多知识在这篇文章,请及时点击下方链接添加里面其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong

  • 声明:作者HowieCong目前只是一个前端开发小菜鸟,写文章的初衷只是全面提高自身能力和见识;如果对此篇文章喜欢或能帮助到你,麻烦给作者HowieCong点个关注/给这篇文章点个赞/收藏这篇文章/在评论区留下你的想法吧,欢迎大家来交流!

2. 作者社交媒体/邮箱-HowieCong