淺談webpack SplitChunksPlugin實(shí)用指南
提到前端打包工具,毫無(wú)疑問(wèn)想先到的是webpack。但是前端發(fā)展地很快,時(shí)不時(shí)會(huì)有新東西出現(xiàn),打包工具這邊之前也出現(xiàn)parcel和rollup。各種工具的碰撞,相互汲取優(yōu)點(diǎn),促進(jìn)技術(shù)的發(fā)展。
webpack4中支持了零配置的特性,同時(shí)對(duì)塊打包也做了優(yōu)化, CommonsChunkPlugin 已經(jīng)被移除了,現(xiàn)在是使用 optimization.splitChunks 代替。
下面就開(kāi)始介紹splitChunks的內(nèi)容。
默認(rèn)情況
首先webpack會(huì)根據(jù)下述條件自動(dòng)進(jìn)行代碼塊分割:
- 新代碼塊可以被共享引用,或者這些模塊都是來(lái)自node_modules文件夾里面
- 新代碼塊大于30kb(min+gziped之前的體積)
- 按需加載的代碼塊,并行請(qǐng)求最大數(shù)量應(yīng)該小于或者等于5
- 初始加載的代碼塊,并行請(qǐng)求最大數(shù)量應(yīng)該小于或等于3
塊打包默認(rèn)情況下只會(huì)影響按需加載模塊,因?yàn)閷?duì)初始?jí)K也進(jìn)行優(yōu)化打包會(huì)影響HTML中的script標(biāo)簽數(shù),增加請(qǐng)求數(shù)。
接下來(lái)看些例子來(lái)理解默認(rèn)情況的打包。
模塊全部是同步引入
// indexA.js
import React from 'react'
import ReactDOM from 'react-dom'
import _ from 'lodash'
console.log(_.join(['a', 'b'], '~'))
ReactDOM.render(
<div>SplitChunk</div>,
document.getElementById('root')
)

默認(rèn)情況只會(huì)影響按需加載模塊,所以所有內(nèi)容全部被打包到一起了。
有模塊動(dòng)態(tài)導(dǎo)入
這里首先使用符合ECMAScript 提案 的 import() 語(yǔ)法
// indexA.js
import React from 'react'
import ReactDOM from 'react-dom'
import _ from 'lodash'
import(/* webpackChunkName: "async-jquery" */ 'jquery').then(component => {
console.log(component)
})
console.log(_.join(['a', 'b'], '~'))
ReactDOM.render(
<div>SplitChunk</div>,
document.getElementById('root')
)

這里jquery使用動(dòng)態(tài)導(dǎo)入,打包結(jié)果中可以看到j(luò)query被單獨(dú)打包了
react按需加載
同樣的我們?cè)囈猺eact按需加載,使用react-router提供的按需加載方案
AsyncModule模塊按上面方案異步加載Dashboard
import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter, Route} from 'react-router-dom'
import AsyncModule from './AsyncModule.jsx'
import _ from 'lodash'
import $ from 'jquery'
console.log(_.join(['a', 'b'], '~'))
ReactDOM.render(
<div>
<BrowserRouter>
<Route path='/' component={AsyncModule} />
</BrowserRouter>
</div>,
document.getElementById('root')
)

從打包結(jié)果可以看到按需加載的模塊被打包到0.js去了。
講完了webpack默認(rèn)情況下對(duì)打包塊的優(yōu)化,接下來(lái)看splitChunks配置項(xiàng)。
配置項(xiàng)
相關(guān)配置項(xiàng):
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {}
}
}
}
- chunks: 表示哪些代碼需要優(yōu)化,有三個(gè)可選值:initial(初始?jí)K)、async(按需加載塊)、all(全部塊),默認(rèn)為async
- minSize: 表示在壓縮前的最小模塊大小,默認(rèn)為30000
- minChunks: 表示被引用次數(shù),默認(rèn)為1
- maxAsyncRequests: 按需加載時(shí)候最大的并行請(qǐng)求數(shù),默認(rèn)為5
- maxInitialRequests: 一個(gè)入口最大的并行請(qǐng)求數(shù),默認(rèn)為3
- automaticNameDelimiter: 命名連接符
- name: 拆分出來(lái)塊的名字,默認(rèn)由塊名和hash值自動(dòng)生成
- cacheGroups: 緩存組。緩存組的屬性除上面所有屬性外,還有test, priority, reuseExistingChunk
- test: 用于控制哪些模塊被這個(gè)緩存組匹配到
- priority: 緩存組打包的先后優(yōu)先級(jí)
- reuseExistingChunk: 如果當(dāng)前代碼塊包含的模塊已經(jīng)有了,就不在產(chǎn)生一個(gè)新的代碼塊
配置項(xiàng)基本就上面這些,我們重點(diǎn)來(lái)看下chunks和cacheGroups。
chunks
chunks的取值是有initial, async, all。默認(rèn)情況下是async,在本文第一部分已經(jīng)介紹了它的表現(xiàn),所以現(xiàn)在來(lái)看下其它兩個(gè)的表現(xiàn)。
initial , all 模式會(huì)將所有來(lái)自node_modules的模塊分配到一個(gè)叫vendors的緩存組;所有重復(fù)引用至少兩次的代碼,會(huì)被分配到default的緩存組。
// indexA.js import './Dashboard.jsx' // indexB.js import './Dashboard.jsx' // Dashboard.jsx import React from 'react'
// webpack.config.js
splitChunks: {
chunks: 'initial'
}

打包表現(xiàn)正如上面所述,產(chǎn)生了兩個(gè)代碼塊vendors, default。
可以通過(guò)配置optimization.splitChunks.cacheGroups.default: false禁用default緩存組。
// webpack.config.js
splitChunks: {
chunks: 'initial',
cacheGroups: {
default: false
}
}

至于all和initial的差別,可以看下這篇文章Webpack 4 — Mysterious SplitChunks Plugin (要科學(xué)上網(wǎng))
里面有提到 initial 模式下會(huì)分開(kāi)優(yōu)化打包異步和非異步模塊。而 all 會(huì)把異步和非異步同時(shí)進(jìn)行優(yōu)化打包。也就是說(shuō)moduleA在indexA中異步引入,indexB中同步引入, initial 下moduleA會(huì)出現(xiàn)在兩個(gè)打包塊中,而 all 只會(huì)出現(xiàn)一個(gè)。
cacheGroups
使用cacheGroups可以自定義配置打包塊。
// indexA.js
import React from 'react'
import ReactDOM from 'react-dom'
import _ from 'lodash'
import $ from 'jquery'
// indexB.js
import React from 'react'
import ReactDOM from 'react-dom'
import('lodash')
import $ from 'jquery'
// webpack.config.js
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: 'commons',
chunks: 'initial',
minChunks: 2
}
}
}
}

根據(jù)開(kāi)頭介紹webapck分割條件,一些公共模塊被打包進(jìn)了commons,自定義打包塊的優(yōu)先級(jí)是0,所以現(xiàn)在公共模塊會(huì)被打包進(jìn)commons,而不是上述提到的默認(rèn)打包塊vendors(優(yōu)先級(jí)為負(fù))。
但是這邊為什么lodash為什么沒(méi)打包在一起呢?可以回顧下initial和all的區(qū)別。接下來(lái)實(shí)驗(yàn)下all的效果。
// indexA, indexB同上
// webpack.config.js
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: 'commons',
chunks: 'all',
minChunks: 2
}
}
}
}

結(jié)果在預(yù)期中,lodash被打包在一起了。
提取第三方庫(kù)
最后看下之前CommonsChunkPlugin常用的分離部分第三方庫(kù)功能。這邊你可以想一下怎么操作。
上面已經(jīng)提到了設(shè)置 chunks: initial || all 都可以提取出第三方庫(kù)。但是它是把所有第三庫(kù)提取出來(lái),所以我們?cè)谥惶崛eact和react-dom的情況下,需要自定義一個(gè)cacheGroup。
// indexA.js
import React from 'react'
import ReactDOM from 'react-dom'
import _ from 'lodash'
import $ from 'jquery'
// webpack.config.js
entry: {
indexA: path.join(__dirname, 'src/indexA.js')
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /react/,
name: 'vendors'
}
}
}
}

我們?nèi)ブ貙懥藇endors打包塊,只打包匹配react的模塊,所以達(dá)到了之前CommonsChunkPlugin的功能。
或者
// index.jsx
import React from 'react'
import ReactDOM from 'react-dom'
// webpack.config.js
entry: {
indexA: path.join(__dirname, 'src/indexA.js'),
vendor: ["react", "react-dom"]
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
name: "vendor",
chunks: "initial"
}
}
}
}

optimization.runtimeChunk
最后提一下runtimeChunk,通過(guò) optimization.runtimeChunk: true 選項(xiàng),webpack會(huì)添加一個(gè)只包含運(yùn)行時(shí)(runtime)額外代碼塊到每一個(gè)入口。(譯注:這個(gè)需要看場(chǎng)景使用,會(huì)導(dǎo)致每個(gè)入口都加載多一份運(yùn)行時(shí)代碼)
總結(jié)
webpack4的splitChunks功能是比較強(qiáng)大的,不過(guò)推薦還是使用默認(rèn)模式,或者提取一下第三方庫(kù)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue實(shí)現(xiàn)多個(gè)元素或多個(gè)組件之間動(dòng)畫效果
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)多個(gè)元素或多個(gè)組件之間動(dòng)畫效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09
Vue項(xiàng)目打包后可修改基礎(chǔ)接口地址配置的具體操作
vue項(xiàng)目打包過(guò)后發(fā)現(xiàn)地址錯(cuò)了或者發(fā)布到別的服務(wù)器發(fā)現(xiàn)請(qǐng)求的地址不對(duì),每次都要去重新打包,太浪費(fèi)時(shí)間,下面這篇文章主要給大家介紹了關(guān)于Vue項(xiàng)目打包后可修改基礎(chǔ)接口地址配置的具體操作,需要的朋友可以參考下2022-08-08
vue中關(guān)于this.refs為空出現(xiàn)原因及分析
這篇文章主要介紹了vue中關(guān)于this.refs為空出現(xiàn)原因及分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
Vue.js學(xué)習(xí)筆記之常用模板語(yǔ)法詳解
本篇文章主要介紹了Vue.js學(xué)習(xí)筆記之常用模板語(yǔ)法詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07
解決vue create 創(chuàng)建項(xiàng)目只有兩個(gè)文件問(wèn)題
這篇文章主要介紹了解決vue create 創(chuàng)建項(xiàng)目只有兩個(gè)文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02
Vue項(xiàng)目如何設(shè)置反向代理和cookie設(shè)置問(wèn)題
這篇文章主要介紹了Vue項(xiàng)目如何設(shè)置反向代理和cookie設(shè)置問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04

