最近一個小夥伴問我他們公司的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)