深入理解基于vue-cli的webpack打包優(yōu)化實(shí)踐及探索
轉(zhuǎn)眼已經(jīng)是2019年,短短三四年時(shí)間,webpack打包工具成為了前端開發(fā)中必備工具,曾經(jīng)一度的面試題都是問(wèn),請(qǐng)問(wèn)前端頁(yè)面優(yōu)化的方式有哪些?大家也是能夠信手拈來(lái)的說(shuō)出緩存、壓縮文件、CSS雪碧圖以及部署CDN等等各種方法,但是今天不一樣了,可能你去面試問(wèn)的就是,請(qǐng)問(wèn)你是否知道webpack的打包原理,webpack的打包優(yōu)化方法有哪些?所以該說(shuō)不說(shuō)的,筆者閑著沒(méi)事研究了一下webpack的打包優(yōu)化,可能大家都有看過(guò)類似的優(yōu)化文章~ 但是筆者還是希望能夠給大家一些新的啟發(fā)~
1、準(zhǔn)備工作:測(cè)速與分析bundle
既然我們要優(yōu)化webpack打包,肯定要提前對(duì)我們的bundle文件進(jìn)行分析,分析各模塊的大小,以及分析打包時(shí)間的耗時(shí)主要是在哪里,這里主要需要用到兩個(gè)webpack插件,speed-measure-webpack-plugin和webpack-bundle-analyzer,前者用于測(cè)速,后者用于分析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腳手架的,所以其實(shí)vue-cli中已經(jīng)幫你做了一些優(yōu)化的工作,可以看到,原先項(xiàng)目最初的配置設(shè)置了splitchunk,進(jìn)行代碼分割,這在大型項(xiàng)目中是很有必要的,畢竟你不希望你的用戶阻塞加載一個(gè)5MB大小的JS文件,所以做代碼分割和懶加載是很有必要的。
說(shuō)遠(yuǎn)了,我們來(lái)看看這個(gè)配置,你需要用smp對(duì)配置進(jìn)行再包裹,因?yàn)镾peedMeasurePlugin會(huì)對(duì)你的其他Plugin對(duì)象包裹一層代理,這樣的目的是為了能夠知道plugin開始和結(jié)束的時(shí)間~
其次,BundleAnalyzerPlugin就跟普通的plugin一樣,加載plugins數(shù)組的后面即可。
接下來(lái)我們看一下最初的打包時(shí)間以及包內(nèi)容分析:
可以看到項(xiàng)目中較大的三個(gè)包,其中兩個(gè)包是我們的第三方依賴,three.js、lottie、lodash、echarts等。
2、開始逐步優(yōu)化
2.1縮小文件查找和處理范圍
這是webpack優(yōu)化中的常規(guī)操作,基本就是對(duì)模塊和文件查找的優(yōu)化,以及減少loader對(duì)一些不必要模塊的處理,但是vue-cli中的loader并沒(méi)有暴露給我們操作,所以其內(nèi)置的loader處理無(wú)法由我們進(jìn)行優(yōu)化,但是其實(shí)vue-cli中的配置項(xiàng)已經(jīng)對(duì)loader的查找路徑進(jìn)行了優(yōu)化,如果你的項(xiàng)目也是使用了vue-cli,你可以通過(guò)以下命令行查看你現(xiàn)有的配置文件是怎樣的:
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)$/ },
- 通過(guò)modules指定查找第三方模塊的路徑。
- 通過(guò)alias指定第三方模塊直接查找到打包構(gòu)建好的壓縮js文件。
- 通過(guò)module指定noparse,對(duì)第三方模塊不再進(jìn)行分析依賴。
優(yōu)化效果:2s?
可以看到時(shí)間就減少了兩三秒,在30s波動(dòng),感覺(jué)沒(méi)有多大差別。
2.2嘗試使用happypack
由于在進(jìn)行webpack優(yōu)化前,翻閱了很多有關(guān)webapck優(yōu)化的文章,所以筆者也想嘗試一下用happypack來(lái)優(yōu)化打包時(shí)間。
在想要用happypack進(jìn)行的打包之前,大抵有這兩種說(shuō)法:
1、webpack4中已經(jīng)默認(rèn)是多線程打包了,所以happypack打包效果不明顯;
2、vue不支持happypack打包,需要設(shè)置thread-loader。
但是筆者想了一下,還是試試看把,大不了我只對(duì)JS和CSS文件設(shè)置happypack。
但是問(wèn)題又來(lái)了,vue-cli內(nèi)置封裝了loader,這個(gè)時(shí)候我要怎么拿到它的配置,改寫里面的loader配置呢。
通過(guò)翻閱vue-cli的官方文檔我們可以看到以下使用介紹:
configureWebpack
Type: Object | Function
如果這個(gè)值是一個(gè)對(duì)象,則會(huì)通過(guò) webpack-merge 合并到最終的配置中。
如果這個(gè)值是一個(gè)函數(shù),則會(huì)接收被解析的配置作為參數(shù)。該函數(shù)及可以修改配置并不返回任何東西,也可以返回一個(gè)被克隆或合并過(guò)的配置版本。
為此,筆者特地調(diào)試進(jìn)了vue-cli的源碼一探究竟:
流程介紹:
由于我們執(zhí)行命令行vue-cli-service build,其實(shí)是先去node_modules的.bin文件夾下查找相應(yīng)的可執(zhí)行文件,.bin下的vue-cli-service會(huì)映射到相應(yīng)的第三方庫(kù)內(nèi)的執(zhí)行文件。
所以我們可以找到這個(gè)可執(zhí)行文件的地址:
/node_modules/@vue/cli-service/bin/vue-cli-service.js
找到了入口,接下來(lái)我們想要進(jìn)入nodejs的調(diào)試,在以往的開發(fā)中,我們會(huì)通過(guò)node --inspect app.js的方式啟動(dòng)一個(gè)后臺(tái)服務(wù),然后在谷歌瀏覽器里進(jìn)入調(diào)試界面(F12選擇綠色的那個(gè)小按鈕)
但是這里卻犯了難,由于我們的打包構(gòu)建是一次執(zhí)行的,不同于一個(gè)后臺(tái)服務(wù),是實(shí)時(shí)監(jiān)聽(tīng)的,服務(wù)一直啟動(dòng)著。查閱了一下,如果是普通的nodejs文件想要調(diào)試的話,需要通過(guò)這樣的方式:
node --inspect-brk=9229 app.js
所以,為了強(qiáng)行走進(jìn)去vue-cli的源碼進(jìn)行調(diào)試,可看vue-cli的處理流程,我們需要這樣輸入以下命令行:
node --inspect-brk=9229 node_modules/@vue/cli-service/bin/vue-cli-service.js build
上面的這個(gè)命令行,等價(jià)于vue-cli-service build。
通過(guò)這樣的方式,我們終于走進(jìn)了vue-cli的源碼,看了它的執(zhí)行流程,你可以在對(duì)應(yīng)的位置打下斷點(diǎn),查看此時(shí)的作用域內(nèi)的變量數(shù)據(jù)。
可以看到vue-cli源碼里的這一段操作,會(huì)執(zhí)行我們傳入的函數(shù),判斷函數(shù)有沒(méi)有返回值來(lái)決定是否要merge進(jìn)其內(nèi)部配置的config。
通過(guò)這段代碼我們可以看出,如果我們configWepack配置為函數(shù),之后通過(guò)參數(shù)的形式獲取到config配置項(xiàng),本身是一個(gè)對(duì)象,對(duì)象是保留引用的形式,所以如果我們直接對(duì)傳入的config對(duì)象進(jìn)行修改,就可以實(shí)現(xiàn)我們最初的目標(biāo)!修改vue-cli內(nèi)置的loader!
當(dāng)然,除了斷點(diǎn)進(jìn)入里面看配置,剛才也說(shuō)了,我們可以通過(guò)命令行輸出為一個(gè)output文件查看現(xiàn)有的配置。
這里可以給大家截圖看一下vue-cli內(nèi)部的配置:
可能有點(diǎn)廢話了,但是通過(guò)斷點(diǎn)的方式,我們可以看到vue-cli其實(shí)已經(jīng)對(duì)js文件設(shè)置了exclude,同時(shí)也幫我們?cè)O(shè)置好了cache-loader,意味著webpack常規(guī)的優(yōu)化方式之一,使用cache-loader緩存它也幫我們做了。
回到最初的起點(diǎn),我們想要處理的是針對(duì)JS和CSS的loader,于是模仿大多數(shù)的配置,我進(jìn)行了以下修改:
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 }
嘗試對(duì)css的loader配置進(jìn)行修改。之后對(duì)plugins進(jìn)行一下配置:
plugins: [ new HappyPack({ id: 'css', threads: 4, loaders: originCssRuleLoader }), ],
本以為這樣就OK了,但是很遺憾的告訴大家,報(bào)錯(cuò)了...
可以看到報(bào)錯(cuò)的內(nèi)容,是在處理vue文件的時(shí)候,出了錯(cuò)誤。
如何解決
筆者百度了,也谷歌了,大抵是說(shuō)happypack不支持vue-loader,同時(shí),根據(jù)報(bào)錯(cuò)也查了一下處理的方案,通過(guò)設(shè)置parallel參數(shù),也還是無(wú)效。
筆者甚至懷疑是自己的happypack配置不對(duì),于是我把配置原樣移植配置到另一個(gè)非vue項(xiàng)目中,一切運(yùn)行正常。
答案:此題無(wú)解~
原因分析:
由于vue文件中會(huì)含有CSS,所以vue-loader會(huì)提取出其中的css,交給其他loader處理,vue-loader-plugin會(huì)通過(guò)在vue文件后面加上查詢字符串來(lái)告訴其他loader,針對(duì)這個(gè)文件要做處理。意味著什么呢?我們的vue-loader在處理文件的時(shí)候,通知其他loader處理,但是此時(shí)的loader配置已經(jīng)被我們改寫成了happypack,而vue又與happypack不兼容,最終導(dǎo)致了報(bào)錯(cuò)。很遺憾的告訴大家,vue-cli接入happypack--失敗。
(注:這一部分主要是筆者在webpack優(yōu)化過(guò)程中的探索,雖然最終不能讓自己的webpack打包很好的優(yōu)化,但是在這個(gè)探索的過(guò)程中,我們也可以學(xué)到很多~包括 vue-cli對(duì)配置對(duì)象的處理?如何調(diào)試普通文件nodejs代碼?vue-loader中對(duì)vue文件的處理流程?vue-loader-plugin幫我們做了什么事?而這些都是要自己慢慢翻閱,慢慢踩坑去了解的~)
2.3使用dllplugin
和大多數(shù)的webpack優(yōu)化教程一樣,筆者也嘗試了利用dllplugin進(jìn)行優(yōu)化,該插件的本質(zhì),是提取出我們常用的第三方模塊,單獨(dú)打成一個(gè)文件包,之后插入到我們的html頁(yè)面中,這樣我們以后每次打包,都不需要針對(duì)第三方模塊進(jìn)行處理,畢竟第三方模塊動(dòng)輒成千上萬(wàn)行。
流程介紹:
1、配置webpack.dll.js針對(duì)第三方庫(kù)打包
2、vue.config.js中配置plugin
3、html中引入dll打包出來(lái)的js文件。(一般采用部署CDN的方式)
由于項(xiàng)目中有很多大型的第三方庫(kù),類似three、echart等,所以筆者進(jìn)行了以下配置:(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' }) ] }
針對(duì)不同的庫(kù)的大小進(jìn)行劃分,打了三個(gè)包,為啥不打成一個(gè)包?一個(gè)包那就太大了,你并不希望你的用戶加載一個(gè)大型JS文件包而阻塞,影響頁(yè)面性能。
接下里是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。接下來(lái)配置HTML:
(由于筆者沒(méi)將DLL打包出來(lái)的js文件上傳到CDN,所以只能本地自己起個(gè)node服務(wù)器返回靜態(tài)資源了)
<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,開始頁(yè)面調(diào)試和開發(fā)~
舒服~
優(yōu)化結(jié)果:
由于少了大型第三方庫(kù),所以時(shí)間控制在了20s左右了。優(yōu)化相對(duì)比較明顯~
3、優(yōu)化與探索總結(jié)
優(yōu)化到這,基本就結(jié)束了。
webpack常見(jiàn)的優(yōu)化方式,優(yōu)化路徑查找、設(shè)置緩存、happypack以及dllplugin,前兩項(xiàng)vue-cli已經(jīng)幫我們做了一些,而happypack由于不和vue兼容,導(dǎo)致無(wú)法接入,dllplugin通過(guò)單獨(dú)提取第三方庫(kù),取得了明顯優(yōu)化。
當(dāng)然,筆者也嘗試剔除了一些項(xiàng)目中無(wú)用的代碼,不過(guò)也是不痛不癢。
webpack優(yōu)化方式總結(jié):
1、優(yōu)化模塊查找路徑
2、剔除不必要的無(wú)用的模塊
3、設(shè)置緩存:緩存loader的執(zhí)行結(jié)果(cacheDirectory/cache-loader)
4、設(shè)置多線程:HappyPack/thread-loader
5、dllplugin提取第三方庫(kù)
當(dāng)然,這是針對(duì)開發(fā)的優(yōu)化,如果是針對(duì)部署上的優(yōu)化呢?我們可以設(shè)置splitchunk、按需加載、部署CDN等,這里就不展開了。
最后
希望這篇文章能夠大家有所收獲~ webpack已經(jīng)是前端仔必備技能了~有空大家鉆研一下webpack的配置和原理,也是會(huì)有所收獲的!謝謝觀看~
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JAVASCRIPT下判斷IE與FF的比較簡(jiǎn)單的方式
在JAVASCRIPT當(dāng)中可以通過(guò)取當(dāng)前瀏覽器返回值來(lái)判斷當(dāng)前使用什么瀏覽器。2008-10-10functional繼承模式 摘自javascript:the good parts
javascript:the good parts 書中Inheritance部分講到了一種functional的繼承方式, 具體這個(gè)functional該如何翻譯,就不是很清楚了, 就直接意會(huì)一下吧2011-06-0620個(gè)常見(jiàn)的JavaScript數(shù)組操作總結(jié)
JavaScript中的Array對(duì)象與其他編程語(yǔ)言中的數(shù)組一樣,是一組數(shù)據(jù)的集合。在JavaScript中,數(shù)組里面的數(shù)據(jù)可以是不同類型的,并具有用于執(zhí)行數(shù)組常見(jiàn)操作的方法,本文整理了一些常用的,需要的可以參考一下2022-09-09JS特權(quán)方法定義作用以及與公有方法的區(qū)別
在構(gòu)造函數(shù)內(nèi)部通過(guò)this關(guān)鍵字定義的的方法為特權(quán)方法它的作用為在構(gòu)造函數(shù)外面公開訪問(wèn)(僅限于實(shí)例化的對(duì)象),而且還能夠訪問(wèn)私有成員和方法,感興趣的你可以參考下哈2013-03-03webpack HappyPack實(shí)戰(zhàn)詳解
這篇文章主要介紹了webpack HappyPack實(shí)戰(zhàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10layer.confirm點(diǎn)擊第一個(gè)按鈕關(guān)閉彈出框的方法
今天小編就為大家分享一篇layer.confirm點(diǎn)擊第一個(gè)按鈕關(guān)閉彈出框的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09fckeditor粘貼Word時(shí)彈出窗口取消的方法
這篇文章主要介紹了fckeditor粘貼Word時(shí)彈出窗口取消的方法,是應(yīng)用fckeditor時(shí)非常實(shí)用的技巧,需要的朋友可以參考下2014-10-10