第一次在大佬云集的掘金里发文,自己是个菜鸟,语言组织不好、技术栈也有点旧,请各位大佬轻喷。
希望可以帮助到一些人,同时文章里也有几个未解决的问题,知道的朋友麻烦讲下(其实本文是个求助文,手动滑稽)
准备工作
对chunk命名
默认打包之后的文件,是以chunkId命名的,0、1、2、3......依次递增,对于我们分析文件十分不友好。因此需更改chunkName。
路由懒加载、组件懒加载等都会生成一个chunk,我们需要对chunk重新命名
// 1. 本人没找到这种方式如何重命名
component: resolve => require(['@/...'], resolve)
// 2. 第三个参数即为chunkname
component: resolve => require.ensure([], () => resolve(require('@/...')), 'xxx')
// 3. webpackChunkName 的值即为chunkName
component: () => import(/* webpackChunkName: 'xxx' */ './xxx')
webpack-bundle-analyzer
可视化分析webpack打包情况
点 这里 查看使用方法
至此,前期准备工作就做好了。
接下来我们先看看未优化前的打包情况

可以看到图中有四个比较大的包。分别为starSkyIndex、vendor、lineChart、jobdirection。
由于项目使用的webpack3.6.0,webpack4好多方便的配置都不能使用,但原理差不多,本文都是基于webpack3进行的配置
接下来我们一步一步进行优化
1. 组件库按需引入
本项目组件库采用vant,开发阶段为开发方便,将组件库完整引入。现阶段需要对组件库采用按需引入的方式来减少打包体积。
按需引入方式可采用官方文档教程
这里本人之前有个误解,认为按需引入只需要如下配置即可,我们可以直接使用组件,插件会帮我们自动引入。我理解的其实就相当于是懒加载,需要的时候再引入。结果显而易见,我错了,一直提示组件未引入。
// 在.babelrc 中添加配置
// 注意:webpack 1 无需设置 libraryDirectory
{
"plugins": [
["import", {
"libraryName": "vant",
"libraryDirectory": "es",
"style": true
}]
]
}
对于某个事物最初如果有自己的理解,要矫正的话是很困难的(至少我认为如此)。经过一番查阅文档,才慢慢理解。原来插件只是帮我们将
import { Button } from 'vant';
转化为
import Button from 'vant/lib/button';
import 'vant/lib/button/style';
这种形式,省去了写具体目录和引css的步骤。
项目打包时,手动配置的组件还是会被全部打包进vendor里,随着首屏加载全部被引入。
优化后效果:

问题:
- 首页只使用了某一个组件,其实也是造成了资源的浪费,能不能只把首页需要的组件打包成一个文件,其他的进行预加载?
- 随着项目中使用的组件越来越多,按需引入组件也会越来越多,在编写的过程中,得先
import一个组件,再Vue.use()一次。作为懒癌患者的我,内心很不想这么写两遍,但是查阅文档,也没找见简化的方法(除了使用编辑器的提示功能)。此处作为第二个问题保留。
2. cdn加速
手动添加cdn
当我们不会的时候,总觉得是很深奥的问题。但当我们尝试着去做一做的时候,发现一切原来如此简单。
在index.html里引入CDN
<!--index.html-->
<body>
<div id="app">
<!-- shell -->
</div>
<script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script>
<!-- built files will be auto injected -->
</body>
webpack配置中配置不打包的资源
// webpack.config.base.js
module.exports={
// ...
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex'
}
}
参考: webpack引入CDN加速
自动引入cdn
手动引入太麻烦,利用插件自动引入吧,只需要配置cdn地址即可。
webpack.config.base.js里的externals配置仍不变
需要新加个cdn.js文件储存cdn信息
// config/cdn.js
const cdn = {
css: [
// 'https://cdn.jsdelivr.net/npm/vant@2.8/lib/index.css'
],
js: [
'https://cdn.bootcss.com/vue/2.5.2/vue.min.js',
'https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js',
'https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js'
]
}
module.exports = cdn
同时修改下webpack.config.js里的插件配置,使HtmlWebpackPlugin接受到cdn参数。
// webpack.config.js
plugins: [
new HtmlWebpackPlugin(Object.assign({
filename: 'index.html',
template: 'index.html',
inject: true
}, config.cdn))
]
最后在模板文件index.html里修改下即可
<!--index.html-->
<body>
<div id="app">
<!-- shell -->
</div>
<script src="./static/js/axios.min.js"></script>
<% for (var i in htmlWebpackPlugin.options.js&&htmlWebpackPlugin.options.js) { %>
<script src="<%= htmlWebpackPlugin.options.js[i] %>"></script>
<% } %>
<!-- built files will be auto injected -->
</body>
参考: vue-cli2使用cdn资源
自动引入优化写法
上边的自动引入,功能上已经实现,但仍存在一些弊端。
比如,需要手动维护三处地方:
- package.json 组件版本
- cdn文件
- externals配置
在网上找到了一种较好的写法,只需要维护一个cdn.js文件即可,点这里查看。感兴趣的可以看一看。经测试,没有问题,项目中也使用了这种方式。
前后效果对比
使用cdn前

使用cdn后

再来看看此时全局的效果

vendor文件只剩了很小的一部分。
接下来是重头戏,也是难点,抽离echarts和three(starSkyIndex中引了three)
3. CommonsChunkPlugin
现在还有三个文件比较大,分别是starSkyIndex、lineChart和jobDirection。
其中,lineChart和jobDirection都引用了echarts,虽然都是按需引入,但也有很大公共部分,现有的配置也无法将这两个文件的公共部分抽离出来(也有可能个人理解不到位,不知道怎么配置了)。
最后无奈,强制进行了抽离,如下。
new webpack.optimize.CommonsChunkPlugin({
name: 'echarts',
chunks: ['lineChart', 'jobDirection'],
minChunks: 2,
async: 'echarts'
})
其中:
chunks: 从哪些chunk中进行抽离minChunks: 当被引用几次即进行抽离children: 将子模块的公共部分抽出来,打包进主模块 --- 与chunks互斥async: 将公共部分从主模块抽离出来及chunk名,此处加不加async效果是一样的
弊端:
配置文件与开发内容强相关了,不利于后期维护及交接。
最后看下抽离echarts之后的效果:

3. 动态引入
现在还剩一个特别大的文件starSkyIndex,几乎占了项目打包文件的半壁江山。
starSkyIndex:项目的一个模块,此模块内容单一,但引了three.js、swiper等插件、且都只是在这个模块中被使用,导致所有的插件及代码都被打包进了这一个文件里。这种方式肯定不行,需要把three和swiper都抽离出去,使这个文件只剩我们编写的代码。
目前的优化思路:
- 将three从这个文件抽离出去,使用“懒加载”的方式引进。
- 使用cdn,但需要使用预加载或预解析的方式,因为首页实际并不需要这些东西。
本来这块完全不知道该怎么优化了,但在写文档整理思路的时候,想到了“懒加载”(就是标题所示的动态引入),于是试试懒加载的引入方式。
// three
// 原先的写法
import * as THREE from 'three'
// 替换为
let THREE = null
export default {
mounted () {
this.importThree()
},
methods: {
importThree () {
import(/* webpackChunkName: 'three' */'three').then(data => {
THREE = data
this.threeStart()
})
}
}
}
重新打包发现,抽离出来了!守得云开见月明,这块之前想了好久查了好久没找见解决办法。
真是众里寻他千百度,那人却在灯火阑珊处啊(允许我小小的嘚瑟一下,虽然可能不是很好的方式,但确实抽离出来了)
来看下效果,抽离three和swiper后,实际编写的代码压缩后只有2.68K

swiper抽离出来之后发现本地报错了,之前的写法如下,与正常引入组件有点差别。之后尝试了commonsChunkPlugin抽离,但是也报错了,先保留吧。
// 之前的写法
import 'swiper/css/swiper.css'
import { Swiper, SwiperSlide } from 'vue-awesome-swiper'
// 我尝试这么改,但报错了
// const { Swiper, SwiperSlide } = () => import(/* webpackChunkName: 'awesome-swiper' */'vue-awesome-swiper')
进一步的优化,当然还是使用cdn,但是此处不能使用上文所讲的方式进行cdn引入。因为这儿的three并不是项目必须文件,只有某一个模块才需要,所以不能在首页index.html直接引。
想在首页使用preload或者prefetch来加载three.js。但又担心影响首页性能(preload)或需要时未加载成功(prefetch),最终也都没使用。
待优化:
- three.js的cdn引入
- swiper抽离