120、Vue打包優(yōu)化 詳解

這里寫(xiě)自定義目錄標(biāo)題

分析工具
Coverage:查看代碼的使用狀況
移除死代碼
懶加載代碼
webpack-bundle-analyzer:查看資源樹(shù)

    1. productionSourceMap:false
  • 2.路由懶加載

    1. 關(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)目具體分析吧

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容