1. 程式人生 > >優化 Vue 專案編譯檔案大小

優化 Vue 專案編譯檔案大小

與其說是優化 Vue,不如說主要是在 webpack 打包的配置中做些文章,使得 Vue 編譯後的檔案儘可能的小。以下介紹自己在專案中進行優化的過程,其中的內容也許並不適合於每個專案,但整體思路是差不多的。

定位問題

要想進行優化,首先我們得清楚問題所在。即:是哪些程式碼/依賴包導致最後的編譯檔案過大?

這裡,我們需要使用 webpack-bundle-analyzer 工具。修改 package.json 檔案,新增:

"analyze": "NODE_ENV=production npm_config_report=true npm run build"

然後執行:

npm run analyze

便會在瀏覽器中開啟一個頁面,展示編譯後的檔案大小及各部分內容大小。以下是專案在優化前的分析結果:

從圖中可以看出,最後編譯出的 vendor.js 檔案達到了 5MB,其中主要來自 echarts。此外,由於 element-ui 在使用時已經注意到按需載入元件,所以可優化的部分不多;而 lodash 由於沒有按需載入,所以成為需要優化的另一個核心部分。

使用按需載入優化

這裡主要是對 lodash 進行優化。當我們在使用 lodash 時,如果使用:

import _ from 'lodash'

_.get(obj, 'key', 'default_value')

這種方式的話,則在編譯時會預設將 lodash 的全部內容進行編譯打包。

webpack 的介紹中確實提到了按需載入,但這個概念可能會出現理解上的偏差,下面我們舉例說明:

// 方法一:會導致載入全部的 lodash 庫
import _ from 'lodash'
_.get()

// 方法二:只會載入其中的 get 方法
import get from 'lodash/get'
get()

即在不新增其他外掛和配置的情況下,webpack 還做不到如此智慧。

想要實現在使用方法一的情況下,也能按照我們使用過的方法真正「按需載入」,則需要使用外掛並新增配置:

首先執行:

npm i --save-dev babel-plugin-lodash babel-cli babel-preset-es2015

然後修改 .babelrc

{
  "plugins": ["lodash"],
  "presets": ["es2015"]
}

之後修改 webpack.prod.conf.js

module: {
    loaders: [{
        'loader': 'babel-loader',
        'test': /\.js$/,
        'exclude': /node_modules/,
        'query': {
            'plugins': ['lodash'],
            'presets': ['es2015']
        }
    }]
}

這之後便可以實現按需載入 lodash 了。重新進行分析,會發現 lodash 部分的大小已經可以忽略不計了。

對於其他的,如 Element-UI 之類的第三方庫,如果我們只使用到了為數不多的元件,建議查詢相應的按需載入外掛和配置方式,這樣可以極大的減少該部分編譯的大小。

路由懶載入

當我們配合 Vue-Router 構建單頁應用時,大量的元件會導致首屏載入緩慢,如官方文件所言:

當打包構建應用時,Javascript 包會變得非常大,影響頁面載入。如果我們能把不同路由對應的元件分割成不同的程式碼塊,然後當路由被訪問的時候才載入對應元件,這樣就更加高效了。

只需將原有的:

import Test from '../pages/test'

export default new Router({
    routes: [
        {
            path: '/test',
            name: 'test',
            component: Test
        }
    ]
});

改為:

const Test = () => import('../pages/test')

export default new Router({
    routes: [
        {
            path: '/test',
            name: 'test',
            component: Test
        }
    ]
});

注意首行的不同。

第三方庫懶載入

在實際開發中,可能存在這樣的場景:

在某個元件/檔案中需要使用 moment 第三方庫來進行時間處理,但其他元件根本用不到。

如果我們這樣引入 moment:

import moment from 'moment'

export default {
    data () {
        
    },
    mounted () {
        
    }
}

則該庫會合並在 vendor.js 中,造成首屏載入緩慢。

為了解決這個問題,我們可以改成以下引入方式:

export default {
    name: '',
    beforeCreate () {
        import('moment').then(module => {
            this.moment = module;
        });
    },
    data () {
        return {
            moment: null
        }
    }
}

這種方式可以使得 moment 庫只在該元件使用處引入。注意,這種方式需要考慮「moment 呼叫時機與 moment 使用的先後問題」。

注:如果該元件是頁面級別的元件,則使用「路由懶載入」中的方法就可以了。

使用 CDN 外部載入

如上所示,echarts 模組佔了很大的部分,由於沒有找到 echarts 按需載入的外掛,這裡我們通過外部引用的方式來減少編譯的大小。

首先,我們修改 index.html,從 CDN 中引入 echarts 檔案:

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

注意,如果需要地圖元件,也需要一併引入。

這之後我們需要刪除所有 import echarts from 'echarts' 的程式碼,即不再通過這種方式引入 echarts。

但問題來了,如果這麼做的話,webpack 在打包的時候會發現 echarts 變數不存在而停止編譯。解決辦法是,我們需要在 webpack 配置中告知編譯器,對於 echarts 變數使用了引入外部資源的方式。需要修改 webpack.base.config.js

module.exports = {
    externals: {
        "echarts": "echarts"
    },
}

這之後我們便可以直接使用 echarts 這一變數而不會導致編譯錯誤了。

當我們將所有采用之前方式引入 echarts 的程式碼刪除或註釋之後,再次進行分析,會發現編譯大小少了很多。

經過以上兩步,原本 5M 的編譯檔案變為了 1.67 M。

這之後,我們還可以根據分析結果,針對性地進行優化。如更換時間庫為更輕量級的 spacetime 等。

伺服器端開啟 gzip

使用 gzip 可以進一步壓縮檔案,使得伺服器傳遞給瀏覽器的檔案是經由壓縮之後的,待瀏覽器收到之後再解壓縮。要使用這一方式,需要伺服器端的支援,這裡以 Nginx 為例。

在 nginx.conf 中,新增如下配置:

gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
#gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png application/javascript;
gzip_vary off;

之後重新整理頁面( 注意禁用快取 ),觀察 js、css 等資原始檔的請求中是否包含 Content-Encoding: gzip,如果存在,則表明 gzip 已成功。

注意,在 gzip_types 中規定了哪些請求型別會使用 gzip 進行壓縮。對於沒有使用 gzip 的資原始檔,可將其 Content-type 型別加入 gzip_types 之中。

伺服器端渲染

注意,在以上對打包過程的優化中,受影響的主要是 vendor.js 檔案中第三方庫的部分( gzip 方法會影響全部資原始檔 )。

如果我們想繼續進行優化,就需要考慮伺服器端渲染了。

Vue 的作用機制實際是使用 js 向 html 中掛載元件,如果我們能夠將這一過程放在伺服器端進行,就可以不再向瀏覽器傳輸一部分驅動檔案,從而進一步減少瀏覽器所需的檔案大小。不過這一過程需要伺服器的額外支援,有興趣的同學可以參考:例項 PK ( Vue服務端渲染 VS Vue 瀏覽器端渲染 )

參考

莉莉安 · 3月23日

樓主~
您的第三方庫懶載入方式
我按照引入後,在頁面中寫此元件,會報錯說沒有註冊該元件

  回覆

有具體一點的使用方式嗎

— 莉莉安 · 3月23日

對於某庫(如 moment)採用懶載入的引入方式時,需要在真正使用時等待該庫檔案已經下載到瀏覽器。比如:

export default {
    name: '',
    beforeCreate () {
        import('moment').then(module => {

            // 這一過程會導致該庫檔案從遠端開始下載,只有下載完成才能使用該庫
            // 可以開啟瀏覽器的 network 看到這一過程
            this.moment = module;
        });
    },
    data () {
        return {
            // 初始化時該值為空
            // moment 懶載入中該值也為空
            // 只有當懶載入完成才有值
            moment: null
        }
    }
}

使用這種方式載入第三方庫,需要手動判斷懶載入是否完成。如果覺得麻煩,可以直接藉助路由懶載入。被納入路由懶載入的元件,其引用的第三方庫也會隨之實現懶載入。