美文网首页echarts中国JavaScript 进阶营
vue2 splitchunk分包优化项目实战&技术分享

vue2 splitchunk分包优化项目实战&技术分享

作者: 游走在城市的鱼 | 来源:发表于2023-05-08 18:15 被阅读0次

    1.目标

    结合vue2项目聊一下优化的思路。主要聊一下webpack分包方向。
    项目环境:Vue2 + webpack4
    项目结构:pc和mobile集成在一个项目中,PC端:element-ui + vue(部分页面会用到vant + jkUI),Mobile: vant + vue + jkUI
    分析工具:Chrome、webpack-bundle-analyzer插件

    2.前置工作

    安装插件:

     npm i webpack-bundle-analyzer -D
    

    配置脚本:

    "analyze": "cross-env NODE_ENV=production ANALYZER=true vue-cli-service build"
    

    修改配置(vue.config.js):

    const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
    // ...省略部分代码
    // chainWebpack方式
    config
      .when(process.env.ANALYZER == 'true', config => {
        config.plugin('webpack-bundle-analyzer').use(BundleAnalyzerPlugin)
      })
    
    // configureWebpack方式
    process.env.ANALYZER == "true" && config.plugins.push(new BundleAnalyzerPlugin());
    

    3.启动项目分析资源加载

     npm run dev
    
    图1-f12查看network.png

    简单分析上图,可以得出结论:
    1.移动端加载了非必要资源:element-ui
    2.vendors体积过大,导致后续加载阻塞
    接下来先从这两个方向分析bundle问题

    4.项目bundle分析

    npm run analyze
    

    以cms项目代码为例,执行以上命令分析


    图2-初始bundle分布.png 图3-bundle大小.png

    结合之前的结论和上面的bundle分布图,决定从下面几个方向进行优化:
    1.分离jk-ui组件库
    2.分离lodash库
    3.分离moment库

    5.开始优化

    webpack4中主要通过splitchunk来进行分包操作

    // 项目初始配置
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        libs: {
          name: 'chunk-libs',
          test: /[\\/]node_modules[\\/]/,
          priority: 10,
          chunks: 'initial' // only package third parties that are initially dependent
        },
        elementUI: {
          name: 'chunk-elementUI',
          priority: 20, 
          test: /[\\/]node_modules[\\/]_?element-ui(.*)/, 
          enforce: true
        },
        echarts: {
          name: 'chunk-echarts', 
          priority: 12, 
          test: /[\\/]node_modules[\\/]_?echarts(.*)/
        },
        commons: {
          name: 'chunk-commons',
          test: resolve('src/components'), 
          minChunks: 3, 
          priority: 5,
          reuseExistingChunk: true
        }
      }
    }
    

    介绍一下all, initial, async(默认)区别:
    all: 把动态和非动态模块同时进行优化打包;所有模块都扔到 vendors.bundle.js 里面
    initial: 把非动态模块打包进 vendor,动态模块优化打包
    async: 把动态模块打包进 vendor,非动态模块保持原样(不优化)

    结合图2-bundle分布图分析修改splitchunk配置:

    // 修改后配置
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        libs: {
          name: 'chunk-libs',
          test: /[\\/]node_modules[\\/]/,
          priority: 10,
          chunks: 'all'
        },
        elementUI: {
          priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
          test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
          reuseExistingChunk: true
        },
        jkUI: {
          priority: 20, 
          test: /[\\/]node_modules[\\/]_?jk-ui(.*)/,
          maxSize: 460800, // gzip 150kb
          minSize: 245760, 
          reuseExistingChunk: true
        },
        lodash: {
          priority: 20, 
          test: /[\\/]node_modules[\\/]_?lodash(.*)/,
          reuseExistingChunk: true
        },
        moment: {
          priority: 20, 
          test: /[\\/]node_modules[\\/]_?moment(.*)/,
          reuseExistingChunk: true
        }
      }
    }
    
    图4-分包后bundle分布.png
    图5-分包后bundle大小.png

    6.根据bundle分析代码

    对项目代码进行分析,可以看出一些明显的问题:


    图6-lodash全局引入问题.png
    图7-moment全局引入问题.png
    图8-element-ui全局引入问题.png

    1.lodash全局引入,改成按需加载
    2.moment引入,可以采用day.js替换
    3.element-ui是全局引入的,包体积有点偏大,改成按需加载

    7.进一步优化

    1.将引入lodash的地方改为按需引入,或者改成utils自己封装的方法

    import { throttle } from 'lodash'
    import { debounce } from 'lodash'
    import { cloneDeep } from 'lodash'
    // 改为
    import throttle from 'lodash/throttle'
    import debounce from 'lodash/debounce'
    import cloneDeep from 'lodash/cloneDeep'
    
    图9-优化后的lodash体积减小.png

    重新运行分析后发现lodash体积非常小,并且没有单独分离出来,可以删除splitchunk中的lodash配置

    2.将引入momentjs的地方,采用dayjs替换

    // 去除moment引用并安装dayjs
    moment().format('YYYY-MM-DD')
    moment(date).isValid()
    // 改为
    dayjs().format('YYYY-MM-DD')
    dayjs(date).isValid()
    

    重新运行分析后momentjs包被换掉,总体体积减小,可以删除splitchunk中的moment配置


    图10-优化moment使用后总体bundle体积减小.png

    3.接下来优化element-ui部分

    在这之前我们先看一下jk-ui部分的加载问题


    图11-访问mobile对应的路由,加载了jk-ui组件库.png 图12-访问pc下的cms-page-list路由,也加载了jk-ui组件库.png

    显然这个地方不是我们理想的情况,理想情况下应该是移动端才会加载jk-ui组件库,也就是按需加载,只有用到的页面才会加载对应的库,分析一下原因,应该是由于我们对于jk-ui配置的问题,当我们不设置chunks时,默认继承spilitChunks.chunks属性,也就是all,设置为all的chunks会被默认加载,现在我们改为async重新运行看看

    jkUI: {
      priority: 20,
      test: /[\\/]node_modules[\\/]_?jk-ui(.*)/,
      maxSize: 460800,
      minSize: 245760,
      reuseExistingChunk: true,
      chunks: 'async' // 新增,之前为默认继承外层的all属性
    }
    

    重新运行后访问两个页面路由:


    图13-重新访问mobile路由,正常加载jk-ui组件库资源.png 图14-重新访问pc端路由,jk-ui资源不再加载.png

    通过上述实践,当动态加载的资源,设置成async之后被分离出来单独的chunk,只会在用到的地方才加载,设置成all则不行。

    回到我们开始的话题,继续优化element-ui库,从上述图13中其实可以看到,mobile路由加载了element资源,其实这并不是我们所希望的,所以分两个点来优化element-ui。(重点:需要保证element-ui为动态加载,并且需要设置为async模式。)
    -第一个目标:优化element-ui体积
    -第二个目标:实现element-ui在pc端路由才加载

    图15-原element-ui加载代码.png

    查看项目代码,可以看出element-ui是全局加载的,会导致element-ui包体积很大,这一点可以通过按需引入来解决问题。从之前的bundle分析中大概可以看到element-ui的体积大概为159kb(gziped)

    由于element-ui的范围较广,选取一个pc页面cms-page-list作为示例:

    // 删除main.js全局加载
    if (!isMobile()) {
      // require('element-ui/lib/theme-chalk/index.css')
      // const ElementUI = require('element-ui')
      // Vue.use(ElementUI, { size: 'small' })
    } else {
      const sensors = require('@/utils/sensors').default
      Vue.prototype.$sensors = sensors
    }
    
    // babel.config.js配置按需引入,参考下element官网
    [
      'component',
      {
        libraryName: 'element-ui',
        styleLibraryName: 'theme-chalk'
      }
    ]
    
    // 页面级别按需引入element组件,页面中子组件引用的element组件也需要按相同方式引入
    import { Input, Button, Form, Select, DatePicker, Table, TableColumn } from 'element-ui'
    // 省略...
    components: {
      ElInput: Input,
      ElButton: Button,
      ElForm: Form,
      ElSelect: Select,
      ElDatePicker: DatePicker,
      ElTable: Table,
      ElTableColumn: TableColumn
    }
    
    // 删除utils/decorator.js首行引用,否则会造成mobile页面引用关系,由于mobile页面中引用了decorator文件,会导致element被引入
    import MessageBox from 'element-ui'
    
    // 修改splitChunks中element-ui相关配置
    elementUI: {
      priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
      test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
      reuseExistingChunk: true,
      chunks: 'async'  // 新增,之前为默认继承外层的all属性
    }
    

    优化后:


    图16-优化element-ui后pc正常加载element-ui资源,且体积减小.png
    图17-优化element-ui后mobile不加载element-ui资源.png
    图18-优化后bundle分布图.png
    图19-优化后chunk体积减小.png

    从上图看出chunk-libs中有vant库,可以采用同样的方式,结合按需加载和async模式抽离,防止在pc端页面冗余加载vant。

    // 新增
    vant: {
      test: /[\\/]node_modules[\\/]vant[\\/]/,
      priority: 20,
      reuseExistingChunk: true,
      chunks: 'async'
    }
    
    图20-分离vant后bundle体积.png

    优化后vant从chunk-libs中分离,且在引用到的页面中才会加载。


    图21-分离vant后mobile正常加载vant库.png
    图22-分离vant后pc不加载vant库.png

    8.最终配置

    splitChunks: {
      chunks: 'all',
      minChunks: 1,
      maxAsyncRequests: 30, // 最多30个请求
      maxInitialRequests: 30, // 最多首屏加载30个请求
      cacheGroups: {
        libs: {
          name: 'chunk-libs',
          test: /[\\/]node_modules[\\/]/,
          priority: 10
        },
        elementUI: {
          priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
          test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
          reuseExistingChunk: true,
          chunks: 'async'
        },
        jkUI: {
          priority: 20,
          test: /[\\/]node_modules[\\/]_?jk-ui(.*)/,
          maxSize: 460800,
          minSize: 245760,
          reuseExistingChunk: true,
          chunks: 'async'
        },
        vant: {
          test: /[\\/]node_modules[\\/]vant[\\/]/,
          priority: 100,
          reuseExistingChunk: true,
          chunks: 'async'
        }
    }
    

    9.总结:

    1.element-ui不能直接引入,否则无法在分包后达到最优体积,直接import('element-ui')或者import ElementUI from 'element-ui'都会在最后打包生成chunk时生成包含element全量包,所以要采用页面级别引入组件的方式来做按需引入。
    2.入口文件main.js中也不能通过import { MessageBox } from 'element-ui'Vue.prototype.$message = MessageBox方式挂在到Vue的原型上,否则也会导致生成的chunk包含element整个包。可以在app.vue文件中挂载

    import MessageBox from 'element-ui'
    // 省略...
    created() {
      Vue.prototype.$MessageBox = MessageBox
    }
    

    3.路由懒加载的页面中,import xxx from 'xxx'可以看作动态导入。
    4.import('xxx')为动态导入。

    问题:
    1.为什么vant会被分为多个chunk?
    2.分离chunk会额外生成一个css,如何合并?

    拓展:
    js新特性Import Maps:https://mp.weixin.qq.com/s/6KV1Q-7Wvwb-8E81fTooWA

    相关文章

      网友评论

        本文标题:vue2 splitchunk分包优化项目实战&技术分享

        本文链接:https://www.haomeiwen.com/subject/vatlsdtx.html