看了不少打包優化的文章,不少都是基於原生的webpack配置,直接在webpack.config.js文件中修改配置的。可是vue-cli建立的項目已經封裝了基本的webpack配置,須要在vue.config.js文件中修改預置的webpack配置。不多看到這方面的文章,所以記錄一下本身的實踐過程和踩過的一些坑。javascript
本次使用技術的版本狀況:html
要優化項目,首先咱們得了解vue-cli已經替咱們作過了哪些優化,也就是須要查看webpack已經配置了哪些選項。
使用vue inpsect
輸出webpack配置,還能夠指定輸出的文件:vue inspect > output.js
。vue
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官方提供一些插件分析打包性能。
git
咱們使用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使用三種指標衡量打包體積:
正則表達式
觀察上圖,能夠發現moment佔據了不小的比重,主要是一些本地化的語言包,默認都會打包進來。
對於普通應用來講,咱們只須要中文語言包就夠了。
優化前:
首先選擇合適的語言包設置語言環境:
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/ // 這個參數表示咱們只想打包這個正則匹配的文件 ) ] }, }; 複製代碼
優化後:
原來393.36KB的語言包只保留中文後變爲僅有3.39KB。
關於moment打包,社區提供了不少方法,還有其餘一些方案能夠參考:github.com/jmblog/how-…
咱們項目中使用了這個庫來生成excel並下載。
原來直接引入import { utils, writeFile } from 'xlsx';
,打包後體積很是龐大。
優化前:
後來在issue區查到了解決方案,改成只引入mini版本:
import { utils, writeFile } from 'xlsx/dist/xlsx.mini.min.js';
若是使用的是typescript會報錯:
聲明一下模塊便可:
// modules.d.ts declare module 'xlsx/dist/xlsx.mini.min.js'; 複製代碼
優化後幾乎只剩了零頭:
注意,官方解釋這個xlsx這麼大是有緣由的,由於涉及到讀取文件,要支持一些比較老的格式。若是你的項目中只是用來生成excel,不涉及讀取文件,就能夠用這個mini版本;若是有涉及到讀取excel文件的操做,仍是老老實實全量引入吧。官方將來或許會提供只支持現代文件格式的輕量級版本。
優化前:
須要使用兩個插件:
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() ] } } 複製代碼
優化後:
低調了許多,找了很久才找到 XD
咱們項目中使用了西瓜播放器,發現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的類型,有三個值:
因此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的難點在於,項目前期使用了兩種方式:
這就致使了echarts全量引入,而且處處打包的問題。
解決方案:因爲咱們的首頁是登陸頁,沒有用到echarts,不須要第一次就加載echarts,所以要作兩件事來優化:
// 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$/ }) ] 複製代碼
優化後立馬小了很多:
優化前:
發現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級,可喜可賀:
優化前:
優化後:
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 } } } } 複製代碼
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
更新中...