Webpack之SplitChunks插件用法詳解

前言

SplitChunks插件是什麼呢,簡單的來講就是Webpack中一個提取或分離代碼的插件,主要做用是提取公共代碼,防止代碼被重複打包,拆分過大的js文件,合併零散的js文件。html

提到前端優化,提取公共代碼是必不可少的手段。在Webpack出現前,提取公共代碼是人爲去處理,而SplitChunks插件的做用就是經過配置讓Webpack去幫你提取公共代碼。Webpack創始人的初衷也是但願能有更多時間寫更多代碼,因此這種純體力的勞動交給Webpack去完成。前端

因此SplitChunks插件是前端進階的一項必備技能,下面就詳細介紹SplitChunks插件的用法。vue

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

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

一、output.filename

此選項給打包後的入口js文件命名,下圖中的app.js文件就是打包後的入口js文件。jquery

  • 在單入口的Vue項目中,打包前的入口文件,在vue.config.js中配置
    module.exports = {
        configureWebpack:{
            entry:'./src/main.js',
        }
    }
    複製代碼
    打包後的入口文件,在vue.config.js中配置
    module.exports = {
        configureWebpack:{
           output:{
                filename: 'js/appCS.js'
            }, 
        }
    }
    複製代碼
    爲何前面要加個js/,由於該js文件是output:path選項指定的目錄下生成的,其默認爲根目錄/。打包結果以下所示
  • 在多入口的Vue項目中,打包前的入口文件,在vue.config.js中配置
    module.exports = {
          configureWebpack:{
              entry: {
                  appCS: './src/main.js'
              },
          }
      }
    複製代碼
    打包後的入口文件,在vue.config.js中配置
    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')
        }
    }
    複製代碼
    打包結果以下所示

二、output.chunkFilename

此選項給打包後的非入口js文件命名,那下圖紅框中所示就是非入口js文件 webpack

在vue.config.js中配置

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

打包結果以下所示 web

然而output.chunkFilename並不能改變chunk-0a4e15c9這樣名字字段。npm

三、webpackChunkName

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

function load(component) {
    return () => import(/* webpackChunkName: "[request]" */ `views/${component}`)
}
const routes = [
    {
        {
            path: '/apiApply',
            name: 'apiApply',
            component: load('api_manage/api_apply'),
        }
    }
]
複製代碼

打包結果以下所示 json

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 xxxrequire('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時,忽略minSizeminChunksmaxAsyncRequestsmaxInitialRequests外面配置選項。

配置選項不少,下面在實際項目中使用SplitChunks,讓你更深入理解這些配置選項。

首先了解一下SplitChunks在Vue Cli3中的默認配置。在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.jschunk-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中有段代碼是這麼寫。

<body>
    <div id=app></div>
    <script src=/js/app.a502ce9a.js></script>
</body>
複製代碼

代表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文件,大小過大會致使首屏加載時間過長。要優化一下,由兩種方法

第一種用externals來優化,看個人另外一篇文章Webpack之externals用法詳解

第二種用SplitChunks優化。例如要把elementchunk-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.jsapi/ability_bind_set.jsedit.vuemixins等模塊被重複打包了好幾回。

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

cacheGroups中配置

api: {
    name: 'api',
    test: /[\\/]api[\\/]/,
    priority: 0,
},
複製代碼

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

打包後再看分析圖會發現 api/common.jsapi/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/apisrc/mixinssrc/service中的內容合併打包生成一個js文件,替代以前打包生成的mixins.8d1d6f50.jsapi.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插件,需切記中庸之道。

相關文章
相關標籤/搜索