环境
- vue-cli 3.11.0
- nginx 1.16.1
写在前面的总结
项目开发过程
- ...此处省略前面的什么需求分析、技术选型、技术架构验证等等步骤,直接进入正题
- 通过脚手架(vue-cli)创建项目
- 搭建前端架构
- 封装基础组件、公共的业务组件、公共模块、方法等
- 正式开发(编码)
- 开发完成,本地测试、打包、部署测试环境、测试,这时你突然发现了一些问题
部署完成
发现,诶,不错,可以跑起来,有点小开心。但是很快就发现了问题:
- 这个性能是不是有点问题?
- 首屏加载怎么这么慢?
- 我是不是没做性能方面的优化?
- 打包优化?
- 雅虎军规?
- 。。。我去,我好像啥都没做
以前用 apache 部署前端应用(jQuery + html + css)时还经常做相关的性能优化,比如用的最多的雅虎军规,但是现在用 vue 技术栈 + nginx 呢?好吧,现在开始优化吧
分析
vue 技术栈 + nginx (当然其它也一样,只要用到类似 webpack 这样的打包工具) 优化可以分为哪些呢?可以简单分为以下三种 :
- 编码层面的优化
- 打包优化
- nginx 服务器配置
编码层面的优化
这一层面的优化就多了去了,比如我们所熟知的 v-if、v-show的使用、虚拟列表的使用、动态加载、懒加载、禁用响应式(有些地方是不需要响应式的,比如单纯的数据展示,即数据不会变)等等方法
打包优化
本篇文章主要介绍 打包优化
nginx 服务器配置
nginx 服务器的配置,参考另外一篇文章,nginx 服务器的安装和配置
打包优化
分析
我们要进行打包优化,首先得知道我们需要优化哪些地方,这就需要我们对打包结果进行分析
webpack-bundle-analyzer
这是一个 webpack 的插件,可以对打包结果进行分析
如何使用
安装插件
npm i webpack-bundle-analyzer -D
配置 webpack
vue-cli3 我们通过 vue.config.js 文件对 webpack 进行配置
module.exports = {
...
// vue-cli3 提供的一种高级技巧,链式操作
chainWebpack (config) {
...
// 意思只在打包时起作用
if (process.env.NODE_ENV === 'production') {
config.plugin('webpack-bundle-analyzer')
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
.end()
}
...
}
...
}
打包
npm run build这时候就开始了打包操作,它会在本地启动一个服务器,默认端口号为 8888,我们在浏览器访问 172.0.0.1:8888 就可以看到打包的分析结果,当然命令行也会输出一些信息,比如我们的包有哪些,有哪些包是不是有问题,比如包太大?或者有 console log 信息等等,有不理解的信息可以继续向下看
打包结果分析
以上两张图片是一样的,只是第二张有每个包的信息
鼠标可以在页面上滑动,发现该页面是一个模块图,对整个应用的包进行分析,有哪些部分组成,以及每部分的一个信息
我们对该页面进行分析,发现 :
着手优化
刚才经过一番分析,发现主要问题是资源包太大了,居然有 13 MB,这不是一个大项目,打包结果怎么可能这么大
分析 dist 目录
source map
查看dist目录下的各个目录,发现居然有 .map 文件,这是 vue-cli3 默认会开启 source map,这样便于我们开发时调 bug,准确定位错误的位置,在打包时我们需要手动关闭
关闭 source map
module.exports = {
...
productionSourceMap: false,
...
}
重新打包,运行
npm run build
, 再次查看 dist 目录,瞬间小了,但是 webpack-bundle-analyzer 分析结果还是没变,我们只是把 打包后的 dist 目录的大小减少了,当然这也是一个优化,这样可以保证部署到生产环境的代码体积,以及代码的安全性
GZip 压缩
我们会发现执行 npm run build
时有一些提示信息
说明资源和入口文件太大,超出了webpack的默认限制,这样会影响 web 的性能,这个是webpack给出的提示,webpack的性能篇给了详细的介绍
有两种解决办法,改掉webpack的默认配置,比如将大小限制调大,或者直接关闭提示,不建议这么做,因为这样做等于你没有做任何的优化,我们采取 资源压缩 的方式
怎么做
两种方式
- nginx 服务器进行压缩
- webpack 打包时压缩
这里我们选择第二种方式,理由如下:
nginx 服务压缩,也可以,但是每次请求资源是,服务器其实是在动态实时进行压缩,这其实是对服务器的一种压力,比如 CPU 进行大量的计算,特别是当负载比较高时,请求过来,服务器进行压缩,请求开始到压缩结束,这段时间内用户是看不到任何东西的,体验不好,用户还以为卡了呢,重复发请求,重复执行压缩,负载越来越高,恶性循环,所以这里采用 webpack 打包时压缩,ngixn 放的时压缩过后的资源,性能就会好很多,当然(划重点:这种方式 nginx 服务器也需要做点配置,请往下看)
安装插件
npm i compression-webpack-plugin -D
修改 vue.config.js
module.exports = {
...
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
return {
plugins: [
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: /\.(js|json|css|jp?eg|png)$/,
// 当资源大于该值时进行压缩
threshold: 0,
// 压缩率小于这个值的资源会被压缩,默认为 0.8
// minRatio: 0.8,
minRatio: 1,
// 删除源文件, 配合webpack-bundle-analyzer时不能删除源文件,且需要配置静态服务器
deleteOriginalAssets: true
})
]
}
}
}
...
}
打包
npm run build
压缩后的结果(看效果)
这里我往存了压缩之前的截图了,至少少了好几兆的数据,读者在实战的时候可以自己试试
配置 nginx 服务器
将打包后的资源包上传到 nginx 服务器进行部署,然后访问页面,发现啥都没有,打开控制一看,各种 404,意思是资源未找到,what ??
原来我们还需要配置一下 nginx 服务器,开启 nginx 的压缩模式,nginx 的压缩分为两种 :
-
动态压缩
就是我们开头说的,需要服务器实时压缩,会提高服务器的负荷,浪费 CPU
-
静态压缩 (我们采用的方式)
利用 nginx 的 GZip Precompression 模块,这个模块的作用是对于支持 gzip 的请求,直接读取已经压缩好的文件(文件名以 .gz 结尾,查看我们打包后的文件,会发现这些文件也都是以 .gz 结尾的文件),而不是动态压缩,对于不支持 gzip 的请求,则读取源文件
说明
nginx 动态压缩需要用到 ngx_http_gzip_module 模块,该模块默认安装;静态压缩用的是 http_gzip_static_module 模块,该模块允许发送以 .gz 作为扩展的预压缩文件代替普通文件,我们需要检测 nginx 是否已经有该模块,我的环境已经有了,没有的话需要重新编译,并指定该模块
查看是否已经安装了该模块:
nginx -V
没有的话则安装:
./configure --with-http_gzip_static_module
make && make install
niginx 配置 gzip, gzip 静态压缩
http {
# 开启gzip
# gzip on;
gzip_static on;
# 启用gzip压缩的最小文件;小于设置值的文件将不会被压缩
gzip_min_length 1k;
# gzip 压缩级别 1-10
gzip_comp_level 2;
# 进行压缩的文件类型,静态压缩设置 gzip_types 没用
# gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
# 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_vary on;
}
查看是否配置成功
游览器发送请求,查看请求信息,如图所示,则成功
OK,压缩的事情终于搞定了,继续查看分析结果,进行下一步的优化
按需加载
经过以上的压缩处理之后,打包后的文件已经小了很多了,现在总共也才 1.4 MB,但是我们还是需要再分析分析有哪些地方始都可以继续优化
继续分析打包结果,我们会发现,其中的第三库的体积占大头,从中可以看到 element-ui 和 echarts(项目中用的 v-charts)占大头,分别 1.49 MB 和 2.42 MB,这是未压缩的体积,如下图所示 :
分析,这两个为什么这么大,难道在开发时没有 配置按需加载 ?经过一番检查,果然,没有做按需加载
element-ui 按需加载
这里我们参照 element-ui 官网快速上手章节的按需引入部分
注意
.babelrc 文件对应的是 vue-cli3 中的 babel.config.js 文件
其中的 es2015 需要改成 '@babel/preset-env', 用 es2015 会如下错误:
// babel 版本冲突,查看 package.json 文件会发现 babel-core 等都是 7.0
Plugin/Preset files are not allowed to export objects, only functions.
Babel 7 废弃了 babel-preset-es201x
而采用新的 env 插件。
所以 element-ui 按需加载最后的配置是:
// babel.config.js
module.exports = {
presets: [
'@vue/app',
['@babel/preset-env', { modules: false }]
],
plugins: [
[
'component',
{
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk'
}
]
]
}
element-ui 配置按需加载后的效果
v-charts 按需加载 (echarts 也有对应的方案)
v-charts的按需加载倒是不用额外的配置,只需要按要求进行编码即可
...
// v-charts 按需加载
import VeLine from 'v-charts/lib/line'
import VePie from 'v-charts/lib/pie'
import VeGauge from 'v-charts/lib/gauge'
import VeHistogram from 'v-charts/lib/histogram'
Vue.component(VeLine.name, VeLine)
Vue.component(VePie.name, VePie)
Vue.component(VeGauge.name, VeGauge)
Vue.component(VeHistogram.name, VeHistogram)
...
按照以上的方式,即可实现 v-charts 的按需加载
v-charts 按需加载后的效果
element-ui、v-charts实现按需加载后,效果显著,只打包了我们实际用到的模块,优化效果显著
路由懒加载
该项目没有进行路由懒加载方面的优化,因为在项目架构初始时就做好了,直接上代码:
export const constRoutes = [
{
path: '/login',
name: 'login',
// 路由的懒加载
component: () => import('@/views/Login.vue'),
hidden: true
},
{
path: '/',
component: () => import('@/views/Home.vue')
},
{
path: '/dashboard',
redirect: '/',
meta: {
roles: ['admin', 'editor'],
// title: 'dashboard'
title: '首页',
icon: 'dashboard'
}
}
]
路由懒加载实现其实很简单,只需要动态的引入我们的组件即可
优化效果
包体积明显减小,从刚开始的 13MB 减小到现在的 1MB,体积降低了高达92%
首屏加载时间也可以达到毫秒级,相比优化之前提高了50%的加载效率
总结
项目优化总的可以分为三类,分别是编码层级的优化、打包优化和 配置nginx(静态资源)服务器
编码层级的优化
文档没有介绍,但是可以简单给大家挪列一些
- v-if vs v-show
- 虚拟列表、长列表优化
- 动态加载(懒加载)
- 你是不是真的需要响应式(响应式带来便利的同时也是对性能的损耗)
- 渲染方面的优化(减少重绘、重排)
- 网络方面的优化
- ...
打包优化
通过一些工具查看我们需要进行哪些优化,比如 webpack-bundle-analyzer、lighthouse、performance、coverage等
-
移除死代码,即永远不会被执行到的代码
UglifyJS、Tree Shaking,能自动把项目中没有用到的代码从打包中去掉,UglifyJS也可以去掉项目中各种console.log
-
懒加载
移除死代码后发现资源包体积还是大,这时候就需要考虑是不是有些资源被重复打包了?特别是多入口的项目,很容易出现这种情况,一个包在多个入口被引用,可以用code spliting方法进行公共代码的抽离,webpack 4采用的是 splitChunks,可以实现拆包以及打包公共模块等功能;
- 生产环境关闭 source map
- 资源压缩,gzip
- 资源按需加载
-
如果有条件使用 CDN 的话,可以大幅度减少打包体积
可以通过 CDN 去加载一些外部资源(保罗全局变量的库),比如 vue、vuex、vue-router、axios、echars、element-ui 等,需要配置 webpack 的 externals 属性
-
有些资源是否要拆分
这个就仁者见仁,智者见智了,比如 CSS 的拆分、小图片是否需要 base64 编码等
拆分带来的好处是:可以做缓存; 缺点:首次加载耗时,HTTP 请求数多了,内联的同时代码体积变大了
建议根据实际场景做决策 - 打包设置缓存,增加首次打包的时间,但是减少后续的打包时间
- 像一些不经常变的资源单独打包,也是借用 splitChunks 实现
配置 nginx 服务器
参考另外一篇文章, nginx 服务器的安装和配置
结束了,点我可以直接回到开始的位置