年前做過(guò)一個(gè)公司內(nèi)部的后管平臺(tái)在辆,前端使用 vue.js 框架開(kāi)發(fā)规揪,基于 vue-cli3 腳手架構(gòu)建項(xiàng)目,ui 組件庫(kù)使用 Element-UI乍赫,其他組件還包括 axios瓣蛀,echarts,vue-router雷厂,vuex 等惋增,該項(xiàng)目功能簡(jiǎn)單,涉及頁(yè)面 40 個(gè)改鲫,都是簡(jiǎn)單的表單或者列表頁(yè)器腋。上線的時(shí)候直接 npm run build
命令打包,未在 vue.config.js 中做任何配置钩杰,將 dist 打好的包放到 nginx 服務(wù)器,做個(gè)反向代理就用上了诊县。
未做優(yōu)化的打包結(jié)果如下:
可以看出 chunk-vendors.82702712.js
文件大小有 1M 多讲弄,對(duì)應(yīng)的 css 文件也有 194 kb,app.653b22c5.js
有 172 kb依痊,項(xiàng)目中所有的三方庫(kù)都打包到 chunk-vendors.82702712.js
中避除,而所有的頁(yè)面,自定義組件等都在 app.653b22c5.js
中胸嘁。因?yàn)闆](méi)有使用動(dòng)態(tài)加載瓶摆,所以這些 1M,幾百 kb 的 js 都會(huì)在剛訪問(wèn)網(wǎng)站的時(shí)候一起加載性宏,pc 上還好群井,一旦放到移動(dòng)端,弱網(wǎng)情況下只能給用戶展示一個(gè)大白板毫胜。
訪問(wèn)項(xiàng)目主頁(yè)书斜,通過(guò) Chrome 的 Instrument converge 功能查看 js,css 的資源使用率酵使,發(fā)現(xiàn) chunk-vendors.82702712.js
的未使用率達(dá)到 85.4%荐吉,對(duì)應(yīng)的 chunk-vendors.ea3fa8e3.css
未使用率 98.3%,app.js 的未使用率是 88.4 %口渔,app.css 未使用率是 85%样屠,說(shuō)明項(xiàng)目首頁(yè)訪問(wèn)的資源體積大,是因?yàn)榘艘淮蟛糠治词褂玫慕M件,如果每個(gè)頁(yè)面都只加載自己需要的組件痪欲,那網(wǎng)站的訪問(wèn)速度會(huì)得到較大的提升悦穿。
基于上面遇到的問(wèn)題,優(yōu)化打包可以從以下幾個(gè)方面入手勤揩。
- 組件動(dòng)態(tài)引入咧党,按需加載,
app.653b22c5.js
可以拆分成若干個(gè) js陨亡,使用時(shí)才去加載對(duì)應(yīng)的 js傍衡。 - 三方庫(kù)組件,按需加載负蠕,
chunk-vendors.82702712.js
可以做拆分蛙埂,不必一下全部加載,可在使用時(shí)再去加載對(duì)應(yīng)部分的 js遮糖。 - 對(duì)資源文件做壓縮绣的,上圖中可以看到 1069.96kb 的文件, gzip 可以壓縮到 310.03kb欲账。
著手優(yōu)化項(xiàng)目打包
路由懶加載
結(jié)合 Vue 的異步組件和 Webpack 的代碼分割功能屡江,輕松實(shí)現(xiàn)路由組件的懶加載。
在 router.js 中赛不,原來(lái)導(dǎo)入組件的方式是:
import Home from './views/Home.vue'
import Login from './views/Login.vue'
import Role from './views/system/Role.vue'
import User from './views/system/User.vue'
...
這就導(dǎo)致這些組件最后都打包到一個(gè) app.653b22c5.js
文件中惩嘉,我們修改導(dǎo)入組件的方式,改為動(dòng)態(tài)導(dǎo)入:
const Home = () => import('./views/Home.vue')
const Login = () => import('./views/Login.vue')
...
修改完所有的組件導(dǎo)入后踢故,在運(yùn)行 npm run build
打包發(fā)現(xiàn)文黎,原來(lái)的 app.653b22c5.js
和 css 文件得到了拆分。
可以看到 app.js 文件從原來(lái)的 172.77kb 降到了 18.14 kb殿较。
但是拆分過(guò)細(xì)耸峭,每個(gè)頁(yè)面都獨(dú)自拆分出來(lái),一個(gè) js 大一點(diǎn)的 7kb淋纲,小的才 2劳闹,3kb,css 拆分后有的 0.1kb洽瞬,甚至還有 0.03kb的玷或,拆分的粒度過(guò)細(xì),也會(huì)造成更多的網(wǎng)絡(luò)資源請(qǐng)求片任,對(duì)網(wǎng)站加載造成影響偏友。
其實(shí)我們的一個(gè)功能流程通常會(huì)涉及多個(gè)頁(yè)面,如果能將多個(gè)頁(yè)面組件分組打包对供,效果會(huì)更好位他,避免了多次網(wǎng)絡(luò)資源請(qǐng)求氛濒。
路由懶加載分組
分組修改方法如下:
const Role = () => import(/* webpackChunkName: "group-role" */'./views/system/Role.vue')
const AddRole = () => import(/* webpackChunkName: "group-role" */'./views/system/AddRole.vue')
const EditRole = () => import(/* webpackChunkName: "group-role" */'./views/system/EditRole.vue')
const User = () => import(/* webpackChunkName: "group-user" */'./views/system/User.vue')
const AddUser = () => import(/* webpackChunkName: "group-user" */'./views/system/AddUser.vue')
const EditUser = () => import(/* webpackChunkName: "group-user" */'./views/system/EditUser.vue')
const UserAllotRole = () => import(/* webpackChunkName: "group-user" */'./views/system/UserAllotRole.vue')
按照功能將組件分組后,打包結(jié)果如下:
每個(gè)功能模塊打包后的 js 大概有十幾kb鹅髓,文件數(shù)量也大大減少舞竿。
node_modules 打包
chunk-vendors.js
是對(duì) node_modules 中三方庫(kù)的打包,本項(xiàng)目中 chunk-vendors.js
的大小窿冯,Element-UI 占了大部分比例骗奖。group-monitor.js 為 327.92 kb 是因?yàn)槠湟肓?echart。
我在 main.js 中完整引入了 Element-UI醒串,而不是按需引入:
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import http from './utils/network/http'
Vue.use(ElementUI)
所以會(huì)將 Element-UI 的所有組件打包到項(xiàng)目中执桌。
對(duì)三方庫(kù)的引用務(wù)必使用按需加載,不需要的組件就不要引入進(jìn)來(lái)芜赌。優(yōu)化時(shí)將 Element-UI 常用的組件在 main.js 中按需引入仰挣。
import { Container, Menu } from 'element-ui';
Vue.use(Container);
Vue.use(Menu);
頁(yè)面中有單獨(dú)用到的 Element-UI 組件,則在該頁(yè)面中單獨(dú)引入對(duì)應(yīng)的組件缠沈,這樣在訪問(wèn)該頁(yè)面時(shí)才去加載這些 ui 組件膘壶。
由于 40 多個(gè)頁(yè)面用到了 Element-UI 組件,改造工作量比較大洲愤,我用 echart 在其他頁(yè)面做測(cè)試颓芭,看下打包情況。
我找到三個(gè)頁(yè)面分別按需引入 echart.js:
// MonitorDetail.vue 按需引入 echart.js
var echarts = require('echarts/lib/echarts');
require('echarts/lib/chart/pie');
require('echarts/lib/component/tooltip');
require('echarts/lib/component/title');
require('echarts/lib/component/legendScroll');
// Bulletin.vue 按需引入 echart.js
var echarts = require('echarts/lib/echarts');
require('echarts/lib/chart/bar');
require("echarts/lib/chart/tree");
// About.vue 按需引入 echart.js
var echarts = require('echarts/lib/echarts');
// 與 Bulletin.vue 公共的引入
require('echarts/lib/chart/bar');
require("echarts/lib/chart/tree");
// 與 MonitorDetail.vue 公共的引入
require('echarts/lib/chart/pie');
require('echarts/lib/component/tooltip');
require('echarts/lib/component/title');
require('echarts/lib/component/legendScroll');
// 自己獨(dú)有的引入
require("echarts/lib/chart/treemap");
require("echarts/lib/chart/graph");
require("echarts/lib/chart/gauge");
About.vue 引入的 echart 組件中有一部分與 MonitorDetail.vue 相同柬赐,一部分與 Bulletin.vue 相同畜伐,還有三個(gè)頁(yè)面公共的部分,我們期望公共的部分打包成一個(gè) js躺率,不會(huì)重復(fù)打包,About.vue 獨(dú)有部分還和 Acount.vue 在一起打包万矾。
打包結(jié)果如下:
about~group-bulletin~group-monitor.js
是三個(gè)頁(yè)面 echart 的公共部分悼吱,about~group-monitor.js
是 About.vue 與 MonitorDetail.vue echart 的公共部分,about~group-bulletin.js
是 About.vue 與 Bulletin.vue 的公共部分良狈,about.js 獨(dú)有的 echart 組件則還在自己的 js 中后添。
只要我們按需引入三方庫(kù)組件,vue-cli3 就能智能的幫助我們合理的打包薪丁,這主要是依靠插件 SplitChunksPlugin 來(lái)完成的遇西。
gzip 壓縮
如果 Nginx 服務(wù)器開(kāi)啟 gzip,會(huì)將靜態(tài)資源在服務(wù)端進(jìn)行壓縮严嗜,壓縮包傳輸給瀏覽器后粱檀,瀏覽器再進(jìn)行解壓使用,這大大提高了網(wǎng)絡(luò)傳輸?shù)男事绕鋵?duì) js茄蚯,css 這類(lèi)文本的壓縮压彭,效果很明顯。
以下是 Nginx 開(kāi)啟 gzip 的配置:
# 開(kāi)啟|關(guān)閉 gzip渗常。
gzip on|off;
# 文件大于指定 size 才壓縮壮不,以 kb 為單位。
gzip_min_length 10;
# 壓縮級(jí)別皱碘,1-9询一,值越大壓縮比越大,但更加占用 CPU癌椿,且壓縮效率越來(lái)越低健蕊。
gzip_comp_level 2;
# 壓縮的文件類(lèi)型。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript;
# 開(kāi)啟后如果能找到 .gz 文件如失,直接返回該文件绊诲,不會(huì)啟用服務(wù)端壓縮。
gzip_static on|off
# 是否添加響應(yīng)頭 Vary: Accept-Encoding 建議開(kāi)啟褪贵。
gzip_vary on;
# 請(qǐng)求壓縮的緩沖區(qū)數(shù)量和大小掂之,以 4k 為單位,32 為倍數(shù)脆丁。
gzip_buffers 32 4K;
如果 Nginx 沒(méi)有開(kāi)啟 gzip世舰,前端在打包的時(shí)候可以打包出一份資源的壓縮版本,Nginx 也會(huì)把壓縮文件傳輸給瀏覽器槽卫。
首先安裝一個(gè)插件:
npm i -D compression-webpack-plugin
在 vue.config.js 中配置下這個(gè)插件:
const CompressionPlugin = require("compression-webpack-plugin")
module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
return {
plugins: [
new CompressionPlugin({
test: /\.js$|\.html$|\.css/,
threshold: 10240,
deleteOriginalAssets: false
})
]
}
}
}
}
nginx 服務(wù)器還要做一下簡(jiǎn)單配置:
gzip_static on;
使用 compression-webpack-plugin
插件后的打包結(jié)果如下:
上圖中的 .gz 就是對(duì)應(yīng)一個(gè)資源文件的壓縮版本跟压。
配置成功后,重新將代碼部署到 nginx歼培,重新載入 nginx 配置震蒋。
我們先看一下沒(méi)有開(kāi)啟 gzip_static off;
gzip 的訪問(wèn)情況:
我們直接看最大的兩個(gè)文件 chunk-vendors.js 和 chunk-vendors.css,他們的大小分別是 748kb 和 194kb躲庄,加載用時(shí)分別是 622ms 和 247ms查剖。
然后我們開(kāi)啟 gzip gzip_static on;
再看下資源的加載情況:
同樣是 chunk-vendors.js 和 chunk-vendors.css,他們的大小變成 190kb 和 29.3kb噪窘,加載時(shí)間變成 245ms 和 46ms笋庄。
以上就是使用 gzip 的效果。
Vuex 動(dòng)態(tài)注冊(cè)模塊
vuex 通常使用靜態(tài)模塊倔监,這些模塊都會(huì)打包到 app.js 中直砂,但是如果有的模塊過(guò)大而且不是立刻就會(huì)用到,我們可以動(dòng)態(tài)的注冊(cè)模塊到 vuex 中浩习。
在使用 vuex 某個(gè)模塊的時(shí)候才注冊(cè):
mounted () {
this.$store.registerModule('myModule', MyModule)
}
在不使用的時(shí)候静暂,注銷(xiāo)模塊:
beforeDestroy () {
this.$store.unregisterModule('myModule')
}
這樣實(shí)現(xiàn)的效果是該頁(yè)面在加載時(shí)才下載模塊內(nèi)容,而不是剛訪問(wèn)網(wǎng)站就去下載谱秽。
工具
打包時(shí)籍嘹,我們可以通過(guò)一些輔助工具幫助分析打包情況闪盔,有重點(diǎn)的去做優(yōu)化。
打開(kāi) Chrome 的調(diào)試模式辱士,CMD+SHIFT+P 調(diào)出命令面板泪掀,輸入 Coverage,選擇 Show Coverage颂碘,然后訪問(wèn)指定網(wǎng)站异赫,點(diǎn)擊按鈕 Instrument converge 就能查看已加載資源的未使用率。
使用 webpack-bundle-analyzer
插件可以分析出打包后的文件結(jié)構(gòu)头岔。
安裝該插件 npm i –D webpack-bundle-analyzer
塔拳,vue.config.js 中做如下配置:
chainWebpack: config => {
if (process.env.NODE_ENV === 'production') {
if (process.env.npm_config_report) {
config
.plugin('webpack-bundle-analyzer')
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
.end()
config.plugins.delete('prefetch')
}
}
}
然后使用 npm run build --report
命令打包就可以查看到打包后的文件結(jié)構(gòu)。
這個(gè)打包后的文件結(jié)構(gòu)頁(yè)面很有參考價(jià)值峡竣,對(duì)打包的優(yōu)化幫助很大靠抑。
參考文章
提升90%加載速度——vuecli下的首屏性能優(yōu)化
Vue 性能優(yōu)化:如何實(shí)現(xiàn)延遲加載和代碼拆分?
vue-cli3+nginx配置gzip壓縮