基於vue-cli的webpack打包優化實踐及探索

轉眼已是2019年,短短三四年時間,webpack打包工具成爲了前端開發中必備工具,曾經一度的面試題都是問,請問前端頁面優化的方式有哪些?你們也是可以信手拈來的說出緩存、壓縮文件、CSS雪碧圖以及部署CDN等等各類方法,可是今天不同了,可能你去面試問的就是,請問你是否知道webpack的打包原理,webpack的打包優化方法有哪些?因此該說不說的,筆者閒着沒事研究了一下webpack的打包優化,可能你們都有看過相似的優化文章~ 可是筆者仍是但願可以給你們一些新的啓發~

一、準備工做:測速與分析bundle

既然咱們要優化webpack打包,確定要提早對咱們的bundle文件進行分析,分析各模塊的大小,以及分析打包時間的耗時主要是在哪裏,這裏主要須要用到兩個webpack插件,speed-measure-webpack-plugin和webpack-bundle-analyzer,前者用於測速,後者用於分析bundle文件。javascript

具體配置

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數組的後面便可。
接下來咱們看一下最初的打包時間以及包內容分析:css

image.png

image.png

能夠看到項目中較大的三個包,其中兩個包是咱們的第三方依賴,three.js、lottie、lodash、echarts等。html

二、開始逐步優化

2.1縮小文件查找和處理範圍

這是webpack優化中的常規操做,基本就是對模塊和文件查找的優化,以及減小loader對一些沒必要要模塊的處理,可是vue-cli中的loader並無暴露給咱們操做,因此其內置的loader處理沒法由咱們進行優化,可是其實vue-cli中的配置項已經對loader的查找路徑進行了優化,若是你的項目也是使用了vue-cli,你能夠經過如下命令行查看你現有的配置文件是怎樣的:前端

npx vue-cli-service inspect > output.js

具體能夠翻閱vuecli官方文檔。vue

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?
image.pngjava

能夠看到時間就減小了兩三秒,在30s波動,感受沒有多大差異。node

2.2嘗試使用happypack

因爲在進行webpack優化前,翻閱了不少有關webapck優化的文章,因此筆者也想嘗試一下用happypack來優化打包時間。
在想要用happypack進行的打包以前,大抵有這兩種說法:
一、webpack4中已經默認是多線程打包了,因此happypack打包效果不明顯;
二、vue不支持happypack打包,須要設置thread-loader。
可是筆者想了一下,仍是試試看把,大不了我只對JS和CSS文件設置happypack。
可是問題又來了,vue-cli內置封裝了loader,這個時候我要怎麼拿到它的配置,改寫裏面的loader配置呢。
經過翻閱vue-cli的官方文檔咱們能夠看到如下使用介紹:webpack

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文件想要調試的話,須要經過這樣的方式:web

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的源碼,看了它的執行流程,你能夠在對應的位置打下斷點,查看此時的做用域內的變量數據。
image.png
能夠看到vue-cli源碼裏的這一段操做,會執行咱們傳入的函數,判斷函數有沒有返回值來決定是否要merge進其內部配置的config。
經過這段代碼咱們能夠看出,若是咱們configWepack配置爲函數,以後經過參數的形式獲取到config配置項,自己是一個對象,對象是保留引用的形式,因此若是咱們直接對傳入的config對象進行修改,就能夠實現咱們最初的目標!修改vue-cli內置的loader!
固然,除了斷點進入裏面看配置,剛纔也說了,咱們能夠經過命令行輸出爲一個output文件查看現有的配置。
這裏能夠給你們截圖看一下vue-cli內部的配置:
image.png
可能有點廢話了,可是經過斷點的方式,咱們能夠看到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了,可是很遺憾的告訴你們,報錯了...
image.png
能夠看到報錯的內容,是在處理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頁面中,這樣咱們之後每次打包,都不須要針對第三方模塊進行處理,畢竟第三方模塊動輒成千上萬行。
流程介紹:

  • 一、配置webpack.dll.js針對第三方庫打包
  • 二、vue.config.js中配置plugin
  • 三、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,開始頁面調試和開發~
舒服~
優化結果:
image.png
因爲少了大型第三方庫,因此時間控制在了20s左右了。優化相對比較明顯~

三、優化與探索總結

優化到這,基本就結束了。
webpack常見的優化方式,優化路徑查找、設置緩存、happypack以及dllplugin,前兩項vue-cli已經幫咱們作了一些,而happypack因爲不和vue兼容,致使沒法接入,dllplugin經過單獨提取第三方庫,取得了明顯優化。
固然,筆者也嘗試剔除了一些項目中無用的代碼,不過也是不痛不癢。
webpack優化方式總結:

  • 一、優化模塊查找路徑
  • 二、剔除沒必要要的無用的模塊
  • 三、設置緩存:緩存loader的執行結果(cacheDirectory/cache-loader)
  • 四、設置多線程:HappyPack/thread-loader
  • 五、dllplugin提取第三方庫

固然,這是針對開發的優化,若是是針對部署上的優化呢?咱們能夠設置splitchunk、按需加載、部署CDN等,這裏就不展開了。

最後

但願這篇文章可以你們有所收穫~ webpack已是前端仔必備技能了~有空你們鑽研一下webpack的配置和原理,也是會有所收穫的!謝謝觀看~

相關文章
相關標籤/搜索