Vue 的打包优化之路

Vue的打包优化之路

场景分析

一个结构简单 依赖蛮多的可视化项目,用到的库有

  1. vue + vue-router + vuex + axios
  2. echarts + 全省份地图文件 + 中国地图文件
  3. elementui
  4. moment(后面被date-fns代替)
  5. lodash lodash-decorator(用到了装饰器)

ECharts的JSON地图文件占了很大一部分,大概有1.96MB gzip以后900KB,这一部分是没有办法做处理的。然后ECharts也应该使用按需加载

接着是依赖的一些公共库,比如Vue全家桶,这部分是可以提取到 cdn 的

最后是一些类似 lodash moment的工具库,可能只引用到了部分功能,但是默认会加载全部包,这样是不划算的。lodash moment默认都不支持 three-shake 因此需要手动按需加载或者使用更小体积的库。

先来看看默认没有经过优化的打包分析

默认配置

只做了简单的异步路由加载,各种库均没有按需引用


vue-bundle-1.png

这一个地方在体积处理上没有更好的优化办法

vue-bundle-2.png

这里可以看到ECharts的库占了很大一部分 然后是 moment elementui这些库

vue-bundle-3.png

初步优化

因为业务的需求 需要用到地图相关信息所以这部分体积是没法减少的。但是并不是初次进入页面就需要加载这些文件。我们知道 ECharts 是通过 ECharts.registerMap('china', China) 注册地图的。 这个注册是可选的,并且仅仅是地图相关的组件会用到,因此我们单独封装这个地图组件,在组件内部注册,并通过异步路由切割,达到分片的目的

//...
import China from 'echarts/map/json/china.json'
import provinceList from './provinceList.json'

// 注册地图
ECharts.registerMap('china', China)
provinceList.forEach(pro => {
  const map = require('echarts/map/json/province/' + pro.path)
  ECharts.registerMap(pro.py, map)
})

@Component
export default class AreaChart extens Vue {}
//...

另外一个优化点在于默认引入 ECharts 是引入全部的 import * as ECharts from 'echarts' 我们只需要部分组件,因此单独定义一个echarts文件, 这样我们所有引用 ECharts的文件,都改为引用这个文件就实现了按需加载

# echarts.ts
import ECharts from 'echarts/lib/echarts'

import 'echarts/lib/chart/bar'
import 'echarts/lib/chart/line'
import 'echarts/lib/chart/pie'
import 'echarts/lib/chart/funnel'
import 'echarts/lib/chart/scatter'
import 'echarts/lib/component/title'
import 'echarts/lib/component/legend'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/axisPointer'
import 'echarts/lib/component/visualMap'
import 'echarts/lib/component/markLine'
import 'echarts/lib/component/markPoint'
import 'echarts/lib/component/markArea'
import 'echarts/lib/component/geo'

export { ECharts }

在 vendors 中还有 moment 这个很大其实我只用到了moment(current).subtract(1, 'day').format('YYYY-MM-DD') 这几个基本功能, 这样的使用引入全部 moment 是不值得,因此改为了 date-fns 然后按需引入了部分功能。上面代码等价转换为 format(subDays(parse(now), 8), 'YYYY-MM-DD') 然而我们只需要按需引入这三个函数就ok了

vue-bundle-4.png
vue-bundle-5.png
vue-bundle-6.png

moment 也被替换为了date-fns

vue-bundle-8.png
vue-bundle-9.png

CDN进一步优化

在初步优化完成后,我们发现 vue 全家桶以及 elemenUI i仍然占了很大一部分 vendors 体积,这部分代码是不变的,但会随着每次 vendor 打包改变 hash 重新加载。我们可以使用 cdn 剔除这部分不经常变化的

另外一方面我们的页面很小,但是因为异步路由加载分割了好几块。小碎片的加载也是影响浏览器性能要素之一,我们通过更改打包策略解决

使用 webpack-cdn-plugin 插件

WebpackCDNPlugin

过去我们外部引入 CDN 需要手动编写 index.html 模板,在里面指定加载的版本,通过这个插件就能自动的把指定的公共库写入到 index.html 模板里,目前的文档有坑,我已经提了PR

需要注意的是,通过CDN引入,在使用 VueRouter Vuex ElmentUI 的时候要改下写法。CDN会它们挂载到window上,因此不再使用 Vue.use(xxx)

import Vue from 'vue'
import VueRouter from 'vue-router'

if (!window.VueRouter) Vue.use(VueRouter)

更改打包策略

通过webpack-chunk-name 合并一些包

const A1 = () => import(/* webpackChunkName: "A" */ '@/views/A1')
const A2 = () => import(/* webpackChunkName: "A" */ '@/views/A2')
const A3 = () => import(/* webpackChunkName: "A" */ '@/views/A3')

[图片上传失败...(image-61ee85-1544087237201)]

剔除全家桶以后,剩下的需要首次加载 vendor 就很小了

优化后的数据

(只显示gzip大小) Vendors Echarts Moment or date-fns
默认打包 601.48kb 195.41kb 66.45kb
初步优化 410.66kb 104.87kb 7kb
CDN优化 206.78kb 104.85kb 7kb

优化的意义与思路

我们优化的目的除了写KPI和升职答辩外,最主要的就是提升用户的体验。

提升首次访问的渲染速度。影响首次渲染速度除了代码上的优化之外就是网络传输的速度。

代码上能优化的地方不多,主要考虑的就是网络传输。

一方面是要考虑打包后的体积,从这个维度来考虑,我们可以通过按需引用以及 CDN。按需引用方便理解效果也比较显著,而使用 CDN 的好处有以下几个方面

  1. 抽离出公共包避免每次打包加快打包速度。分离公共库以后,每次重新打包就不会再把这些打包进 vendors 文件中,即使更改了 hash 用户也只需要获取改变的部分
  2. cdn 具有复用的效果。网站A B 都引用同一份资源(版本路径都相同),那么用户访问了A网站以后浏览器缓存了这部分资源,访问B网站时就可以直接复用,减少不必要的加载
  3. CDN减轻自己服务器的访问压力,并且能实现资源的并行下载。浏览器对 src 资源的加载是并行的(执行是按照顺序的), 通过不同的域名加载资源提高很多的加载速度

另一方面应该减少需要加载包的数量,特别是体积较小的碎片包。在初次优化之后,我们发现很多自己写的组件只占了很小的体积,却仍然分割成了独立的块。我们便可以将这些碎片包打包成一个包,减少请求次数。

最后

从没有优化到最后使用CDN优化,可以显著的发现打包后的文件大小减少。如果我们的应用有很高的 pv 每一点优化到最后都能节省很多的流量。从数值上看到优化的效果,对于程序员来说也是蛮有成就感的。
以上就是关于打包优化的一点分享

你可能感兴趣的:(Vue 的打包优化之路)