Vue項目優化小結

最近花了點時間優化了一下這幾個月開發的項目,在網上搜集了很多並實踐,如有不對歡迎指出;如有更好的優化方法,請告訴我!謝謝!

調試工具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的體積佔比很大,整個項目中,只有三處地方使用了lodashthrottle方法。

// 優化前
import * as _ from 'lodash'
private togglePicker = _.throttle(this.togglePickerAction, 400);

// 優化後
import * as _ from 'lodash/throttle'
private togglePicker = _(this.togglePickerAction, 400);

這樣打包的時候只會打包有關lodashthrottle的東西,再次打包會發現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項目優化小結

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章