最近花了點時間優化了一下這幾個月開發的項目,在網上搜集了很多並實踐,如有不對歡迎指出;如有更好的優化方法,請告訴我!謝謝!
調試工具network中的幾個參數
DOMContentLoaded、Finish和Load的含義
-
DOMContentLoaded: DOM樹構建完成;
-
Load:頁面加載完畢,包括DOM樹構建和請求css、圖片等外部資源;
-
Finish:頁面上所有http請求發送到響應完成的時間。
HTTP1.0/1.1 協議限定,單個域名的請求併發量是 6 個,即 Finish 是所有請求(不只是XHR請求,還包括DOC,img,js,css等資源的請求)在併發量爲6的限制下完成的時間
Load > DOMContentLoaded意味着請求外部資源時間較大;
Finish > Load意味着頁面請求量(和時間)較大(較長)。
Waterfall參數解析
顏色解析
- 淺灰:查詢中;
- 深灰:停滯,代理轉發,請求發送;
- 橙色:初始連接;
- 綠色:等待中;
- 藍色:內容下載。
優化方向
- 減少瀑布寬度,即減少資源的加載時間;
- 減少瀑布的高度,即減少請求的數量;
- 將綠色的”開始渲染“線向左移動,即通過優化資源請求順序來加快渲染時間。
Vue項目優化實踐
腳手架是v-cli3版本的。
前期準備
生成打包報告
npm run build --report
可打開打包後的report.html
查看打包體積,根據體積進行優化。
使用PageSpeed Insights
插件
可以對網址進行評分,還會給出一些建議。
接下來分幾種方向進行優化。
修改vue.config.js,優化打包體積
路由懶加載
不使用路由懶加載的話,會在一開始就下載完所有路由對應的組件文件。
// 修改前
import xxx from '@/components/xxx';
routes:[
path: 'xxx',
name: 'xxx',
component: xxx
]
// 修改後
routes:[
path: 'xxx',
name: 'xxx',
component: () => import(/* webpackChunkName: "Chat" */ './components/xxx')
]
關閉預加載模塊
雖然在上面我們配置了異步加載,但是因爲 vuecli 3
默認開啓 prefetch
(預先加載模塊),提前獲取用戶未來可能會訪問的內容,在首屏會把這十幾個路由文件,都一口氣下載了,所以我們要關閉這個功能。
// vue.config.js
module.exports = {
...
chainWebpack: (config) => {
// 移除 prefetch 插件
config.plugins.delete('prefetch-index')
// 移除 preload 插件
config.plugins.delete('preload-index');
},
...
}
Preload 是一個新的控制特定資源如何被加載的新的 Web 標準,這是已經在 2016 年 1 月廢棄的 subresource prefetch 的升級版。這個指令可以在 中使用,比如 。一般來說,最好使用 preload 來加載你最重要的資源,比如圖像,CSS,JavaScript 和字體文件。這不要與瀏覽器預加載混淆,瀏覽器預加載只預先加載在HTML中聲明的資源。preload 指令事實上克服了這個限制並且允許預加載在 CSS 和JavaScript 中定義的資源,並允許決定何時應用每個資源。
Prefetch 是一個低優先級的資源提示,允許瀏覽器在後臺(空閒時)獲取將來可能用得到的資源,並且將他們存儲在瀏覽器的緩存中。一旦一個頁面加載完畢就會開始下載其他的資源,然後當用戶點擊了一個帶有 prefetched 的連接,它將可以立刻從緩存中加載內容。有三種不同的 prefetch 的類型,link,DNS 和 prerendering。
按需加載
項目中使用vant
框架,加載依賴包時應該這麼操作:
// 優化前
import vantUI from 'vant'
Vue.use(vantUI)
// 優化後
import { Button, Toast, Picker, List, Cell, PullRefresh, Rate, Popup, Loading, DatetimePicker, ActionSheet } from 'vant'
import 'vant/lib/index.css';
Vue.use(Button);
Vue.use(Picker);
...
gzip壓縮
nmp install compression-webpack-plugin -D
// vue.config.js
const CompressionWebpackPlugin = require('compression-webpack-plugin');
module.exports = {
...
configureWebpack: () => {
let plugins = [];
...
if (process.env.NODE_ENV === "'production'"){
plugins.push(new CompressionWebpackPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: productionGzipExtensions,
threshold: 10240,
minRatio: 0.8,
deleteOriginalAssets:false//是否刪除源文件
}))
}
return {
output: { // 輸出重構 打包編譯後的 文件名稱 【模塊名稱.版本號.時間戳】
filename: `js/[name].${process.env.VUE_APP_Version}.${Timestamp}.js`,
chunkFilename: `js/[name].${process.env.VUE_APP_Version}.${Timestamp}.js`
},
plugins
}
},
...
}
打包之後文件被壓縮了~不過需要告訴服務器做相應的配置,如果發送請求的瀏覽器支持 gzip
,就發送給它 gzip
格式的文件。
CSS拆分
CSS是否要拆分就看具體環境了,拆分了之後請求的資源數會變多,不拆分請求的體積可能會很大。
module.exports = {
...
// css相關配置
css: {
// 是否使用css分離插件 ExtractTextPlugin,不需要改成false即可
extract: true,
// 開啓 CSS source maps?
sourceMap: false,
// css預設器配置項
loaderOptions: {},
// 啓用 CSS modules for all css / pre-processor files.
modules: false
},
...
}
CDN
是否使用CDN也看具體環境了,我用了CDN請求資源數更多了,首屏加載又變慢了°(°ˊДˋ°) °。
<!-- index.html -->
<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.1.3/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.1.2/vuex.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
// vue.config.js
module.exports = {
...
configureWebpack: () => {
...
externals:{
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios'
}
}
...
}
其他
在我的打包報告裏面,lodash
的體積佔比很大,整個項目中,只有三處地方使用了lodash
的throttle
方法。
// 優化前
import * as _ from 'lodash'
private togglePicker = _.throttle(this.togglePickerAction, 400);
// 優化後
import * as _ from 'lodash/throttle'
private togglePicker = _(this.togglePickerAction, 400);
這樣打包的時候只會打包有關lodash
的throttle
的東西,再次打包會發現lodash
中的體積已經大大減少。
補充
const path = require('path')
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const CompressionWebpackPlugin = require('compression-webpack-plugin')
const productionGzipExtensions = ['js', 'css']
const resolve = (dir) => path.join(__dirname, dir)
const Timestamp = new Date().getTime();
module.exports = {
productionSourceMap: false, // 是否在構建生產包時生成 sourceMap 文件,false將提高構建速度
outputDir: `release/${process.env.VUE_APP_Version}`,
lintOnSave: false,
publicPath: process.env.NODE_ENV === 'production' ? '/online/' : './',
chainWebpack: (config) => {
config.module.rule('pug')
.test(/\.pug$/)
.use('pug-html-loader')
.loader('pug-html-loader')
.end()
config.resolve.alias
.set('@', resolve('src'))
.end()
// 移除 prefetch 插件
config.plugins.delete('prefetch-index')
// 移除 preload 插件
config.plugins.delete('preload-index');
},
// lintOnSave: true,
pluginOptions: {
'style-resources-loader': {
preProcessor: 'less',
patterns: [
path.resolve(__dirname, 'src/assets/style/common.less')
]
}
},
// css相關配置
css: {
// 是否使用css分離插件 ExtractTextPlugin
extract: true,
// 開啓 CSS source maps?
sourceMap: false,
// css預設器配置項
loaderOptions: {},
// 啓用 CSS modules for all css / pre-processor files.
modules: false
},
configureWebpack: () => {
let plugins = [];
plugins.push(new SpeedMeasurePlugin());
if (process.env.NODE_ENV === "'production'"){
plugins.push(new CompressionWebpackPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: productionGzipExtensions,
threshold: 10240,
minRatio: 0.8,
deleteOriginalAssets:false//是否刪除源文件
}))
}
return {
output: { // 輸出重構 打包編譯後的 文件名稱 【模塊名稱.版本號.時間戳】
filename: `js/[name].${process.env.VUE_APP_Version}.${Timestamp}.js`,
chunkFilename: `js/[name].${process.env.VUE_APP_Version}.${Timestamp}.js`
},
plugins
}
},
pages: {
index: {
// page 的入口
entry: 'src/main.ts',
// 模板來源
template: 'public/index.html',
// 在 dist/index.html 的輸出
filename: 'index.html',
// 當使用 title 選項時,
// template 中的 title 標籤需要是 <title><%= htmlWebpackPlugin.options.title %></title>
title: 'Index',
// 在這個頁面中包含的塊,默認情況下會包含
// 提取出來的通用 chunk 和 vendor chunk。
chunks: ['chunk-vendors', 'chunk-common', 'index']
}
},
parallel: require('os').cpus().length > 1,
devServer: {
proxy: {}
}
}
運行優化
合理使用v-if和v-show
v-for加上key
使用Object.freeze優化長列表
初始化時,vue會對data做getter、setter改造,在現代瀏覽器裏,這個過程實際上挺快的,但仍然有優化空間。
Object.freeze()
可以凍結一個對象,凍結之後不能向這個對象添加新的屬性,不能修改其已有屬性的值,不能刪除已有屬性,以及不能修改該對象已有屬性的可枚舉性、可配置性、可寫性。該方法返回被凍結的對象。當你把一個普通的 JavaScript 對象傳給 Vue 實例的
data
選項,Vue 將遍歷此對象所有的屬性,並使用 Object.defineProperty 把這些屬性全部轉爲 getter/setter,這些 getter/setter 對用戶來說是不可見的,但是在內部它們讓 Vue 追蹤依賴,在屬性被訪問和修改時通知變化。但 Vue 在遇到像
Object.freeze()
這樣被設置爲不可配置之後的對象屬性時,不會爲對象加上 setter getter 等數據劫持的方法作者:Mr_Google
鏈接:https://juejin.im/post/5d5e89aee51d453bdb1d9b61
本項目裏面有很多純展示列表,所以修改如下:
this.list = Object.freeze(this.list.concat(list));
監聽事件、eventBus和計時器等即時銷燬
activated () {
document.addEventListener('click', this.handleClick)
},
deactivated () {
document.removeEventListener('click', this.handleClick)
},
someMethod(){
eventBus.$on(this.action);
}
beforeDestroy(){
eventBus.$off(this.action);
}
編譯優化
項目開發時候熱更新打包好慢…修改了.env.dev,在裏面加入這麼一句:
VUE_CLI_BABEL_TRANSPILE_MODULES:true
其他優化
合併CSS,精簡JS,優化圖片等。
首次發佈於:Vue項目優化小結