基於vue-cli項目的webpack打包優化實踐

前言

看了不少打包優化的文章,不少都是基於原生的webpack配置,直接在webpack.config.js文件中修改配置的。可是vue-cli建立的項目已經封裝了基本的webpack配置,須要在vue.config.js文件中修改預置的webpack配置。不多看到這方面的文章,所以記錄一下本身的實踐過程和踩過的一些坑。javascript

本次使用技術的版本狀況:html

  • vue:2.6.10
  • @vue/cli:4.0.5
  • webpack:4.31.0

vue-cli中的webpack

要優化項目,首先咱們得了解vue-cli已經替咱們作過了哪些優化,也就是須要查看webpack已經配置了哪些選項。
使用vue inpsect輸出webpack配置,還能夠指定輸出的文件:vue inspect > output.jsvue

vue-cli提供了兩種方式來更改webpack配置:
一、原生配置方式,配置的結果將會被 webpack-merge 合併入最終的 webpack 配置。java

// vue.config.js
module.exports = {
    configureWebpack: {
        // 在這裏直接書寫webpack配置項...
    }
}
複製代碼

二、鏈式配置方式,vue-cli內部是使用webpack-chain這個插件來維護webpack配置的,由於能更細粒度的控制其內部配置,所以也是官方比較推薦的一個方式。node

// vue.config.js
module.exports = {
    chainWebpack: config => {
        config.resolve.alias.set('@assets', resolve(`src/assets`));
    },
}
複製代碼

這兩種方法能夠配合使用。
爲了簡便,也爲了少踩點兒坑,本次優化主要採用原生的webpack配置,也就是使用configureWebpack的方式。 優化過程分爲打包體積優化和打包速度優化。webpack

優化打包體積

使用webpack-bundle-analyzer分析打包體積

webpack官方提供一些插件分析打包性能。
git

  • webpack-chart:webpack stats 可交互餅圖。
  • webpack-visualizer:可視化並分析你的 bundle,檢查哪些模塊佔用空間,哪些多是重複使用的。
  • webpack-bundle-analyzer:一個 plugin 和 CLI 工具,它將 bundle 內容展現爲便捷的、交互式、可縮放的樹狀圖形式。
  • webpack bundle optimize helper:此工具會分析你的bundle,併爲你提供可操做的改進措施建議,以減小 bundle 體積大小。

咱們使用webpack-bundle-analyzer來分析打包體積。github

// yarn add analyze-webpack-plugin --dev

// vue.config.js
const AnalyzeWebpackPlugin = require('analyze-webpack-plugin')
module.exports = {
  configureWebpack: {
    plugins: [
      new AnalyzeWebpackPlugin({}),
    ],
  }
}
複製代碼

運行打包命令:yarn build,會自動打開分析結果頁面。web

webpack-bundle-analyzer使用三種指標衡量打包體積:
正則表達式

  • stat:輸入的文件大小,還未通過例如壓縮之類的轉換。
  • parsed:輸出的文件大小,代碼通過醜化壓縮後的大小。
  • gzip:開啓了gzip壓縮後的大小。
優化moment —— ContextReplacementPlugin

觀察上圖,能夠發現moment佔據了不小的比重,主要是一些本地化的語言包,默認都會打包進來。
對於普通應用來講,咱們只須要中文語言包就夠了。

優化前:

  • Stat: 540.76KB
  • Parsed: 234.36KB
  • Gzipped: 68.46KB

首先選擇合適的語言包設置語言環境

import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
複製代碼

ContextReplacementPlugin插件的做用是改變某個模塊的打包上下文,經過修改正則,來讓webpack只打包咱們想要的文件。

// yarn add webpack --dev

// vue.config.js
const webpack = require('webpack');
module.exports = {
  configureWebpack: {
    plugins: [
      new webpack.ContextReplacementPlugin(
        /moment[/\\]locale$/, // 這個參數代表了咱們要改變的打包上下文
        /zh-cn/ // 這個參數表示咱們只想打包這個正則匹配的文件
      )
    ]
  },
};
複製代碼

優化後:

  • Stat: 150.79KB
  • Parsed: 54.61KB
  • Gzipped: 17.96KB

原來393.36KB的語言包只保留中文後變爲僅有3.39KB。

關於moment打包,社區提供了不少方法,還有其餘一些方案能夠參考:github.com/jmblog/how-…

優化XLSX

咱們項目中使用了這個庫來生成excel並下載。
原來直接引入import { utils, writeFile } from 'xlsx';,打包後體積很是龐大。

優化前:

  • Stat: 1.23MB
  • Parsed: 920.85KB
  • Gzipped: 327.65KB

後來在issue區查到了解決方案,改成只引入mini版本:

import { utils, writeFile } from 'xlsx/dist/xlsx.mini.min.js';

若是使用的是typescript會報錯:

聲明一下模塊便可:

// modules.d.ts
declare module 'xlsx/dist/xlsx.mini.min.js';
複製代碼

優化後幾乎只剩了零頭:

  • Stat: 236.73KB
  • Parsed: 189.66KB
  • Gzipped: 60.79KB

注意,官方解釋這個xlsx這麼大是有緣由的,由於涉及到讀取文件,要支持一些比較老的格式。若是你的項目中只是用來生成excel,不涉及讀取文件,就能夠用這個mini版本;若是有涉及到讀取excel文件的操做,仍是老老實實全量引入吧。官方將來或許會提供只支持現代文件格式的輕量級版本。

lodash打包體積 —— lodash專用plugin

優化前:

  • Stat: 540.17KB
  • Parsed: 73.29KB
  • Gzipped: 25.74KB

須要使用兩個插件:

  • babel-plugin-lodash 用來精簡Lodash模塊的,只保留用到的方法。

  • lodash-webpack-plugin 這個插件經過用noop, identity, 或其餘更簡單的替代品來替換一些模塊的特性,使得打包後的體積更小(翻譯)。
    注意:這個插件默認會關閉一些lodash不經常使用的特性,能夠給插件傳遞options來開啓某些特性。

這兩個插件配合使用來使效果最大化。只須要在Babel插件中添加lodash,並在webpack配置中添加一個插件:

// yarn add babel-plugin-lodash lodash-webpack-plugin --dev

// babel.config.js
modules.exports = {
  // 其餘配置省略...
  plugins: ['lodash']
}

// vue.config.js
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
module.exports = {
  // ...
  configureWebpack: {
    plugins: [
      new LodashModuleReplacementPlugin()
    ]
  }
}
複製代碼

優化後:

  • Stat: 56.42KB
  • Parsed: 11.34KB
  • Gzipped: 3.81KB

低調了許多,找了很久才找到 XD

抽取公共代碼 —— splitChunks(webpack4以前使用commonChunkPlugin,webpack4以後使用splitChunks)

咱們項目中使用了西瓜播放器,發現xgplayer做爲第三方庫,並無被打包進chunk-vendors,而且還重複打包了兩次。

關於這個xgplayer,引用狀況是:有兩個頁面引用了一個公共的組件,這個組件引用了xgplayer。因此爲何xgplayer沒有打包進chunk-vendors?

看一下vue-cli預設的webpack配置:

// ...
optimization: {
    minimizer: [
      // ...
    ],
    splitChunks: {
      cacheGroups: {
        vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
        },
        common: {
          name: 'chunk-common',
          minChunks: 1,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    }
  }
複製代碼

vendors打包了node_modules裏符合條件的第三方庫,這個條件就是chunks: 'initial'
chunks表示要打包的這些chunks的類型,有三個值:

  • initial:初始的chunk,須要當即加載,其實就是main.ts裏經過import同步引入的模塊。
  • async:經過import()等動態引入的chunk,也就是按需引入的異步的模塊。
  • all:包含同步和異步的模塊。選了這個,將會打包全部test匹配到的模塊,這裏是node_modules,顯然是不合適的,由於有些第三方庫可能晚點纔會用到,好比這裏的xgplayer。

因此xgplayer雖然是經過import同步引入的,但引用它的兩個頁面組件在路由文件中是import()按需引入的,而且沒有在main.ts中引入xgplayer,因此天然不會打包到chunk-vendors裏。

因此應該按照異步模塊async或all的類型來打包。

// vue.config.js
module.exports = {
  // ...
  configureWebpack: {
    optimization: {
      splitChunks: {
        cacheGroups: {
          xgplayer: {
            name: 'xgplayer',
            test: /[\\/]node_modules[\\/]xgplayer[\\/]/,
            minSize: 0,
            minChunks: 1,
            reuseExistingChunk: true,
            chunks: 'all'
          }
        }
      }
    }
  }
}
複製代碼

優化後只打包了一份:

優化echarts —— IgnorePlugin

優化echarts的難點在於,項目前期使用了兩種方式:

  1. 引入了第三方的vue-echarts,參考這個組件寫了一個本身的公共組件base-chart,結果並無用到這個庫。 頁面的圖表有的是基於base-chart的同時按需引入相關組件如echarts/lib/line。
  2. 有的是直接引用原生的echart從0開始寫的組件。

這就致使了echarts全量引入,而且處處打包的問題。

解決方案:因爲咱們的首頁是登陸頁,沒有用到echarts,不須要第一次就加載echarts,所以要作兩件事來優化:

  1. 這個第三方vue-echarts在main.ts中全局註冊組件了,但其實並無使用,須要刪除全局引用,避免打包進chunk-vendors。
  2. 抽離重複打包的部分,合併進一個chunk。
// main.ts的組件註冊代碼也要註釋掉
// import ECharts from 'vue-echarts';
// Vue.component('echart', ECharts);

optimization: {
  splitChunks: {
    cacheGroups: {
      echarts: {
        name: 'echarts',
        test: /[\\/]node_modules[\\/]echarts[\\/]/,
        minSize: 0,
        minChunks: 1,
        reuseExistingChunk: true,
        chunks: 'all',
      },
    },
  },
},
複製代碼

通過優化後已經從chunk-vendor裏抽離出來,並把多處存在的echarts引用合併進了一個bundle。可是能夠看到體積仍是很大的。

github上有人就打包體積太大提了issue,做者建議使用在線builder,根據項目使用狀況按需打包。並說5.0版本可能會考慮減少打包體積。

可是實際使用過程當中打包到中途某些資源504網關超時了,重試了幾回都失敗,只好另尋他法。

使用webpack內置的IgnorePlugin插件來忽略項目中用不到的文件。能夠對照在線builder的網址。
分別從node_modules/echarts/lib目錄下的component、chart、coord三個目錄進行排除。

IgnorePlugin插件配置項中,須要先使用contextRegExp來肯定即將要排除的文件的上下文,這裏是echarts目錄。
而後使用resourceRegExp來指定要排除的資源的正則表達式。
實際上,這裏只排除了這些目錄,還有一些跟目錄同級的文件,可能跟要排除的這些圖表/組件相關,可是爲了不誤判,就作不到那麼精細了。

plugins: [
  new webpack.IgnorePlugin({
    resourceRegExp:
      /^\.\/lib\/(component\/visualMap|toolbox|timeline|geo|brush|calendar)|(chart\/effectScatter|candlestick|heatmap|tree|treemap|sunburst|map|graph|boxplot|parallel|gauge|funnel|sankey|themeRiver|pictorialBar)|(coord\/polar|geo|singleAxis|calendar)$/,
    contextRegExp: /echarts$/
  })
]
複製代碼

優化後立馬小了很多:

優化ant-design-vue

優化前:

發現icons佔據很大的位置,可是實際使用的時候極少使用icons。
GitHub上面有人提了issue 做者解釋說button會自動引用icon,設計如此。ant-design已經在優化了,目前暫時使用了做者推薦的方法來按需引入icon:
增長一個別名,讓webpack解析的時候使用咱們提供的icons.js文件中的路徑,只打包使用過的icon。

resolve: {
  alias: {
    '@ant-design/icons/lib/dist$': resolve('./src/core/antd/icons.js')
  }
},
複製代碼

而後在src目錄下添加相應的文件,見github

export {
  default as SettingOutline
} from '@ant-design/icons/lib/outline/SettingOutline'
export {
  default as GithubOutline
} from '@ant-design/icons/lib/outline/GithubOutline'
export {
  default as CopyrightOutline
} from '@ant-design/icons/lib/outline/CopyrightOutline'

/* MultiTab begin */
export {
  default as CloseOutline
} from '@ant-design/icons/lib/outline/CloseOutline'
export {
  default as ReloadOutline
} from '@ant-design/icons/lib/outline/ReloadOutline'
export {
  default as DownOutline
} from '@ant-design/icons/lib/outline/DownOutline'
export {
  default as AlignLeftOutline
} from '@ant-design/icons/lib/outline/AlignLeftOutline'
/* MultiTab end */

/* Layout begin */
export {
  default as LeftOutline
} from '@ant-design/icons/lib/outline/LeftOutline'
export {
  default as RightOutline
} from '@ant-design/icons/lib/outline/RightOutline'
export {
  default as MenuFoldOutline
} from '@ant-design/icons/lib/outline/MenuFoldOutline'
export {
  default as MenuUnfoldOutline
} from '@ant-design/icons/lib/outline/MenuUnfoldOutline'
export {
  default as DashboardOutline
} from '@ant-design/icons/lib/outline/DashboardOutline'
export {
  default as VideoCameraOutline
} from '@ant-design/icons/lib/outline/VideoCameraOutline'
export {
  default as LoadingOutline
} from '@ant-design/icons/lib/outline/LoadingOutline'
export {
  default as GlobalOutline
} from '@ant-design/icons/lib/outline/GlobalOutline'
export {
  default as UserOutline
} from '@ant-design/icons/lib/outline/UserOutline'
export {
  default as LogoutOutline
} from '@ant-design/icons/lib/outline/LogoutOutline'
/* Layout end */
複製代碼

優化後已經低調了許多:

至此,項目打包已經獲得了很大程度的優化,對比優化前,打包的整體積減少了約1/3,壓縮後減少了約一半的體積,終於降到了KB級,可喜可賀:

優化前:

  • Stat: 10.54MB
  • Parsed: 4.94MB
  • Gzipped: 1.55MB

優化後:

  • Stat: 7.57MB
  • Parsed: 3.2MB
  • Gzipped: 1003.36KB
總結
  1. 對於首頁不須要的模塊,儘可能不要使用同步引用(import XX from '...')的方式引入到入口文件中,避免打包到一塊兒,以減小首次請求的時間,加快首頁的渲染速度;
  2. 使用第三方庫的時候儘可能按需引入,若是有須要,可使用IgnorePlugin或ContextReplacementPlugin告訴webpack咱們須要/不須要打包的文件;
  3. 使用splitChunks提取公共模塊,注意chunks這個屬性的值,若是是在按需引入(import())的vue組件中使用同步引入的模塊,chunks設置成initial是沒用的。這也是爲何vue-cli預設的splitChunks沒有幫咱們把某些重複代碼抽離出來,它只會幫咱們處理同步的模塊:
// ...
optimization: {
    minimizer: [
      // ...
    ],
    splitChunks: {
      cacheGroups: {
        vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
        },
        common: {
          name: 'chunk-common',
          minChunks: 1,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    }
}
複製代碼

優化打包速度

使用speed-measure-webpack-plugin插件測量打包各環節耗費時間

vue-cli中的使用方法

// yarn add speed-measure-webpack-plugin --dev

// vue.config.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

module.exports = {
  // 這裏沒法使用鏈式寫法chainWebpack,會報錯
  configureWebpack: smp.wrap({
    // ... webpack config goes here ...
  }
}
複製代碼

運行打包指令:yarn build

使用dll提取不常更新的公共庫

更新中...

相關文章
相關標籤/搜索