Webpack中 SplitChunks 插件用法詳解

前言

SplitChunks 插件是什麼呢?
簡單的來講就是 Webpack 中一個提取或分離代碼的插件,主要做用是提取公共代碼,防止代碼被重複打包,拆分過大的js文件,合併零散的js文件。
提到前端優化,提取公共代碼是必不可少的手段。
在 Webpack 出現前,提取公共代碼是人爲去處理,而 SplitChunks 插件的做用就是經過配置讓 Webpack 去幫你提取公共代碼。Webpack創始人的初衷也是但願能有更多時間寫更多代碼,因此這種純體力的勞動交給Webpack去完成。javascript

1. 工欲利其事必先利其器

由於 SplitChunks 插件會提取模塊並打包生成js文件。
先學會給打包生成的js文件命名,否則很差確認打包生成的js文件是否是你想要的。
Webpack中給打包生成的js文件命名有如下幾種方法:html

1.1 output.filename前端

此選項給打包後的入口 js 文件命名,下圖中的 app.js 文件就是打包後的入口js文件。
在這裏插入圖片描述
單入口的 Vue項目中vue

打包前的入口文件,在 vue.config.js 中配置java

module.exports = { configureWebpack:{ entry:'./src/main.js', } }

打包後的入口文件,在 vue.config.js 中配置node

module.exports = { configureWebpack:{ output:{ filename: 'js/appCS.js' }, } }

爲何前面要加個 js/ ,由於該 js 文件是 output:path 選項指定的目錄下生成的,其默認爲根目錄 /。jquery

打包結果以下所示:
在這裏插入圖片描述
多入口的 Vue 項目中webpack

打包前的入口文件,在 vue.config.js 中配置web

module.exports = { configureWebpack:{ entry: { appCS: './src/main.js' }, } }

打包後的入口文件,在 vue.config.js 中配置npm

module.exports = { configureWebpack:{ output:{ filename: 'js/[name].js' }, } }

[name] 是入口文件模塊名稱

打包結果以下所示:
在這裏插入圖片描述
會發現多個 app.js,這是由於 configureWebpack 去採用合併策略來配置 Webpack。
要去掉 app.js 要用 chainWebpack 來配置:

module.exports = { 
	chainWebpack: config =>{ 
		config.entryPoints.delete('app').end().entry('appCS').add('./src/main.js') 
	} 
}

打包結果以下所示:
在這裏插入圖片描述
1.2 output.chunkFilename

此選項給打包後的非入口 js 文件命名,那下圖紅框中所示就是非入口 js 文件
在這裏插入圖片描述
在 vue.config.js 中配置

module.exports = {
    configureWebpack:{
        output:{
            chunkFilename: 'CS.[name].js'
        },
    }
}

打包結果以下所示
在這裏插入圖片描述
然而 output.chunkFilename 並不能改變 chunk-0a4e15c9 這樣名字字段

1.3 webpackChunkName
webpackChunkName:塊的名稱,[request]可解釋爲實際的解析文件名。
可在路由懶加載中使用:

function load(component) {
  return () => import(/* webpackChunkName: "[request]" */ `views/${component}`)
}

const routes = [
  {
    {
      path: '/apiApply',
      name: 'apiApply',
      component: load('api_manage/api_apply'),
    }
  }
]

打包結果以下所示:
在這裏插入圖片描述
src/views/api_manage/api_apply/index.vue 這個組件打包後生成的 js 文件是 api_manage-api_apply.48227bf7.js。

若是不用 [request] 也能夠這麼寫。

component: () =>import(/* webpackChunkName: "api_manage-api_apply"*/ 'src/views/api_manage/api_apply'),

再看一下其它 js 文件,發現還有 chunk-xxx.js 類的文件,以下圖紅框中所示
在這裏插入圖片描述
這些 js 文件的命名要在 SplitChunks 插件中設置了。

2. SplitChunks插件配置選項

  • chunks 選項,決定要提取哪些模塊
    默認是 async :只提取異步加載的模塊出來打包到一個文件中。
    異步加載的模塊:經過 import(‘xxx’) 或 require([‘xxx’],() =>{}) 加載的模塊。

  • initial:提取同步加載和異步加載模塊;
    若是 xxx 在項目中異步加載了,也同步加載了,那麼 xxx 這個模塊會被提取兩次,分別打包到不一樣的文件中。
    同步加載的模塊:經過 import xxx 或 require(‘xxx’) 加載的模塊。

  • all:無論異步加載仍是同步加載的模塊都提取出來,打包到一個文件中;

  • minSize 選項:規定被提取的模塊在壓縮前的大小最小值,單位爲字節;
    默認爲30000,只有超過了30000字節纔會被提取。

  • maxSize 選項:把提取出來的模塊打包生成的文件大小不能超過maxSize值;
    若是超過了,要對其進行分割並打包生成新的文件。
    單位爲字節,默認爲0,表示不限制大小。

  • minChunks 選項:表示要被提取的模塊最小被引用次數,引用次數超過或等於minChunks值,才能被提取。

  • maxAsyncRequests 選項:最大的按需(異步)加載次數,默認爲 6;

  • maxInitialRequests 選項:打包後的入口文件加載時,還能同時加載js文件的數量(包括入口文件),默認爲4。

    優先級 :maxInitialRequests / maxAsyncRequests < maxSize < minSize;

  • automaticNameDelimiter 選項:打包生成的js文件名的分割符,默認爲:~

  • name選項:打包生成 js 文件的名稱;

  • cacheGroups 選項,核心重點,配置提取模塊的方案,裏面每一項表明一個提取模塊的方案。
    下面是 cacheGroups 每項中特有的選項,其他選項和外面一致,若 cacheGroups 每項中有,就按配置的,沒有就使用外面配置的;

    • test 選項:用來匹配要提取的模塊的資源路徑或名稱,值是正則或函數;

    • priority 選項:方案的優先級,值越大表示提取模塊時優先採用此方案,默認值爲0;

    • reuseExistingChunk 選項:true / false。
      爲true時,若是當前要提取的模塊,在已經在打包生成的js文件中存在,則將重用該模塊,而不是把當前要提取的模塊打包生成新的 js 文件。

    • enforce選項:true / false。
      爲true時,忽略minSize,minChunks,maxAsyncRequests和maxInitialRequests外面選項

配置選項不少,下面在實際項目中使用 SplitChunks,讓你更深入理解這些配置選項。
首先了解一下 SplitChunks 在 Vue Cli3 中的默認配置。
在這裏插入圖片描述
整理後默認配置以下所示:

module.exports = {
  configureWebpack:config =>{
    return {
      optimization: {
        splitChunks: {
          chunks: 'async',
          minSize: 30000,
          maxSize: 0,
          minChunks: 1,
          maxAsyncRequests: 6,
          maxInitialRequests: 4,
          automaticNameDelimiter: '~',
          cacheGroups: {
            vendors: {
              name: `chunk-vendors`,
              test: /[\\/]node_modules[\\/]/,
              priority: -10,
              chunks: 'initial'
            },
            common: {
              name: `chunk-common`,
              minChunks: 2,
              priority: -20,
              chunks: 'initial',
              reuseExistingChunk: true
            }
          }
        }
      }
    }
  }
};

先安裝 webpack-bundle-analyzer 插件,能夠可視化分析打包後的文件。

npm install webpack-bundle-analyzer --save-dev

在 vue.config.js 中引入插件

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports={
  configureWebpack:config =>{
    return {
      plugins:[
        new BundleAnalyzerPlugin()
      ]
    }
  }
}

打包後,會在瀏覽器自動打開 http://127.0.0.1:8888/ ,內容以下所示
在這裏插入圖片描述
會發現裏面有個 chunk-vendors.js 文件,是 cacheGroups 中 vendors 這個方案打包出來的 js 文件。

能夠用 name 選項來修改 chunk-vendors.js 文件的名字,代碼以下:

vendors: {
  name: `app-chunk-vendors`,
  test: /[\\/]node_modules[\\/]/,
  priority: -10,
  chunks: 'initial'
},

打包後,會發現 chunk-vendors.js 文件已經變成了 app-chunk-vendors.js 文件裏面內容不變。
在這裏插入圖片描述

3. SplitChunks 實戰操做之入口文件

先去掉 cacheGroups 裏面的方案,再打包一下。

cacheGroups: {
 vendors: false,
 common: false
}

在這裏插入圖片描述
app.a502ce9a.js 和 chunk-be34ce9a.ceff3b64.js 這兩個 js 文件是由項目中 main.js 這個入口文件打包生成的。
例如 app.js 文件中有 element-ui、moment、jquery、vue、router、store、jsencrypt 等內容,這些都是在 main.js 中引入。

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import JsEncrypt from 'jsencrypt';
import $ from 'jquery';
import ElementUI from 'element-ui';
Vue.use(ElementUI);
import treeSelect from 'fxft-tree-select';
Vue.use(treeSelect);
import moment from 'moment';
Vue.prototype.moment = moment;
//註冊全局變量、全局函數
import base from 'service/base';
Vue.use(base);
//註冊打印插件
import print from  'service/print';
Vue.use(print);
const vm = new Vue({
    router,
    store,
    render: h => h(App)
}).$mount('#app')
window.vm = vm;

打包生成的 index.html 中有段代碼是這麼寫。

<div>
    <div id=app></div>
    <script src=/js/app.a502ce9a.js></script>
</div>

代表 app.js 是項目一開始就要加載的,會影響首屏的加載時間。
那麼能夠在 main.js 中去掉一些在首屏中暫時用不到的引入,好比這些均可以暫時去掉。

import JsEncrypt from 'jsencrypt';
import treeSelect from 'fxft-tree-select';
Vue.use(treeSelect);
//註冊打印插件
import print from  'service/print';
Vue.use(print);

打包後再看分析圖,會發現 jsencrypt 等內容都在 app.js中 都消失了。
在這裏插入圖片描述
爲何說 chunk-be34ce9a.js 也是從 main.js 打包生成的?由於在main.js中有段代碼:

//註冊全局變量、全局函數
import base from 'service/base';
Vue.use(base);

在看 service/base.js 文件中有

import('./Export2Excel').then(res => {
  res.export_json_to_excel(defaultOpition);
})

Export2Excel.js 是異步加載,在 service/Export2Excel.js 中

import { saveAs } from 'file-saver'
import XLSX from 'xlsx'

至關,file-saver、xlsx 也是異步加載,因此 file-saver、xlsx 會被提取出來打包生成 chunk-be34ce9a.js 文件。

在默認配置下,main.js 中異步加載或間接異步加載的模塊,都會被另外打包生成一個 js 文件

若是要把從 node_modules 中加載的模塊所有打包到一個js文件中,要怎麼作呢?Vue Cli3中的已經幫咱們作了。

cacheGroups: {
  vendors: {
    name: `chunk-vendors`,
    test: /[\\/]node_modules[\\/]/,
    priority: -10,
    chunks: 'initial'
  },
}

其核心是 test 選項,匹配項目從 node_modules 中加載的模塊並提取打包生成 chunk-vendors.js 文件。
打包後再從分析圖中搜索node_modules,發現仍是由不少文件中含有從node_modules中加載的模塊,和預期的不同。
在這裏插入圖片描述
這是 chunks 選項在做怪,值 initial 表示:若是 xxx 在項目中異步加載或同步加載多少次,那麼xxx這個模塊也會被提取多少次,分別打包到不一樣的文件中。core-js庫在項目中每一個文件都會加載到,故它會提取屢次。

只要把 chunks 選項的值改爲 all(無論異步加載仍是同步加載的模塊都是提取打包到一個文件中),就能夠把從 node_modules中加載的模塊所有打包到一個 js 文件中。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
發現 chunk-vendors.js 的大小有點大了,有1.91MB,仍是項目初始化時須要加載的 js 文件,大小過大會致使首屏加載時間過長。
解決:用 SplitChunks優化,例如把 element 從 chunk-vendors.js 提取出來,要在 cacheGroups 中配置:

element: {
  chunks: 'all',
  name: `element-ui`,
  test: /[\\/]element-ui[\\/]/,
  priority: 0,
},

注意 priority選項,要把 element 單獨提取出來,priority 的值必須比 vendors 方案中的 priority 的值大,否則提取不出來。

打包後可看到 element 被打包生成新的 element-ui.js 文件,chunk-vendors.js 大小變成1.27MB,比原來的 1.91MB有減少。
此外能夠本身提取 xlsx、moment、jquery 等第三方依賴。
在這裏插入圖片描述

4. SplitChunks 實戰操做之非入口文件

在分析圖中,除了入口文件,還有不少 js 文件,這些文件中有一大部分是項目中組件打包生成的。

若是在實現路由懶加載時,用到 /webpackChunkName:"[request]"/,那麼在由組件打包生成的 js 文件名上,能夠得知這個 js 文 件是哪一個組件打包生成的。
在這裏插入圖片描述
圖中的
base_info_manage-group_info_set-ability_bind_set.85b419a1.js 是項目中views/base_info_manage/group_info_set/bility_bind_set/index.vue 這個組件打包生成的。

base_info_manage-group_info_set-ability_bind_set-edit.08f91768.js 是項目中views/base_info_manage/group_info_set/bility_bind_set/edit.vue 這個組件打包生成的。

另外會發現:chunk-5c1416e3.1cbcb0ec.js 的內容怎麼跟 base_info_manage-group_info_set-ability_bind_set-edit.08f91768.js 的內容類似,只少了 src/api 的內容。
並且 api/common.js、api/ability_bind_set.js、edit.vue、mixins 等模塊被重複打包了好幾回。

能夠用SplitChunks提取一下這些模塊。避免重複打包,減小打包生成的文件整體大小。

在 cacheGroups 中配置:

api: {
 name: 'api',
 test: /[\\/]api[\\/]/,
 priority: 0,
},

當提取多個模塊打包生成文件時,name 選項爲必填

在這裏插入圖片描述
打包後再看分析圖會發現 api/common.js、api/ability_bind_set.js 已經被提取到 api.05ad5193.js 中了。
在這裏插入圖片描述
接着提取 mixins 模塊,在 cacheGroups 中配置

mixins: {
  name: 'mixins',
  test: /[\\/]mixins[\\/]/,
  priority: 0,
},

在這裏插入圖片描述
打包後再看分析圖發現 mixins 模塊已經被提取到 mixins.8d1d6f50.js 中
在這裏插入圖片描述
接着提取 edit.vue 模塊,在 cacheGroups 中配置

base_info_manage: {
  name: 'base_info_manage',
  test: /[\\/]base_info_manage[\\/]/,
  minChunks: 2,
  priority: 0,
},

在這裏插入圖片描述
其中 minChunks 選項,必須爲2,由於從上面分析圖來看,edit.vue 被引用了2次,而 index.vue 只被引用了1次。
若是爲1,則 index.vue 也會被提取出來;
若是爲2以上的值,則 edit.vue 不會被提取出來。
在這裏插入圖片描述
在這裏插入圖片描述
打包後再看分析圖發現 edit.vue 模塊已經被提取到 base_info_manage.d5c14c01.js 中。
若是以爲 base_info_manage.d5c14c01.js 文件太大,有兩種方法能夠處理。
在這裏插入圖片描述
第一種是利用 maxSize 選項,提取模塊後打包生成的文件大小不能超過 maxSize 值,若是超過了,要再提取並打包生成新的文件。

base_info_manage: {
    name: 'base_info_manage',
    test: /[\\/]base_info_manage[\\/]/,
    minChunks: 2,
    priority: 0,
    maxSize: 102400
},

打包後再看分析圖會發現 base_info_manage.js 已經被分割成五個小 js 文件了
在這裏插入圖片描述
第二種是按 base_info_manage 文件夾下的子文件夾來繼續提取,例如 base_info_manage 文件夾下有個子文件叫group_info_set。
在 cacheGroups 中配置

group_info_set: {
  name: 'group_info_set',
  test: /[\\/]base_info_manage[\\/]group_info_set[\\/]/,
  minChunks: 2,
  priority: 10,
},

打包後再看分析圖會發現 base_info_manage.js 中的 group_info_set 模塊集合已經被提取到 group_info_set.js 文件中。base_info_manage.d5c14c01.js 文件也有相應的減少。
在這裏插入圖片描述
在這裏插入圖片描述
另外能夠把 src/api、src/mixins、src/service 中的內容合併打包生成一個 js 文件,替代以前打包生成的 mixins.8d1d6f50.js 和api.05ad5193.js

common: {
  test: /[\\/]api[\\/]|[\\/]mixins[\\/]|[\\/]src[\\/]service[\\/]/,
  priority: 0,
  name: 'common',
},

在這裏插入圖片描述

其餘的就很少說,能夠按照上面所講在本身的項目用 SplitChunks 插件來控制 Webpack 打包生成本身想要的 js 文件,直到 dist/js 文件夾中每一個 js 都是你所預想的生成的 js 文件爲止。

5. 總結

用 SplitChunks 插件來控制 Webpack 打包生成的 js 文件的內容的精髓在於:防止模塊被重複打包,拆分過大的js文件,合併零散的js文件。最終的目的就是減小請求資源的大小和請求次數。因這二者是互相矛盾的,故要以項目實際的狀況去使用SplitChunks插件,需切記中庸之道。

相關文章
相關標籤/搜索