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

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,动态??橛呕虬?br> 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整个包??梢栽赼pp.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

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容