最近一個小夥伴問我他們公司的Vue後臺項目怎麼首次加載要十多秒太慢了,有什麼能優化的,因而乎我打開了他們的網站,發現主要耗時在加載vendor.js文件這個文件高達2M,因而乎我就拿來他們的代碼看看,進行了一番折騰。最終仍是取得了不錯的效果。javascript
對於網頁性能,如何提高加載速度、等原理以及操做,在 修言 大佬 這本 《前端性能優化原理與實踐》 書中介紹的很詳細,有興趣的小夥伴能夠去看看。css
本文將主要從
webpack
打包的角度進行一些首屏加載速度的優化,以及打包速度的優化的實踐html
我選取的是一個用vue-cli2.0+版本構建的 Vue
+ Vuex
+ Vue-router
+ axios
+ elment-ui
的一個後臺系統項目進行測試,大概有20個異步加載路由頁面。前端
咱們將優化分紅了3個主要的角度,每個角度優化後進行速度打包速度的測試,打包構建花費的時間列在下面:vue
優化resolve.modules、配置裝載機的 include & exclude、使用webpack-parallel-uglify-plugin 壓縮代碼java
配置 externals 使庫文件採用cdn加載node
webpack DllPlugin、webpack DllReferencePlugin 分離框架庫文件webpack
次數\打包耗時(s) | 原始配置用時 | 優化步驟1 | 優化步驟2 | 優化步驟3 |
---|---|---|---|---|
1 | 24.86 | ==23.86== | 11.22 | 13.92 |
2 | 23.52 | 14.51 | 11.04 | 12.63 |
3 | 25.49 | 14.04 | 11.29 | 13.19 |
4 | 24.84 | 14.56 | 11.25 | 13.14 |
5 | 24.60 | 15.44 | 11.86 | 14 |
由此可看出,仍是能達到顯著的提高了10多s左右效果。具體時間,固然跟你的項目又關係。接下來,咱們將介紹如何具體操做。ios
咱們首先經過修改基本的
webpack
配置的方式提高打包速率git
原理:
webpack 的 resolve.modules 是用來配置模塊庫(即 node_modules)所在的位置。當 js 裏出現 import 'vue' 這樣不是相對、也不是絕對路徑的寫法時,它便會到 node_modules 目錄下去找。
在默認配置下,webpack 會採用向上遞歸搜索的方式去尋找。但一般項目目錄裏只有一個 node_modules,且是在項目根目錄。爲了減小搜索範圍,可咱們以直接寫明 node_modules 的全路徑
因此平時在寫 import
導入模塊的時候引入指向的是具體的哪一個文件,也對打包速度的提高又必定的影響
操做:
打開 build/webpack.base.conf.js
文件,添加以下 modules
代碼塊:
module.exports = {
resolve: {
...
modules: [
resolve('src'),
resolve('node_modules')
],
...
},
複製代碼
原理:
webpack
的 loaders
裏的每一個子項均可以有 include 和 exclude 屬性:操做:
打開 build/webpack.base.conf.js
文件,添加以下 include
,exclude
配置:
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig,
include: [resolve('src')], // 添加配置
exclude: /node_modules\/(?!(autotrack|dom-utils))|vendor\.dll\.js/ // 添加配置
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')], // 添加配置
exclude: /node_modules/ // 添加配置
},
複製代碼
除此以外,若是咱們選擇開啓緩存將轉譯結果緩存至文件系統,則至少能夠將 babel-loader 的工做效率提高兩倍。要作到這點,咱們只須要爲 loader 增長相應的參數設定:
loader: 'babel-loader?cacheDirectory=true'
複製代碼
原理:
webpack
使用 UglifyJS
插件進行代碼壓縮,但因爲其採用單線程壓縮,速度很慢。webpack-parallel-uglify-plugin
插件,它能夠並行運行 UglifyJS
插件,從而更加充分、合理的使用 CPU 資源,從而大大減小構建時間,該插件能設置緩存,大大減少構建時間。操做: 1.安裝 webpack-parallel-uglify-plugin
插件
yarn add webpack-parallel-uglify-plugin -D
// or
npm i webpack-parallel-uglify-plugin -D
複製代碼
2.打開 build/webpack.prod.conf.js
文件,並做以下修改
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
...
// 刪掉webpack提供的UglifyJS插件
//new UglifyJsPlugin({
// uglifyOptions: {
// compress: {
// warnings: false
// }
// },
// sourceMap: config.build.productionSourceMap,
// parallel: true
//}),
// 增長 webpack-parallel-uglify-plugin來替換
new ParallelUglifyPlugin({
cacheDir: '.cache/',
uglifyJS:{
output: {
comments: false
},
compress: {
warnings: false,
drop_debugger: true, // 去除生產環境的 debugger 和 console.log
drop_console: true
}
}
}),
...
複製代碼
原理:
操做:
這一步具體操做,就沒貼代碼了,我感受沒做用不明顯,時間還加了一點點,多是跟項目有關把,想使用的小夥伴自行百度用到本身項目裏面試試。
當你把上面這些優化都作完了,運行build的時候發現第一次所須要的構建時間跟最開始同樣23s左右,稍微少了2秒(主要是優化resolve,loader等的效果)
再次build的時候時間大大減小,由於在跟目錄下 .cache/
下緩存了 Uglify
相關的js多以大大提升了構建的速度。趕忙去試試把。小夥伴們。
開頭說到因爲
vendor.js
過大引發的首頁加載慢,可是vue打包好的 vendor.js 是由什麼構成的呢?
vue-cli 生成的項目中 集成了 webpack-bundle-analyzer 依賴可視化分析工具
運行
npm run build --report
複製代碼
vendor.js
Parsed 後爲739kb,包主要包含了 像
Vue
、
Vue-router
、
elment-ui
等之類須要全局引入的庫文件。這些庫文件都是一些不常常變更的問題,因此咱們能夠考慮把他們分離出來,用cdn的方式把框架庫引入。
原理:
利用 webpack
的 externals
屬性 。文檔
官網的解釋 :防止 將某些 import 的包(package) 打包 到 bundle 中,而是在運行時(runtime)再去從外部獲取這些擴展依賴(external dependencies)。
通俗的解釋:讓某些資源包即便不在本地npm安裝,經過 script
標籤引入後也能使用
操做:
index.html
中添加如下內容<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>XXXX平臺</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/element-ui/2.4.1/theme-chalk/index.css">
</head>
<body>
<div id="app"></div>
<script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.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/axios/0.17.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/element-ui/2.4.1/index.js"></script>
<!-- built files will be auto injected -->
</body>
</html>
複製代碼
注意!版本號要與 package.json
中的版本號一致
build/webpack.base.conf.js
module.exports = {
...
externals: {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'axios': 'axios',
'element-ui': 'ELEMENT'
}
...
}
複製代碼
注意!這裏 axios
變量名要使用 axios
注意!這裏 element-ui
變量名要使用 ELEMENT
,由於element-ui
的 umd
模塊名是 ELEMENT
src/router/index.js
// import Vue from 'vue'
import VueRouter from 'vue-router'
// 註釋掉
// Vue.use(VueRouter)
...
}
複製代碼
src/store/index.js
...
// 註釋掉
// Vue.use(Vuex)
...
}
複製代碼
src/main.js
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
// 註釋掉
// import 'element-ui/lib/theme-chalk/index.css'
// router setup
import router from './router'
// Vuex setup
import store from './store'
Vue.use(ElementUI)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
複製代碼
完事
上面都配置好了後啓動 npm run build
發現構建時間在11-12s左右,爲何相比較於步驟1的提高並不大呢,由於步驟1中 ParallelUglifyPlugin
在重複構建中,並無改動代碼,緩存起了重要做用
vendor
包Parsed 後只有
24KB
左右,框架文件利用cdn加速,以及瀏覽器緩存機制,能夠顯著提高首頁的訪問速度。咱們能夠把文件部署在服務器上,打開Chrome network查看具體的加載用時。
缺點
vue-devtools
谷歌調試工具了,畢竟直接用的線上的資源包。可是,根據環境作區分修改部分代碼,就能夠實現開發環境用的本地包,打包後的使用cdn資源。具體請參考這位大佬的實踐 Vue SPA 首屏加載優化實踐 ,能夠區分環境來引入。webpack DllPlugin
、webpack DllReferencePlugin
預編譯第三方庫文件既然 cdn 仍是有他的弊端,那麼咱們爲什麼不考慮把庫文件合併呢,因此咱們利用
webpack.DllPlugin
+webpack DllReferencePlugin
+add-asset-html-webpack-plugin
預編譯而且引入
原理:
webpack DllPlugin
插件將第三方插件單獨打包出來至 vendor.dll.js
webpack DllReferencePlugin
是把這些預先編譯好的模塊引用起來add-asset-html-webpack-plugin
把vendor.dll.js
包插入html操做:
咱們仍是從操做1完成後繼續修改代碼(cdn的相關操做代碼退回)
build
文件夾中新建 webpack.dll.conf.js
文件,內容以下(主要是配置下須要提早編譯打包的庫):var path = require('path')
var webpack = require('webpack')
var context = path.join(__dirname, '..')
module.exports = {
entry: {
vendor: [
'vue/dist/vue.common.js',
'vuex',
'vue-router',
'axios',
'element-ui'
]
},
output: {
path: path.join(context, 'static/js'), // 打包後的 vendor.js放入 static/js 路徑下
filename: '[name].dll.js',
library: '[name]'
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
plugins: [
new webpack.DllPlugin({
path: path.join(context, '[name].manifest.json'),
name: '[name]',
context: context
}),
// 壓縮js代碼
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
output: { // 刪除打包後的註釋
comments: false
}
})
]
}
複製代碼
package.json
文件,添加一條編譯命令:"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"lint": "eslint --ext .js,.vue src",
"build": "node build/build.js",
"build:dll": "webpack --config build/webpack.dll.conf.js --progress"
},
複製代碼
而後命令行運行 npm run build:dll
這時,會在 static/js 裏面生成 vendor.dll.js
, vendor
屬性內的相關庫文件就打包在內了。
index.html
這邊將 vendor.dll.js
引入進來。<body>
<div id="app"></div>
<script src="./static/js/vendor.dll.js"></script>
</body>
複製代碼
build/webpack.base.conf.js
文件,編輯添加以下配置,做用是經過 DLLReferencePlugin 來使用 DllPlugin 生成的 DLL Bundleconst webpack = require('webpack');
module.exports = {
...
plugins: [
new webpack.DllReferencePlugin({
// name參數和dllplugin裏面name一致,能夠不傳
name: 'vendor',
// dllplugin 打包輸出的manifest.json
manifest: require('../vendor.manifest.json'),
// 和dllplugin裏面的context一致
context: path.join(__dirname, '..')
})
]
...
}
複製代碼
build/webpack.prod.js
註釋掉 CommonsChunkPlugin
相關代碼,由於庫文件在以前的 vendor.dll.js 中已經編譯好了,不須要在編譯module.exports = {
plugins: [
...
// 去掉這裏的CommonsChunkPlugin
// new webpack.optimize.CommonsChunkPlugin({
// name: 'vendor',
// minChunks (module) {
// // any required modules inside node_modules are extracted to vendor
// return (
// module.resource &&
// /\.js$/.test(module.resource) &&
// module.resource.indexOf(
// path.join(__dirname, '../node_modules')
// ) === 0
// )
// }
// }),
// 去掉這裏的CommonsChunkPlugin
// new webpack.optimize.CommonsChunkPlugin({
// name: 'manifest',
// minChunks: Infinity
// }),
...
]
}
複製代碼
完事
至此,保存代碼,進行構建,發現構建時間大概在14s左右。怎麼比cdn時間還增多了呢,由於element-ui的樣式文件還須要每次打包,樣式不建議單獨打包出來,要麼也是使用cdn的方式。
最後咱們仍是部署到服務器上打開Chrome network查看網頁具體的加載用時。
vendor
文件已經不見了,不須要每次打包了,直接引入
vendor.dll.js
文件就好,這樣還有一個好處:當你有多個項目的依賴相同的時候,引用同一份
dll
便可。
真的就完事兒了? 你們有沒有注意到 vendor.dll.js
是一個固定的文件,沒有加 hash 後綴,這對緩存來講是致命的,當你升級了庫或者增長了庫文件,從新打包後的 仍是叫作 vendor.dll.js
文件,沒有破壞緩存,當用戶訪問時程序可能會出現問題。
有時候開發環境和測試環境可能 引入的vendor.dll.js
路徑不同你得手動更改,也是一個問題。既然這樣怎麼辦呢??
還好有 add-asset-html-webpack-plugin
這個插件進行依賴資源的注入,本人在實踐的時候覺得找到了救命稻草。但是奈何不知道是姿式不對,仍是該插件已通過時未升級,程序運行時候報錯,沒法使用,也但願使用過的大佬,指點一下。。
至此關於 Vue SPA 項目中的優化,介紹的差很少了,可是僅僅只是提供一個思路,優化並非一成不變的,有些項目可能只須要步驟1,有些項目可能引用資源小採用cdn的方式也能夠,而有些多個項目依賴都相同,就可考慮dll,固然是根據具體的場景來進行選擇優化。
最終仍是以部署到服務器後,清除緩存訪問,後分析加載時間。畢竟加載時間比打包時間重要得多
可是,咱們平時寫代碼的時候應該多多思考,在寫代碼的時候注意一些細節,也能提高很多效率和性能。
舉個例子1:不少項目會用到 echarts
,我發現有小夥伴把 echarts
注入在 main.js
中,這顯然是不必的白白增大了 vendor.js
的大小,應該在僅僅須要使用的頁面去引入就好,還得注意echarts
的地圖組件,是採用同步渲染,仍是異步渲染好呢,還有根據窗口的 resize
,是否注意防抖和節流呢。
舉個例子2:當咱們使用百度地圖的jssdk的時候,是在 index.html
裏面經過 script
標籤引入,仍是在某個頁面須要使用地圖的時候採用異步加載的形式呢。這些都是值得咱們思考的問題。
因此從每一步寫代碼的細節多多思考。
至此寫完了,我也是抱着學習的態度,若有什麼錯誤,請大佬們斧正,順便請教 add-asset-html-webpack-plugin
的正確姿式。
相關代碼託管在github vue-spa-optimization 上,上面有4個分支
master:
:未作任何優化的原始版本simple:
作了上面步驟1中相關優化的版本cdn:
作了上面步驟1與步驟2優化的版本(cdn)dll:
作了上面步驟1與步驟3優化的版本(dll)