场景分析
一个结构简单,依赖蛮多的可视化项目,用到的库有
1. Vue + VueRouter + 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 默认都不支持 tree-shaking 因此需要手动按需加载或者使用更小体积的库。
先来看看默认没有经过优化的打包分析。
默认配置
只做了简单异步路由加载,各种库均没有按需引用。
这里可以看到 ECharts 库占了很大一部分,然后是 moment ElementUI 这些库。
初步优化
因为业务需求需要用到地图相关信息所以这部分体积是没法减少的。但是并不是初次进入页面就需要加载这些文件。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') 然后只需要按需引入这三个函数就行了
moment 也被替换为了date-fns
CDN 进一步优化
在初步优化完成后,发现 Vue 全家桶以及 ElementUI 仍然占了很大一部分 vendors 体积,这部分代码是不变的,但会随着每次 vendors 打包改变 hash 重新加载。我们可以使用 CDN 剔除这部分不经常变化的公共库。
另外一方面有些页面很小,但是因为异步路由加载分割了好几块。小碎片的加载也是影响浏览器性能要素之一,可以通过更改打包策略解决。
使用 webpack-cdn-plugin 插件
过去我们外部引入 CDN 需要手动编写 index.html 模板,在里面指定加载的版本,通过这个插件就能自动的把指定的公共库写入到 index.html 模板里,目前的文档有坑,我已经提了PR。
需要注意的是,通过 CDN 引入,在使用 VueRouter Vuex ElementUI 的时候要改下写法。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')剔除全家桶以后,剩下的需要首次加载 vendors 就很小了。
优化后的数据
优化的意义与思路
优化的目的最主要的就是提升用户的体验。
提升首次访问的渲染速度。影响首次渲染速度除了代码上的优化之外就是网络传输的速度。
代码上能优化的地方不多,主要考虑的就是网络传输。
一方面是要考虑打包后的体积,从这个维度来考虑,我们可以通过按需引用以及 CDN。按需引用方便理解效果也比较显著,而使用 CDN 的好处有以下几个方面:
1. 抽离出公共包避免每次打包加快打包速度。分离公共库以后,每次重新打包就不会再把这些打包进 vendors 文件中,即使更改了 hash 用户也只需要获取改变的部分;
2. CDN 具有复用的效果。网站 A、B 都引用同一份资源(版本路径都相同),那么用户访问了A网站以后浏览器缓存了这部分资源,访问 B 网站时就可以直接复用,减少不必要的加载 ;
3. CDN减轻自己服务器的访问压力,并且能实现资源的并行下载。浏览器对 src 资源的加载是并行的(执行是按照顺序的), 通过不同的域名加载资源提高很多的加载速度;
另一方面应该减少需要加载包的数量,特别是体积较小的碎片包。在初次优化之后,发现很多自己写的组件只占了很小的体积,却仍然分割成了独立的块。我们便可以将这些碎片包打包成一个包,减少请求次数。
最后
从没有优化到最后使用CDN优化,可以显著的发现打包后的文件大小减少。如果我们的应用有很高的 pv 每一点优化到最后都能节省很多的流量。从数值上看到优化的效果,对于程序员来说也是蛮有成就感的。 以上就是关于打包优化的一点分享。