深入理解基於vue-cli的webpack打包優化實踐及探索
轉眼已經是2019年,短短三四年時間,webpack打包工具成為了前端開發中必備工具,曾經一度的面試題都是問,請問前端頁面優化的方式有哪些?大家也是能夠信手拈來的說出快取、壓縮檔案、CSS雪碧圖以及部署CDN等等各種方法,但是今天不一樣了,可能你去面試問的就是,請問你是否知道webpack的打包原理,webpack的打包優化方法有哪些?所以該說不說的,筆者閒著沒事研究了一下webpack的打包優化,可能大家都有看過類似的優化文章~ 但是筆者還是希望能夠給大家一些新的啟發~
1、準備工作:測速與分析bundle
既然我們要優化webpack打包,肯定要提前對我們的bundle檔案進行分析,分析各模組的大小,以及分析打包時間的耗時主要是在哪裡,這裡主要需要用到兩個webpack外掛,speed-measure-webpack-plugin和webpack-bundle-analyzer,前者用於測速,後者用於分析bundle檔案。
具體配置
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin"); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin const smp = new SpeedMeasurePlugin({ outputFormat:"human", }); module.exports = { configureWebpack: smp.wrap({ plugins: [ new webpack.ProvidePlugin({ $: "zepto", Zepto: "zepto", }), new BundleAnalyzerPlugin(), ], optimization: { splitChunks: { cacheGroups: { echarts: { name: "chunk-echarts", test: /[\\/]node_modules[\\/]echarts[\\/]/, chunks: "all", priority: 10, reuseExistingChunk: true, enforce: true, }, demo: { name: "chunk-demo", test: /[\\/]src[\\/]views[\\/]demo[\\/]/, chunks: "all", priority: 20, reuseExistingChunk: true, enforce: true, }, page: { name: "chunk-page", test: /[\\/]src[\\/]/, chunks: "all", priority: 10, reuseExistingChunk: true, enforce: true, }, vendors: { name: "chunk-vendors", test: /[\\/]node_modules[\\/]/, chunks: "all", priority: 5, reuseExistingChunk: true, enforce: true, }, }, }, }, }) }
由於是基於vue-cli腳手架的,所以其實vue-cli中已經幫你做了一些優化的工作,可以看到,原先專案最初的配置設定了splitchunk,進行程式碼分割,這在大型專案中是很有必要的,畢竟你不希望你的使用者阻塞載入一個5MB大小的JS檔案,所以做程式碼分割和懶載入是很有必要的。
說遠了,我們來看看這個配置,你需要用smp對配置進行再包裹,因為SpeedMeasurePlugin會對你的其他Plugin物件包裹一層代理,這樣的目的是為了能夠知道plugin開始和結束的時間~
其次,BundleAnalyzerPlugin就跟普通的plugin一樣,載入plugins陣列的後面即可。
接下來我們看一下最初的打包時間以及包內容分析:
可以看到專案中較大的三個包,其中兩個包是我們的第三方依賴,three.js、lottie、lodash、echarts等。
2、開始逐步優化
2.1縮小檔案查詢和處理範圍
這是webpack優化中的常規操作,基本就是對模組和檔案查詢的優化,以及減少loader對一些不必要模組的處理,但是vue-cli中的loader並沒有暴露給我們操作,所以其內建的loader處理無法由我們進行優化,但是其實vue-cli中的配置項已經對loader的查詢路徑進行了優化,如果你的專案也是使用了vue-cli,你可以通過以下命令列檢視你現有的配置檔案是怎樣的:
npx vue-cli-service inspect > output.js
具體可以翻閱vuecli官方文件。
resolve:{ modules: [path.resolve(__dirname, 'node_modules')], alias:{ 'three':path.resolve(__dirname, './node_modules/three/build/three.min.js'), 'zepto$':path.resolve(__dirname, './node_modules/zepto/dist/zepto.min.js'), 'swiper$':path.resolve(__dirname, './node_modules/swiper/dist/js/swiper.min.js'), 'lottie-web$':path.resolve(__dirname, './node_modules/lottie-web/build/player/lottie.min.js'), 'lodash$':path.resolve(__dirname, './node_modules/lodash/lodash.min.js'), } }, module:{ noParse:/^(vue|vue-router|vuex|vuex-router-sync|three|zepto|swiper|lottie-web|lodash)$/ },
- 通過modules指定查詢第三方模組的路徑。
- 通過alias指定第三方模組直接查詢到打包構建好的壓縮js檔案。
- 通過module指定noparse,對第三方模組不再進行分析依賴。
優化效果:2s?
可以看到時間就減少了兩三秒,在30s波動,感覺沒有多大差別。
2.2嘗試使用happypack
由於在進行webpack優化前,翻閱了很多有關webapck優化的文章,所以筆者也想嘗試一下用happypack來優化打包時間。
在想要用happypack進行的打包之前,大抵有這兩種說法:
1、webpack4中已經預設是多執行緒打包了,所以happypack打包效果不明顯;
2、vue不支援happypack打包,需要設定thread-loader。
但是筆者想了一下,還是試試看把,大不了我只對JS和CSS檔案設定happypack。
但是問題又來了,vue-cli內建封裝了loader,這個時候我要怎麼拿到它的配置,改寫裡面的loader配置呢。
通過翻閱vue-cli的官方文件我們可以看到以下使用介紹:
configureWebpack
Type: Object | Function
如果這個值是一個物件,則會通過 webpack-merge 合併到最終的配置中。
如果這個值是一個函式,則會接收被解析的配置作為引數。該函式及可以修改配置並不返回任何東西,也可以返回一個被克隆或合併過的配置版本。
為此,筆者特地除錯進了vue-cli的原始碼一探究竟:
流程介紹:
由於我們執行命令列vue-cli-service build,其實是先去node_modules的.bin資料夾下查詢相應的可執行檔案,.bin下的vue-cli-service會對映到相應的第三方庫內的執行檔案。
所以我們可以找到這個可執行檔案的地址:
/node_modules/@vue/cli-service/bin/vue-cli-service.js
找到了入口,接下來我們想要進入nodejs的除錯,在以往的開發中,我們會通過node --inspect app.js的方式啟動一個後臺服務,然後在谷歌瀏覽器裡進入除錯介面(F12選擇綠色的那個小按鈕)
但是這裡卻犯了難,由於我們的打包構建是一次執行的,不同於一個後臺服務,是實時監聽的,服務一直啟動著。查閱了一下,如果是普通的nodejs檔案想要除錯的話,需要通過這樣的方式:
node --inspect-brk=9229 app.js
所以,為了強行走進去vue-cli的原始碼進行除錯,可看vue-cli的處理流程,我們需要這樣輸入以下命令列:
node --inspect-brk=9229 node_modules/@vue/cli-service/bin/vue-cli-service.js build
上面的這個命令列,等價於vue-cli-service build。
通過這樣的方式,我們終於走進了vue-cli的原始碼,看了它的執行流程,你可以在對應的位置打下斷點,檢視此時的作用域內的變數資料。
可以看到vue-cli原始碼裡的這一段操作,會執行我們傳入的函式,判斷函式有沒有返回值來決定是否要merge進其內部配置的config。
通過這段程式碼我們可以看出,如果我們configWepack配置為函式,之後通過引數的形式獲取到config配置項,本身是一個物件,物件是保留引用的形式,所以如果我們直接對傳入的config物件進行修改,就可以實現我們最初的目標!修改vue-cli內建的loader!
當然,除了斷點進入裡面看配置,剛才也說了,我們可以通過命令列輸出為一個output檔案檢視現有的配置。
這裡可以給大家截圖看一下vue-cli內部的配置:
可能有點廢話了,但是通過斷點的方式,我們可以看到vue-cli其實已經對js檔案設定了exclude,同時也幫我們設定好了cache-loader,意味著webpack常規的優化方式之一,使用cache-loader快取它也幫我們做了。
回到最初的起點,我們想要處理的是針對JS和CSS的loader,於是模仿大多數的配置,我進行了以下修改:
configureWebpack:(config)=>{ console.log("webpack config start"); let originCssRuleLoader = config.module.rules[6].oneOf[0].use; let newCssRuleLoader = 'happypack/loader?id=css'; config.module.rules[6].oneOf[0].use = newCssRuleLoader config.module.rules[6].oneOf[1].use = newCssRuleLoader config.module.rules[6].oneOf[2].use = newCssRuleLoader config.module.rules[6].oneOf[3].use = newCssRuleLoader ...//other code }
嘗試對css的loader配置進行修改。之後對plugins進行一下配置:
plugins: [ new HappyPack({ id: 'css', threads: 4, loaders: originCssRuleLoader }), ],
本以為這樣就OK了,但是很遺憾的告訴大家,報錯了...
可以看到報錯的內容,是在處理vue檔案的時候,出了錯誤。
如何解決
筆者百度了,也谷歌了,大抵是說happypack不支援vue-loader,同時,根據報錯也查了一下處理的方案,通過設定parallel引數,也還是無效。
筆者甚至懷疑是自己的happypack配置不對,於是我把配置原樣移植配置到另一個非vue專案中,一切執行正常。
答案:此題無解~
原因分析:
由於vue檔案中會含有CSS,所以vue-loader會提取出其中的css,交給其他loader處理,vue-loader-plugin會通過在vue檔案後面加上查詢字串來告訴其他loader,針對這個檔案要做處理。意味著什麼呢?我們的vue-loader在處理檔案的時候,通知其他loader處理,但是此時的loader配置已經被我們改寫成了happypack,而vue又與happypack不相容,最終導致了報錯。很遺憾的告訴大家,vue-cli接入happypack--失敗。
(注:這一部分主要是筆者在webpack優化過程中的探索,雖然最終不能讓自己的webpack打包很好的優化,但是在這個探索的過程中,我們也可以學到很多~包括 vue-cli對配置物件的處理?如何除錯普通檔案nodejs程式碼?vue-loader中對vue檔案的處理流程?vue-loader-plugin幫我們做了什麼事?而這些都是要自己慢慢翻閱,慢慢踩坑去了解的~)
2.3使用dllplugin
和大多數的webpack優化教程一樣,筆者也嘗試了利用dllplugin進行優化,該外掛的本質,是提取出我們常用的第三方模組,單獨打成一個檔案包,之後插入到我們的html頁面中,這樣我們以後每次打包,都不需要針對第三方模組進行處理,畢竟第三方模組動輒成千上萬行。
流程介紹:
1、配置webpack.dll.js針對第三方庫打包
2、vue.config.js中配置plugin
3、html中引入dll打包出來的js檔案。(一般採用部署CDN的方式)
由於專案中有很多大型的第三方庫,類似three、echart等,所以筆者進行了以下配置:(webpack.dll.js)
const webpack = require("webpack") const path = require("path") const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { vuebundle: [ 'vue', 'vue-router', 'vuex', ], utils:[ 'lodash', 'swiper', 'lottie-web', 'three', ], echarts:[ 'echarts/lib/echarts', "echarts/lib/chart/bar", "echarts/lib/chart/line", "echarts/lib/component/tooltip", "echarts/lib/component/title", "echarts/lib/component/legend", ] }, output: { path: path.resolve(__dirname, './static/'), filename: '[name].dll.js', library: '[name]_library' }, plugins: [ new webpack.DllPlugin({ path: path.join(__dirname, 'build', '[name]-manifest.json'), name: '[name]_library' }) ] }
針對不同的庫的大小進行劃分,打了三個包,為啥不打成一個包?一個包那就太大了,你並不希望你的使用者載入一個大型JS檔案包而阻塞,影響頁面效能。
接下里是vue.config.js的配置:
plugins: [ new webpack.ProvidePlugin({ $: "zepto", Zepto: "zepto", }), new DllReferencePlugin({ manifest: require('./build/echarts-manifest.json'), }), new DllReferencePlugin({ manifest: require('./build/utils-manifest.json'), }), new DllReferencePlugin({ manifest: require('./build/vuebundle-manifest.json'), }), new BundleAnalyzerPlugin(), ]
引入了DllPlugin。接下來配置HTML:
(由於筆者沒將DLL打包出來的js檔案上傳到CDN,所以只能本地自己起個node伺服器返回靜態資源了)
<body> <div id="app"></div> <!-- built files will be auto injected --> <script type="text/javascript" src="http://localhost:3000/echarts.dll.js"></script> <script type="text/javascript" src="http://localhost:3000/utils.dll.js"></script> <script type="text/javascript" src="http://localhost:3000/vuebundle.dll.js"></script> </body>
然後npm run serve,開始頁面除錯和開發~
舒服~
優化結果:
由於少了大型第三方庫,所以時間控制在了20s左右了。優化相對比較明顯~
3、優化與探索總結
優化到這,基本就結束了。
webpack常見的優化方式,優化路徑查詢、設定快取、happypack以及dllplugin,前兩項vue-cli已經幫我們做了一些,而happypack由於不和vue相容,導致無法接入,dllplugin通過單獨提取第三方庫,取得了明顯優化。
當然,筆者也嘗試剔除了一些專案中無用的程式碼,不過也是不痛不癢。
webpack優化方式總結:
1、優化模組查詢路徑
2、剔除不必要的無用的模組
3、設定快取:快取loader的執行結果(cacheDirectory/cache-loader)
4、設定多執行緒:HappyPack/thread-loader
5、dllplugin提取第三方庫
當然,這是針對開發的優化,如果是針對部署上的優化呢?我們可以設定splitchunk、按需載入、部署CDN等,這裡就不展開了。
最後
希望這篇文章能夠大家有所收穫~ webpack已經是前端仔必備技能了~有空大家鑽研一下webpack的配置和原理,也是會有所收穫的!謝謝觀看~
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援碼農教程。