這里寫(xiě)自定義目錄標(biāo)題
分析工具
Coverage:查看代碼的使用狀況
移除死代碼
懶加載代碼
webpack-bundle-analyzer:查看資源樹(shù)
- productionSourceMap:false
2.路由懶加載
- 關(guān)閉Prefetch
4.element-ui組件按需加載
5.使用 CDN 外部加載資源-vue, vuex, vue-router,axios
6.使用 CDN 外部加載資源-echarts
7.gzip
結(jié)論
后記:css是否要拆分
分析工具
Coverage:查看代碼的使用狀況
如何打開(kāi)caverage 前提:chrome瀏覽器的版本必須是59或以上,在ctrl+shift+i快速打開(kāi)devtools,點(diǎn)擊右上角的... More tools 有個(gè)Coverage。
Coverage 是chrome開(kāi)發(fā)者工具的一個(gè)新功能,從字面意思上可以知道它是可以用來(lái)檢測(cè)代碼在網(wǎng)站運(yùn)行時(shí)有哪些js和css是已經(jīng)在運(yùn)行,而哪些js和css是還沒(méi)有用到的,如圖,這是我在打開(kāi)csdn網(wǎng)頁(yè)時(shí),所顯示的已運(yùn)行和尚未運(yùn)行的代碼情況。

那這個(gè)新功能有什么作用呢?
如上圖所示,最右邊顯示的是我們加載的css和js文件數(shù)量,紅色區(qū)域表示已運(yùn)行的代碼,而青色表示已加載但未運(yùn)行的代碼??捎脕?lái)發(fā)現(xiàn)頁(yè)面中尚未用到的js 和 css代碼,你可以為用戶(hù)只提供必要的代碼,這樣就可以提升頁(yè)面的性能。這對(duì)于找出可以進(jìn)行拆分的腳本以及延遲加載非關(guān)鍵腳本來(lái)說(shuō)非常有用。
上面錄制的數(shù)據(jù)中,最大的文件是 vendor.js,其中 55% 的代碼都沒(méi)有執(zhí)行過(guò),約 80 KB,這已經(jīng)相當(dāng)于一張典型圖片的文件大小了。
如果某個(gè)文件覆蓋率低(即未使用代碼比例很高),通常意味著用戶(hù)加載了太多不必要的代碼(要么真的是無(wú)用代碼,要么是當(dāng)前時(shí)點(diǎn)還沒(méi)執(zhí)行到的代碼),有性能常識(shí)的同學(xué)不難推斷出,這會(huì)導(dǎo)致頁(yè)面的完全加載時(shí)間、或單頁(yè)應(yīng)用的啟動(dòng)時(shí)間變慢,在慢速網(wǎng)絡(luò)下的性能損耗會(huì)尤其明顯;此外,更多代碼的解析、編譯也就意味著更多的硬件資源消耗,在低端設(shè)備上也會(huì)存在明顯的性能問(wèn)題。
在筆者看來(lái),Coverage 數(shù)據(jù)至少能從下面 2 個(gè)方面指導(dǎo)我們進(jìn)行 WEB 應(yīng)用的優(yōu)化:
移除死代碼
以 Coverage 數(shù)據(jù)為參考,我們能了解頁(yè)面重?zé)o用代碼的比例到底有多大?,F(xiàn)實(shí)世界中,很多工程師可能是在遺留代碼庫(kù)上工作,并且遺留代碼庫(kù)存在的時(shí)間還很長(zhǎng),那么很可能這個(gè)代碼庫(kù)中存在大量的無(wú)用代碼,但是誰(shuí)也不敢刪除他們,因?yàn)?JS 這門(mén)語(yǔ)言的動(dòng)態(tài)性,你不能粗暴的把哪些看起來(lái)“沒(méi)有被使用”的代碼直接刪掉,除非你很清楚所有的代碼執(zhí)行路徑,很顯然這對(duì)于大型應(yīng)用或者遺留代碼庫(kù)來(lái)說(shuō)是不現(xiàn)實(shí)的。
怎么移除死代碼呢?我們可以依賴(lài)打包工具,比如 UglifyJS 在壓縮代碼時(shí)支持直接刪除死代碼的配置項(xiàng)。而 Webpack 2 中引入了 Tree Shaking 的特性,能夠自動(dòng)把項(xiàng)目中沒(méi)有用到的代碼從打包中去掉,但是這種優(yōu)化僅限于被 export 的代碼??偠灾?,死代碼要盡可能想辦法去掉,Coverage 工具能提供一個(gè)判斷基準(zhǔn)。
懶加載代碼
如果能刪的死代碼都刪了,但是 Coverage 數(shù)據(jù)還是居高不下,那么你應(yīng)該換個(gè)角度思考。就像前文所說(shuō),JS 是動(dòng)態(tài)語(yǔ)言,可能部分代碼在頁(yè)面加載時(shí)并沒(méi)有用到,但是用戶(hù)后來(lái)的操作會(huì)觸發(fā)這些代碼的執(zhí)行,為什么不讓這些代碼在需要的時(shí)候再加載呢?聰明的你可能已經(jīng)想到了,這就是懶加載的技術(shù)。
使用 Webpack 打包且沒(méi)有對(duì)配置做特別調(diào)優(yōu)的話,它默認(rèn)會(huì)把所有依賴(lài)打包成一個(gè)巨大的文件,很容易出現(xiàn)首次加載覆蓋率很低的情況,在 Webpack 中實(shí)現(xiàn)懶加載可以參考 Code Splitting 和 bundle-loader,具體的配置細(xì)節(jié)這里不展開(kāi)講。使用懶加載之后可以極大的減少頁(yè)面初次下載的代碼,從而提高性能。需要注意的是,懶加載優(yōu)化需要在模塊數(shù)量和模塊大小之間把握一個(gè)平衡,否則過(guò)多的模塊懶加載反而對(duì)性能不利,因?yàn)槊總€(gè) HTTP 請(qǐng)求也是有額外開(kāi)銷(xiāo)的。
webpack-bundle-analyzer:查看資源樹(shù)
我使用的是vue-cli 3.0,需要先安裝插件webpack-bundle-analyzer(npm安裝會(huì)很慢,推薦使用cnpm)
cnpm i webpack-bundle-analyzer
在vue.config.js中添加分析工具的配置:
module.exports = {
chainWebpack: (config) => {
/* 添加分析工具 */
if (process.env.NODE_ENV === 'production') {
config
.plugin('webpack-bundle-analyzer')
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
.end()
config.plugins.delete('prefetch')
} else {
}
}
}
再運(yùn)行
npm run build --report
會(huì)在瀏覽器打開(kāi)一個(gè)項(xiàng)目打包的情況圖,便于直觀地比較各個(gè)bundle文件的大小。

瘦身開(kāi)始
1. productionSourceMap:false
修改vue.config.js中的配置
module.exports = {
outputDir: `${srcFile}`, // 在npm run build時(shí) 生成文件的目錄 type:string, default:'dist'
productionSourceMap: false, // 是否在構(gòu)建生產(chǎn)包時(shí)生成 sourceMap 文件,false將提高構(gòu)建速度
}
把productionSourceMap改為false。不然在最終打包的文件中會(huì)出現(xiàn)一些map文件,map文件的作用在于:項(xiàng)目打包后,代碼都是經(jīng)過(guò)壓縮加密的,如果運(yùn)行時(shí)報(bào)錯(cuò),輸出的錯(cuò)誤信息無(wú)法準(zhǔn)確得知是哪里的代碼報(bào)錯(cuò)。
有了map就可以像未加密的代碼一樣,準(zhǔn)確的輸出是哪一行哪一列有錯(cuò)。
如果不關(guān)掉,生產(chǎn)環(huán)境是可以通過(guò)map文件看到源碼的。
2.路由懶加載
在router.js文件中,原來(lái)的靜態(tài)引用方式
import ShowBlogs from '@/components/ShowBlogs'
routes:[ path: 'Blogs', name: 'ShowBlogs', component: ShowBlogs ]
改為
routes:[ path: 'Blogs',name: 'ShowBlogs',component: () => import('./components/ShowBlogs.vue')
以函數(shù)的形式動(dòng)態(tài)引入,這樣就可以把各自的路由文件分別打包,只有在解析給定的路由時(shí),才會(huì)下載路由組件。
3. 關(guān)閉Prefetch
因?yàn)関uecli 3默認(rèn)開(kāi)啟prefetch(預(yù)先加載模塊),提前獲取用戶(hù)未來(lái)可能會(huì)訪問(wèn)的內(nèi)容
在首屏?xí)堰@十幾個(gè)路由文件,都一口氣下載了
所以我們要關(guān)閉這個(gè)功能,在vue.config.js中設(shè)置
參考官網(wǎng)的做法:

4.element-ui組件按需加載
首屏需要加載的依賴(lài)包,其中element-ui整整占了568k
原本的引進(jìn)方式引進(jìn)了整個(gè)包:
import ElementUI from 'element-ui'
Vue.use(ElementUI)
按需引入
借助 babel-plugin-component,我們可以只引入需要的組件,以達(dá)到減小項(xiàng)目體積的目的。
首先,安裝 babel-plugin-component:
npm install babel-plugin-component -D
然后,將 .babelrc 修改為:
{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
接下來(lái),如果你只希望引入部分組件,比如 Button 和 Select,那么需要在 main.js 中寫(xiě)入以下內(nèi)容:
import Vue from 'vue';
import { Button, Select } from 'element-ui';
import App from './App.vue';
Vue.component(Button.name, Button);
Vue.component(Select.name, Select);
/* 或?qū)憺? * Vue.use(Button)
* Vue.use(Select)
*/
new Vue({
el: '#app',
render: h => h(App)
});
注意:有一些組件的引入方式有所不同,具體的參照element官網(wǎng)介紹。
Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$confirm = MessageBox.confirm;
...
5.使用 CDN 外部加載資源-vue, vuex, vue-router,axios
對(duì)于vue, vuex, vue-router,axios等我們可以利用wenpack的externals參數(shù)來(lái)配置,這里我們?cè)O(shè)定只需要在生產(chǎn)環(huán)境中才需要使用:
// vue.config.js
const isProduction = process.env.NODE_ENV === 'production';
const cdn = {
css: [],
js: [
'https://cdn.bootcss.com/vue/2.5.17/vue.runtime.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',
'https://cdn.bootcss.com/axios/0.18.0/axios.min.js',
]
}
module.exports = {
chainWebpack: config => {
// 生產(chǎn)環(huán)境配置
if (isProduction) {
// 生產(chǎn)環(huán)境注入cdn
config.plugin('html')
.tap(args => {
args[0].cdn = cdn;
return args;
});
}
},
configureWebpack: config => {
if (isProduction) {
// 用cdn方式引入
config.externals = {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'axios': 'axios'
}
}
},
}
```javascript
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 使用CDN的CSS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style">
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet">
<% } %>
<!-- 使用CDN的JS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="preload" as="script">
<% } %>
</head>
<body>
<noscript>
<strong>We're sorry but eye-admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
</body>
</html>
6.使用 CDN 外部加載資源-echarts
這次優(yōu)化主要是針對(duì)echarts,在其文檔里也有提到按需加載,但是這次我們不用按需加載了,我想把echarts徹底干掉。
首先在index.html中引入echarts的外部CDN(如果需要地圖組件,也需要一并引入)
//index.html
<script src="https://cdn.bootcss.com/echarts/4.1.0/echarts.min.js"></script>
復(fù)制代碼然后在webpack.base.config.js中,做如下改動(dòng)
// module.exports中增加externals對(duì)象
module.exports = {
externals: {
"echarts": "echarts" //默認(rèn)是配置引用的庫(kù)(這里是echarts)暴露出的全局變量
},
}
7.gzip
拆完包之后,我們?cè)儆胓zip做一下壓縮
安裝compression-webpack-plugin
cnpm i compression-webpack-plugin -D
在vue.congig.js中引入并修改webpack配置
const path = require("path");
const CompressionPlugin = require('compression-webpack-plugin');//引入gzip壓縮插件
const webpack = require("webpack");
// vue.config.js
module.exports = {
//基本路徑(相對(duì)于服務(wù)器根目錄 靜態(tài)資源的相對(duì)路徑)
publicPath: process.env.NODE_ENV === "production" ? "/dist/" : "/", //font scss資源路徑 不同環(huán)境切換控制
productionSourceMap:false,//打包時(shí)不要map文件
//輸出文件目錄
outputDir: "dist",
//是否在保存的時(shí)候檢查
lintOnSave: true,
//放置生成的靜態(tài)資源 (js、css、img、fonts) 的 (相對(duì)于 outputDir 的) 目錄。
assetsDir: 'statick',
devServer: {
// host: 'localhost',
// host: "0.0.0.0",
// https: false, // https:{type:Boolean}
// open: true, //配置自動(dòng)啟動(dòng)瀏覽器 http://172.16.1.12:7071/rest/mcdPhoneBar/
// hotOnly: true, // 熱更新
port: 8090
// proxy:{
// '/': {
// target: 'http://192.168.0.125:3000/',
// changeOrigin: true,
// pathRewrite: {}
// },
},
configureWebpack: {//引入jquery
plugins: [
new webpack.ProvidePlugin({
$:"jquery",
jQuery:"jquery",
"windows.jQuery":"jquery"
}),
new CompressionPlugin({//gzip壓縮配置
test:/\.js$|\.html$|\.css/,//匹配文件名
threshold:10240,//對(duì)超過(guò)10kb的數(shù)據(jù)進(jìn)行壓縮
deleteOriginalAssets:false,//是否刪除原文件
})
]
},
};
在服務(wù)器我們也要做相應(yīng)的配置
如果發(fā)送請(qǐng)求的瀏覽器支持gzip,就發(fā)送給它gzip格式的文件
我的服務(wù)器是用express框架搭建的
只要安裝一下compression就能使用
const compression = require('compression')
app.use(compression())
結(jié)論
各次優(yōu)化的表格
| # | 打包后體積 | 壓縮后體積 | 首屏js資源 | 打包耗時(shí) |
|---|---|---|---|---|
| 優(yōu)化前 | 1.44M | 425k | 3M | 72s |
| 第一次優(yōu)化(路由懶加載) | 1.44M | 425k | 3M | 72s |
| 第二次優(yōu)化(element-ui按需加載) | 1.44M | 425k | 3M | 72s |
| 第三次優(yōu)化(引入外部CDN) | 1.44M | 425k | 3M | 72s |
- 1.遇到webpack打包性能問(wèn)題,先執(zhí)行npm run build --report分析一波,然后根據(jù)分析結(jié)果來(lái)做相應(yīng)的優(yōu)化,誰(shuí)占體積大就干誰(shuí).路由很多的復(fù)雜頁(yè)面,路由懶加載是肯定要做的
- 2.現(xiàn)在很多庫(kù)都有提供按需加載的功能,有需要的話可以按照官方文檔的做法來(lái)按需加載
- 3.webpack提供的externals可以配合外部資源CDN輕松大幅度減少打包體積,適用于echarts、jQuery、lodash這種暴露了一個(gè)全局變量的庫(kù)
- 4.千萬(wàn)不要忘了開(kāi)啟Gzip壓縮
- 5.本文講的只是針對(duì)于webpack層面的優(yōu)化,性能優(yōu)化不只這些,還有其他方面的優(yōu)化,比如頁(yè)面渲染優(yōu)化(減少重排)、網(wǎng)絡(luò)加載優(yōu)化等。
- 6.確定引入的必要性
前端發(fā)展到如今時(shí)期,倘若項(xiàng)目采用了 MVVM模式框架,數(shù)據(jù)雙向綁定,那么像 jQuery 這般類(lèi)庫(kù),不能說(shuō)沒(méi)有絲毫引入的必要,至少可以說(shuō)確實(shí)沒(méi)有引入的必要。對(duì)此,如果還有些顧慮,完全可以參考下 YOU MIGHT NOT NEED JQUERY;用原生寫(xiě)幾行代碼就可以解決的事兒,實(shí)在不易引入這么個(gè)龐然大物,平添煩惱。 - 7.避免類(lèi)庫(kù)引而不用
倘若這類(lèi)情況發(fā)生,對(duì)整個(gè)打包體積,不僅大而且虧。項(xiàng)目一旦大了,很難人為保證每個(gè)引入的類(lèi)庫(kù),都被有用到,尤其是二次開(kāi)發(fā)。所以工具的利用十分必要,強(qiáng)烈推薦類(lèi)如 Eslint 這般工具,并且注入對(duì)應(yīng)規(guī)則,對(duì)聲明卻未使用的代碼,給予強(qiáng)制提醒;這不僅可以有效的規(guī)避類(lèi)似情形發(fā)生(也適用于普通變量的檢測(cè)),而且還能使得團(tuán)隊(duì)代碼風(fēng)格,盡可能地保持相似;要知道代碼足夠遵守規(guī)則,也可讓壓縮工具更有效壓縮代碼,一舉多得,何樂(lè)不為?
后記:css是否要拆分
vuecli 3和vuecli2.x還有一個(gè)區(qū)別是
vuecli 3會(huì)默認(rèn)開(kāi)啟一個(gè)css分離插件 ExtractTextPlugin
每一個(gè)模塊的css文件都會(huì)分離出來(lái),整整13個(gè)css文件,而我們的首頁(yè)就請(qǐng)求了4個(gè),花費(fèi)了不少的資源請(qǐng)求時(shí)間
我們可以在vue.config.js中關(guān)閉它
css: {
// 是否使用css分離插件 ExtractTextPlugin
extract: false,
// 開(kāi)啟 CSS source maps?
sourceMap: false,
// css預(yù)設(shè)器配置項(xiàng)
loaderOptions: {},
// 啟用 CSS modules for all css / pre-processor files.
modules: false

打包出來(lái)的文件中,直接就沒(méi)有了css文件夾
取而代之的是整合起來(lái)的一個(gè)js文件,負(fù)責(zé)在一開(kāi)始就注入所有的樣式
首屏加載文件數(shù)減少,但體積變大,最終測(cè)下來(lái)速度沒(méi)有太大差異
所以,是否要css拆分就見(jiàn)仁見(jiàn)智,具體項(xiàng)目具體分析吧