Vue打包優化詳解


分析工具
Coverage:查看代碼的使用狀況
移除死代碼
懶加載代碼
webpack-bundle-analyzer:查看資源樹
  1. productionSourceMap:false
  2.路由懶加載
  3. 關閉Prefetch
  4.element-ui組件按需加載
  5.使用 CDN 外部加載資源-vue, vuex, vue-router,axios
  6.使用 CDN 外部加載資源-echarts
  7.gzip
結論
后記:css是否要拆分

分析工具

Coverage:查看代碼的使用狀況

Coverage 是chrome開發者工具的一個新功能,從字面意思上可以知道它是可以用來檢測代碼在網站運行時有哪些js和css是已經在運行,而哪些js和css是還沒有用到的,如圖,這是我在打開csdn網頁時,所顯示的已運行和尚未運行的代碼情況。

 

 

 

那這個新功能有什么作用呢?

如上圖所示,最右邊顯示的是我們加載的css和js文件數量,紅色區域表示已運行的代碼,而青色表示已加載但未運行的代碼。可用來發現頁面中尚未用到的js 和 css代碼,你可以為用戶只提供必要的代碼,這樣就可以提升頁面的性能。這對於找出可以進行拆分的腳本以及延遲加載非關鍵腳本來說非常有用。

上面錄制的數據中,最大的文件是 vendor.js,其中 55% 的代碼都沒有執行過,約 80 KB,這已經相當於一張典型圖片的文件大小了。
如果某個文件覆蓋率低(即未使用代碼比例很高),通常意味着用戶加載了太多不必要的代碼(要么真的是無用代碼,要么是當前時點還沒執行到的代碼),有性能常識的同學不難推斷出,這會導致頁面的完全加載時間、或單頁應用的啟動時間變慢,在慢速網絡下的性能損耗會尤其明顯;此外,更多代碼的解析、編譯也就意味着更多的硬件資源消耗,在低端設備上也會存在明顯的性能問題。

在筆者看來,Coverage 數據至少能從下面 2 個方面指導我們進行 WEB 應用的優化:

移除死代碼

以 Coverage 數據為參考,我們能了解頁面重無用代碼的比例到底有多大。現實世界中,很多工程師可能是在遺留代碼庫上工作,並且遺留代碼庫存在的時間還很長,那么很可能這個代碼庫中存在大量的無用代碼,但是誰也不敢刪除他們,因為 JS 這門語言的動態性,你不能粗暴的把哪些看起來“沒有被使用”的代碼直接刪掉,除非你很清楚所有的代碼執行路徑,很顯然這對於大型應用或者遺留代碼庫來說是不現實的。
怎么移除死代碼呢?我們可以依賴打包工具,比如 UglifyJS 在壓縮代碼時支持直接刪除死代碼的配置項。而 Webpack 2 中引入了 Tree Shaking 的特性,能夠自動把項目中沒有用到的代碼從打包中去掉,但是這種優化僅限於被 export 的代碼。總而言之,死代碼要盡可能想辦法去掉,Coverage 工具能提供一個判斷基准。

懶加載代碼

如果能刪的死代碼都刪了,但是 Coverage 數據還是居高不下,那么你應該換個角度思考。就像前文所說,JS 是動態語言,可能部分代碼在頁面加載時並沒有用到,但是用戶后來的操作會觸發這些代碼的執行,為什么不讓這些代碼在需要的時候再加載呢?聰明的你可能已經想到了,這就是懶加載的技術。
使用 Webpack 打包且沒有對配置做特別調優的話,它默認會把所有依賴打包成一個巨大的文件,很容易出現首次加載覆蓋率很低的情況,在 Webpack 中實現懶加載可以參考 Code Splitting 和 bundle-loader,具體的配置細節這里不展開講。使用懶加載之后可以極大的減少頁面初次下載的代碼,從而提高性能。需要注意的是,懶加載優化需要在模塊數量和模塊大小之間把握一個平衡,否則過多的模塊懶加載反而對性能不利,因為每個 HTTP 請求也是有額外開銷的。

webpack-bundle-analyzer:查看資源樹

我使用的是vue-cli 3.0,需要先安裝插件webpack-bundle-analyzer(npm安裝會很慢,推薦使用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 {
    }
  }
}

再運行

npm run build --report

會在瀏覽器打開一個項目打包的情況圖,便於直觀地比較各個bundle文件的大小。

 

 

 瘦身開始

1. productionSourceMap:false

修改vue.config.js中的配置

module.exports = {
  outputDir: `${srcFile}`, // 在npm run build時 生成文件的目錄 type:string, default:'dist'
  productionSourceMap: false, // 是否在構建生產包時生成 sourceMap 文件,false將提高構建速度
}

把productionSourceMap改為false。不然在最終打包的文件中會出現一些map文件,map文件的作用在於:項目打包后,代碼都是經過壓縮加密的,如果運行時報錯,輸出的錯誤信息無法准確得知是哪里的代碼報錯。

有了map就可以像未加密的代碼一樣,准確的輸出是哪一行哪一列有錯。
如果不關掉,生產環境是可以通過map文件看到源碼的。

2.路由懶加載

在router.js文件中,原來的靜態引用方式

import ShowBlogs from '@/components/ShowBlogs'

routes:[ path: 'Blogs', name: 'ShowBlogs', component: ShowBlogs ]

改為

routes:[ path: 'Blogs',name: 'ShowBlogs',component: () => import('./components/ShowBlogs.vue')

 

 

 以函數的形式動態引入,這樣就可以把各自的路由文件分別打包,只有在解析給定的路由時,才會下載路由組件。

3. 關閉Prefetch

因為vuecli 3默認開啟prefetch(預先加載模塊),提前獲取用戶未來可能會訪問的內容
在首屏會把這十幾個路由文件,都一口氣下載了
所以我們要關閉這個功能,在vue.config.js中設置
參考官網的做法:

 

 

4.element-ui組件按需加載

首屏需要加載的依賴包,其中element-ui整整占了568k
原本的引進方式引進了整個包:

import ElementUI from 'element-ui'
Vue.use(ElementUI)

按需引入
借助 babel-plugin-component,我們可以只引入需要的組件,以達到減小項目體積的目的。 

首先,安裝 babel-plugin-component:

npm install babel-plugin-component -D

然后,將 .babelrc 修改為:

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

接下來,如果你只希望引入部分組件,比如 Button 和 Select,那么需要在 main.js 中寫入以下內容:

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);
/* 或寫為
 * Vue.use(Button)
 * Vue.use(Select)
 */

new Vue({
  el: '#app',
  render: h => h(App)
});

注意:有一些組件的引入方式有所不同,具體的參照element官網介紹。

Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$confirm = MessageBox.confirm;
...

5.使用 CDN 外部加載資源-vue, vuex, vue-router,axios

對於vue, vuex, vue-router,axios等我們可以利用wenpack的externals參數來配置,這里我們設定只需要在生產環境中才需要使用:

// 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 => {
        // 生產環境配置
        if (isProduction) {
            // 生產環境注入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

這次優化主要是針對echarts,在其文檔里也有提到按需加載,但是這次我們不用按需加載了,我想把echarts徹底干掉。
首先在index.html中引入echarts的外部CDN(如果需要地圖組件,也需要一並引入)

//index.html
<script src="https://cdn.bootcss.com/echarts/4.1.0/echarts.min.js"></script>

復制代碼然后在webpack.base.config.js中,做如下改動

// module.exports中增加externals對象
	module.exports = {
    	externals: {
        	"echarts": "echarts"        //默認是配置引用的庫(這里是echarts)暴露出的全局變量
    	},
}

7.gzip

拆完包之后,我們再用gzip做一下壓縮

安裝compression-webpack-plugin

 

cnmp i compression-webpack-plugin -D

 

在vue.congig.js中引入並修改webpack配置

const CompressionPlugin = require('compression-webpack-plugin')

configureWebpack: (config) => {
        if (process.env.NODE_ENV === 'production') {
            // 為生產環境修改配置...
            config.mode = 'production'
            return {
                plugins: [new CompressionPlugin({
                    test: /\.js$|\.html$|\.css/, //匹配文件名
                    threshold: 10240, //對超過10k的數據進行壓縮
                    deleteOriginalAssets: false //是否刪除原文件
                })]
            }
        }

在服務器我們也要做相應的配置
如果發送請求的瀏覽器支持gzip,就發送給它gzip格式的文件
我的服務器是用express框架搭建的
只要安裝一下compression就能使用

const compression = require('compression')
app.use(compression())

結論

  1. 遇到webpack打包性能問題,先執行npm run build --report分析一波,然后根據分析結果來做相應的優化,誰占體積大就干誰.路由很多的復雜頁面,路由懶加載是肯定要做的
  2. 現在很多庫都有提供按需加載的功能,有需要的話可以按照官方文檔的做法來按需加載
  3. webpack提供的externals可以配合外部資源CDN輕松大幅度減少打包體積,適用於echarts、jQuery、lodash這種暴露了一個全局變量的庫
  4. 千萬不要忘了開啟Gzip壓縮
  5. 本文講的只是針對於webpack層面的優化,性能優化不只這些,還有其他方面的優化,比如頁面渲染優化(減少重排)、網絡加載優化等。
  6. 確定引入的必要性
  7. 前端發展到如今時期,倘若項目采用了 MVVM模式框架,數據雙向綁定,那么像 jQuery 這般類庫,不能說沒有絲毫引入的必要,至少可以說確實沒有引入的必要。對此,如果還有些顧慮,完全可以參考下 YOU MIGHT NOT NEED JQUERY;用原生寫幾行代碼就可以解決的事兒,實在不易引入這么個龐然大物,平添煩惱。
  8. 避免類庫引而不用
  9. 倘若這類情況發生,對整個打包體積,不僅大而且虧。項目一旦大了,很難人為保證每個引入的類庫,都被有用到,尤其是二次開發。所以工具的利用十分必要,強烈推薦類如 Eslint 這般工具,並且注入對應規則,對聲明卻未使用的代碼,給予強制提醒;這不僅可以有效的規避類似情形發生(也適用於普通變量的檢測),而且還能使得團隊代碼風格,盡可能地保持相似;要知道代碼足夠遵守規則,也可讓壓縮工具更有效壓縮代碼,一舉多得,何樂不為?

后記:css是否要拆分

vuecli 3和vuecli2.x還有一個區別是
vuecli 3會默認開啟一個css分離插件 ExtractTextPlugin
每一個模塊的css文件都會分離出來,整整13個css文件,而我們的首頁就請求了4個,花費了不少的資源請求時間
我們可以在vue.config.js中關閉它

css: {
// 是否使用css分離插件 ExtractTextPlugin
extract: false,
// 開啟 CSS source maps?
sourceMap: false,
// css預設器配置項
loaderOptions: {},
// 啟用 CSS modules for all css / pre-processor files.
modules: false
}

 

打包出來的文件中,直接就沒有了css文件夾
取而代之的是整合起來的一個js文件,負責在一開始就注入所有的樣式
首屏加載文件數減少,但體積變大,最終測下來速度沒有太大差異
所以,是否要css拆分就見仁見智,具體項目具體分析吧


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM