记一次Vue项目打包优化
接手了一个移动端Vue项目,由于历史原因,整个项目打包速度和页面加载速度都比较慢,本文记录了优化该项目的一些工作。之前整理过一篇webpack折腾记(四):性能优化,本文可以算作该篇文章的一次实践。
项目背景
整个项目存在下面两个问题。
第一个问题是:打包体积很大,原始JS代码3M多~,gzip后也要400多个k,此外css文件gzip后近160k,
另外发现这个项目部署的时候甚至没有配置CDN缓存~相当于进入单次进入页面就需要加载很多静态资源,部署时间和用户访问静态资源加载时间都比较长,因此需要优化第三方依赖的打包。
第二个问题是:整个项目使用的是vue cli2,整个打包脚本的运行,在生产机器上居然要跑两分钟左右,单单是webpack打包都要花费四五十秒
基于上面两个问题,每次部署都是一场噩梦,点击发布后需要部署很长时间,页面加载速度也很慢,急需优化
减少代码体积
除了常规的异步路由等操作之外,还从下面几个方面优化代码体积
减少base64图片内联
由于代码中很多地方直接在js中使用require('xxx.png')的方式引入图片,小图片会打包成base64内联图片,导致打包后的JS文件体积较大,且很难被gzip压缩
一种办法是将url-loader
的尺寸限制参数调低,减少内联图片的数量,但是这会导致部分小图片各自都需要走单独的HTTP请求,是一种得不偿失的做法。
对于小图片而言,更常见的做法是使用精灵图,将多张小图标图片合并成一张整图,然后走单独的HTTP请求。使用精灵图有两个注意事项
- 移动端一般会使用rem等屏幕适配,如果使用rem单位来控制
background-positon
属性,但还是有可能遇见"精灵图背景错位"的问题,可以使用百分比来配置background-positon
- 如果将现有图标重构为精灵图,成本也不小
还是使用字体图标比较好,但是也存在开发成本(突然发现上一次在项目中使用字体图标,貌似是两年以前的事情了~有点怀念)
所以最后的解决办法是:把所有使用的图标都进行了压缩,这个项目之前的图标都是直接使用设计提供的源文件,没有进行任何压缩,导致转成的base64体积更大了。
将所有图片进行压缩带来的一个后续问题是:小体积的图标变得更多了(/捂脸),所以最后内联打包到JS文件中的体积并没有很明显地减少;但是就整体图片数量而言,节省了很多稍大图片的HTTP请求。
Element UI
由于历史原因,这个移动端项目中居然使用了Element UI
,并且还是全局引入的,但实际上只使用了一小部分功能,所以这一块是优化的重点。
首先将Vue.use(ElementUI)
删掉
然后,需要找到使用了哪些组件,这是个体力活,大概就是全局查询使用<el-
组件的文件,以及使用了this.$message
、this.$confirm
等接口的地方
接着将找到的组件按需引入,为了避免挨个文件去修改,偷了个懒,统一将使用到的组件的引入放在一个独立的入口文件里面
// registerComponent.js
import {Row,Col,Button,Tabs,TabPane,Input,Collapse,CollapseItem,Car,Progress,Message} from "element-ui";
let components = [Row,Col,Button,Tabs,TabPane,Input,Collapse,CollapseItem,Car,Progress]
components.forEach(comp => {
Vue.component(comp.name, comp)
})
Vue.prototype.$message = Message;
准后后面逐步将用到的组件如Row
、Col
等移除,到时候再从此处移除对于ElementUI的依赖。
最后,根据官网的按需引入文档,安装babel-plugin-component
插件,配置.babelrc
即可。
由于还是需要一些全局样式和公共代码,所以按需加载并没法实现精准地只加载对应模块的功能。经过上面的步骤,可以将ElementUI的体积缩小到300kb左右。
moment
由于项目需要一些日期处理的工具函数,于是直接引入了moment,众所周知,moment的多语言本地化locale
目录占用的体积相当庞大,但是对该项目而言基本上没有使用。所以,优化moment的第一步就是将他的多语言干掉,这个可以使用webpack.IgnorePlugin
实现
在webpack的插件配置项添加
plugins: [
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
即使是去掉了locale
目录,moment在未压缩的时候也有140kb左右的大小,就一个工具函数库而言,这个体积有点太大了。一种处理方法是使用[https://www.npmjs.com/package/dayjs]来替代moment。
由于dayjs
和moment
存在相同的API,可以直接通过alias实现将moment替换成dayjs,可以迅速将140kb的moment替换成6kb的dayjs。
resolve: {
alias: {
'moment': 'dayjs'
}
},
异步组件
由于某个组件依赖了codemirror
这个库实现功能,导致整个依赖大概增加了400多kb。
考虑到由于目前只有该组件依赖了codemirror
,所以可以通过注册异步组件的形式减少主包的文件体积
Vue.component('codePreview', () => ({
component: import('@/xxx/code-preview'),
delay: 50,
timeout: 6000
}))
这样只有当需要该组件的时候,才会加载对应的分包。
后续:由于异步组件会导致用户体验不太好,然后又改回同步组件了。
对于这种比较大的依赖库,除了一股脑打包到vendor.js
文件中,还可以通过script
标签直接加载CDN文件,然后通过externals
来减少依赖大小。
打包速度优化:升级vue-cli
使用speed-measure-webpack-plugin
测量了一下打包速度
上图是在我本地i7 16G内存的MBP上打包的速度截图,真实服务器比这个性能要差一些
从上面的截图可以看见,打包耗时主要集中在UglifyJsPlugin
、sass-resources-loader
、vue-loader
和px2rem-loader
上,由于vue-cli2使用的是webpack3,如果单独升级webpack,会带来一系列的改动,不如直接升级到vue-cli4
准备工作:
- 安装vue-cli最新版,
vue -V
目前最新的版本是@vue/cli 4.1.1
- 由于项目多个分支的node_moduels是公用的,所以最好重新拉一个项目进行升级操作,避免升级过程中插入的一些紧急需求。
首先切换到项目所在目录,然后运行vue create projectName
,会提示目录已存在,然后选择Merge
合并
合并后新的package.json
、readme.md
、还有一些公共文件会被vue-cli4覆盖,整个升级需要进行两个部分
第一个任务是恢复被覆盖的依赖,需要将旧项目中的dependencies
,以及一些devDependencies
如postcss-px2rem-exclude
等依赖,添加到新的package.json中,下图是迁移后package.json发生的一些变化。
在升级时还发现了一些很少被用到的模块如stylus
,只有一个vue文件使用到,然后就直接将它干掉了~
然后使用yarn install
安装依赖,由于之前项目也配置了eslint
,因此整个项目继续使用之前的.eslintrc.js
即可
第二个任务是将vue-cli2中修改的一些webpack配置迁移到vue.config.js
中,包括新增的loader
和plugins
,此外,环境变量需要修改到.env.dev
等文件中,参考环境变量
至此整个升级过程还算比较顺利。最后,记得在部署的时候,需要先删掉之前的node_modules,然后重新安装。
升级升级到vue-cli4后附带的一个功能是可以使用vue ui了,这样可以更方便地启动项目、分析打包。
整个打包速度的提升是十分明显的,那么问题来了:为什么升级后的打包速度会快这么多呢?后面会整理一篇关于vue-cli的源码分析文章~
小结
本文主要记录了一个Vue项目的打包优化
- 通过升级vue-cli优化打包速度
- 通过图片优化、按需加载等方式减少打包后的文件体积
代码优化并配置完Nginx缓存之后,整个应用的开发体验和用户体验有了非常明显的提升,
- 现在基本上二十来秒就可以完成整个应用的部署,
- 手机浏览器首次首屏打开速度在1s以内
比起刚接手的时候要好一些了。此外还剩一些TODO的工作,如移除所有ElementUI、重构业务逻辑等工作需要进行,后面边开发优化啦~
(PS:实现了oPic之后,写博客放图方便多了~
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。