webpack效能優化
一.概述
1.webpack效能優化
- 開發環境效能優化
- 生產環境效能優化
2.開發環境效能優化
- 優化
webpack
的打包構建速度HRM
- 優化程式碼除錯
source-map
3.生產環境效能優化
- 優化打包構建速度
oneOf
babel
快取- 多程序打包
- 優化程式碼執行的效能
- 快取(
hash-chunkhash-contenthash
) tree shacking
code split
- 懶載入/預載入
PWA
externals
dll
- 快取(
4.存在的問題
當我們修改某一個css
或者模組的時候,所有的檔案都會被重新打包;如果模組很多的情況下,這樣是很耗費時間的。我們希望只修改發生變化的模組;這就要使用webpack
HMR
功能;
二.HMR
hot module replacement
:熱模組替換/模組熱替換;
作用:一個模組發生變化,只會重新打包這一個模組,而不是打包所有;這樣會極大地提升構建速度。
使用方法:只需要在devServer
中設定hot
屬性為true
即可:
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
//開啟HRM
hot: true
}
注意:修改了webpack
配置之後,一定要重啟devServer
重啟後,再次開啟devServer
:
npx webpack-dev-server
可以看到開啟的html
檔案的輸出中顯示,已開啟HRM
:
隨後,修改入口檔案index.js
依賴的index.less
,可以看到這一次只重新打包了index.less
模組:
所以驗證了css
檔案可以使用HMR
功能,其實:
-
樣式檔案:可以使用
HMR
功能:因為style-loader
內部實現; -
js
檔案:預設不使用HMR
功能 ;-
解決方法:需要修改
js
程式碼,新增支援HMR
功能的程式碼:if (module.hot){ //一旦 module.hot 為 true ,說明開啟了HMR功能。此時才讓HMR功能生效 module.hot.accept('./print.js', function(){ //方法會監聽print.js檔案的變化,一旦發生變化,其他模組不會重新打包構建,會執行後面的回撥函式 }) }
上面的
print.js
為發生變化的模組,如果有其他發生變化需要開啟HMR
功能的模組,只需要新增相應路徑就可以了;注意:
HMR
功能對js
檔案處理,只能處理非入口js
檔案。因為入口檔案引入了很多依賴,它一變就要不可避免的重新引入依賴; -
-
html
檔案:預設不使用HMR
功能,同時會導致問題:html
檔案不能熱更新了(devServer
的功能,自動更新);-
解決方法:修改
entry
入口,將html
檔案引入。但是還是是用不了HMR
功能;entry: ['./src/js/index.js', './src/index.html']
-
其實
HTML
檔案不需要HMR
功能,因為html
檔案通常只有一個,它變了就說明有重新打包的必要;
-
三.source-map
source-map
是一種技術,提供原始碼到構建後代碼的對映技術。(如果構建後代碼出錯了,通過對映可以追蹤原始碼錯誤)
使用方法為,只需要在五大屬性的同級下新增一個devtool
屬性即可:
devtool: 'source-map'
執行webpack
打包後,會生成一個.map
檔案:
裡面儲存著對映關係:
寫法分為:
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
其中:
-
source-map
:外部; -
作用:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;
-
inline-source-map
:內聯:所有檔案只生成一個source-map
檔案,並且拼接在js
檔案中的最後;- 作用:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;
-
hidden-source-map
:外部- 作用:顯示錯誤程式碼和錯誤原因,但是沒有錯誤位置。不能追蹤到原始碼錯誤,只能提示到構建後代碼的位置;
-
eval-source-map
:內聯:每一個檔案都生成對應的source-map
,並且都在eval
函式中;-
作用:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;只不過提示為每個檔案通過
eval
新增的雜湊值:
-
-
nosources-source-map
:外部;-
作用:顯示錯誤程式碼和錯誤原因,但是沒有任何原始碼資訊;與
hidden-source-map
一起提供隱藏程式碼的服務:但是點選提示,不會顯示原始碼資訊:
-
-
cheap-source-map
:外部;-
作用:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;但是隻能精確到行:
如圖,只有
console
發生了錯誤,但是隻能精確到行;
-
-
cheap-module-source-map
:外部;- 作用:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;同樣只能將錯誤精確到原始碼中的行;不同在於
module
會將loader
的source-map
也加進來;
- 作用:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;同樣只能將錯誤精確到原始碼中的行;不同在於
內聯與外部的區別:
- 外部方式會生成
.map
檔案而內聯是沒有的,而是將.map
檔案的內容內嵌到打包後的js
檔案的最後; - 內聯構建速度更快
下面我們來一個個測試它們的作用:
1.作用演示
source-map
首先在入口檔案index.js
中增添一處錯誤程式碼:
隨後通過下列指令開啟devServer
:
npx webpack-dev-server
在顯示的網頁中可以看到出現了錯誤提示:
並且這個提示包含了錯誤出現在哪個檔案的哪一行中,點選這個提示可以跳轉到原始碼出錯的地方:
所以source-map
的作用為:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;
inline-source-map
注意:修改了webpack.config.js
的配置後,一定要重新啟動devServer
;在開啟的網頁中,一樣顯示了錯誤程式碼的準確資訊和原始碼的錯誤位置;
點選提示即可跳轉到錯誤原始碼的位置:
2.選擇
source-map
有這麼多種寫法,那麼我們怎麼選擇呢?
開發環境
要求:速度快,除錯友好;
-
速度方面:
eval
>inline
>cheap
...通過組合,速度從快到慢的寫法如下:
eval-cheap-source-map
eval-source-map
-
除錯友好方面:定位更精確,就越友好。所以友好程度排名為:
source-map
:精確到具體位置;cheap-module-source-map
:精確到行,但是包含了loader
等依賴檔案;cheap-source-map
:最後是精確到行,但是不包含依賴檔案;
折中方案為採用eval-source-map
或者 eval-cheap-module-source-map
,框架中的腳手架預設使用前者;
生產環境
原始碼要不要隱藏?除錯要不要更友好?
內聯會讓程式碼體積變大,所以在生產環境中一般不使用內聯;
-
隱藏程式碼:
hidden-source-map
:只隱藏原始碼,會提示構建後的程式碼錯誤;nosources-source-map
:全部隱藏;
最後結論:
需要除錯友好時使用
source-map
,需要速度快時使用cheap-module-source-map
四.oneOf
通常一個loader
處理一類檔案,也就是說並不是每一個檔案都需要使用全部的loader
去處理,這樣會降低效能;可以使用oneOf
設定檔案只匹配oneOf
陣列中的一個loader
:
rules: [
{
//oneOf的意思為:只會匹配以下中的一個loader
oneOf: [
//1.處理css檔案
{
//...
},
//2.處理less檔案
{
//...
},
//3.js語法檢查
{
//...
},
//4.js相容性處理
{
//...
},
//5.處理圖片
{
//...
},
//6.處理html中的圖片
{
//...
},
//8.處理其他檔案
{
//...
}
]
}
]
需要注意的是隻能選擇oneOf
陣列中的一個loader
執行;像eslint
和babel
這樣的loader
都會處理js
檔案,這就需要將其中一個loader
抽離出oneOf
陣列了:
rules: [
//js語法檢查eslint
{
test: /\.js$/,
exclude: /node_modules/,
//優先執行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
oneOf: [
//js相容性處理babel
{
//...
},
]
}
]
並且eslint-loader
中enforce
屬性為pre
表示會優先執行該loader
;這樣兩個loader
就不會互相影響了;
這樣配合著使用oneOf
可以提升打包構建的速度;
使用webpack
進行檔案打包時,並不是每次打包所有的檔案都不一樣,如果可以將每次打包都不變的檔案快取起來,下次直接複用,就可以大幅度提升打包的速度。這就涉及到了webpack
中的快取機制;
五.快取
1.babel快取
比如處理了100
份檔案,我們希望在第一次處理的時候可以將這100
份檔案的處理結果快取起來,以後只重新處理其中發生變化的檔案,其餘檔案直接使用快取。可通過cacheDirectory
開啟快取,配置如下:
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {version: 3},
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
//開啟babel快取
//第二次構建時,會讀取之前的快取
cacheDirectory: true
}
},
2.檔案基本快取
src
的檔案結構如下:
首先在根目錄下建立一個伺服器檔案server.js
:
/**
* 伺服器
* 啟動伺服器指令;
* 方式一:
* 首先需要全域性安裝:npm i nodemon -g
* nodemon server.js
*
* 方式二:
* node server.js
*/
const express = require('express')
const app = express();
app.use(express.static('build', {maxAge: 1000 * 3600}));
app.listen(3000)
執行該檔案:
node server.js
由於該檔案監聽的是3000
埠,所以訪問地址為:http://localhost:3000
開啟除錯工具的network
選項,可以看到第一次開啟網頁,資源都需要請求:
重新整理一次後,再次檢視:
可以看到,瀏覽器直接從快取讀取了這兩個檔案。
點開其中一個檔案,可以看到在HTTP
響應頭中的max-age
欄位正是server.js
中設定的3600s
:
也就是這兩個資源需要被強制快取兩個小時
讀取快取的速度非常之快,所以快取能夠大大提高載入速度。
存在的問題:
當被快取的檔案發生變化時,就不能使用這個過期的快取了,需要重新請求該最新版的該檔案。
解決方案:
hash
每次打包都給檔案拼接上一個hash
值當做是版本號,這樣快取的檔案與發生更新後的檔案由於版本號不一樣,名字也就不同,所以會重新載入更新後的檔案;具體配置為在每個檔案的output
物件中為filename
屬性拼接上hash
值:
output: {
filename: 'js/built.[hash:10].js',
path: resolve(__dirname, 'build')
},
//...
plugins: [
new MiniCssExtractPlugin({
//設定輸出路徑
filename: 'css/built.[hash:10].css',
})
]
再次執行webpack
打包,此時打包出來的檔案就會拼接上10
位的hash
值了:
這個hash
值取的是每次執行webpack
時生成hash
值的前10
位:
這樣就保證了,每次打包都會生成不同的打包檔案。
隨後,執行伺服器程式碼:
node server.js
開啟地址http://localhost:3000
,這次請求的資源都帶上了hash
值:
當我們修改了原始碼之後,再進行打包,打包出來的檔案就拼接上了另外10
位hash
值了:
此時重新整理網頁,瀏覽器就會重新請求,經過再次打包的更新後的檔案了:
檔案資源快取存在的問題
由於所有檔案都共享同一次webpack
打包時生成的hash
值,所以只要有一個檔案發生了變化,重新打包後,所有檔案的快取都會失效。
chunkhash
webpack
引入了另外一個hash
值:chunkhash
。只需要將webpack.config.js
配置中的hash
替換為chunkhash
即可。根據chunk
生成雜湊值,如果打包來源於同一個chunk
雜湊值一樣。
此時,再進行打包,發現打包後的css
和js
檔案仍然共有一個hash
值:
這是因為:css
檔案是在js
檔案中引入的,所以同屬於一個chunk
。
chunk
:入口檔案index.js
會引入很多檔案,比如css
、less
等,這樣一個整體被稱為chunk
,即程式碼塊;
contenthash
使用contenthash
才是最優解,這是根據檔案的內容生成的hash
值,不同檔案的hash
值一定不一樣;將webpack.config.js
中的chunkhash
改為contenthash
,再次打包:
這次打包出來的css
檔案與js
檔案的hash
值就不一樣了;這樣的話如果只改變了css
檔案,那麼瀏覽器第一次快取的js
檔案仍然有效,只需要重新請求css
檔案即可:
第一次開啟頁面,兩檔案都需要請求:
修改css
檔案並重新打包後,再一次重新整理瀏覽器。只需要重新請求css
檔案即可:
所以一共有兩種快取策略:
babel
快取:直接在webpack.config.js
中配置即可;- 優點:讓第二次打包構建速度更快;
- 檔案資源快取:給檔案新增
hash
值,有三種選擇,最優解為新增contenthash
值。- 優點:讓上線執行的程式碼更好地使用快取;
六.Tree shaking
1.簡介
在應用程式中引入的原始碼和一些庫可以理解為樹上的一個綠色的活的樹葉,哪些應用程式中沒有引用的程式碼就是灰色的,枯萎的樹葉;為了去掉這些灰色的樹葉,我們需要搖晃這棵樹,這就是樹搖(tree shaking
)的概念;樹搖是為了去除應用程式中沒有使用的程式碼,這樣能讓程式碼的體積變得更小。
tree shaking
的作用為去除無用的程式碼;但是有前提:
- 必須使用
ES6
模組化; - 將
mode
設定為production
環境;
2.作用
減少程式碼體積。例如:
// app.js
export function A(a, b) {
return a + b
}
export function B(a, b) {
return a + b
}
// index.js
import {A} from '/app.js'
A(1, 2)
當index.js
引用了app.js
的A
函式,如果tree-shaking
起了作用,B
函式是不會被打包進最後的bundle
的。因為,index.js
中並沒有用到B
函式;
3.配置
在webpack.josn
中配置sideEffects
:
"sideEffects": false
它表示所有的程式碼都是沒有副作用的,都可進行tree shaking
。
問題:可能會把css
或@babel/polyfill
等檔案刪除(副作用);
解決方法:將sideEffects
的值設定為["*.css"]
:
"sideEffects": ["*.css"]
這樣寫在該陣列內的元素,就不會執行tree shaking
;
預設情況下會將入口檔案index.js
與其引入的js
檔案一起打包,最後輸出一個js
檔案。如圖所示,原始碼資料夾src
中的test.js
和index.js
最後都被打包成了一個js
檔案:
如果我們需要將程式碼分門別類地打包呢?這時候就需要用到程式碼分割了;
七.程式碼分割
1.直接使用多入口
要進行抽離,可以先從entry
指定的入口檔案路徑下手,配置如下:
entry: {
index: './src/js/index.js',
test: './src/js/test.js'
},
entry
這樣的配置稱為多入口;對於多入口:有一個入口,最終輸出就有一個bundle
。配置後,執行webpack
進行打包:
由於配置裡有兩個入口,所以打包後輸出的js
檔案也有兩個:
為了方便區分打包後輸出的內容,修改output
配置:
output: {
//[name]:取檔名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
再次打包:此時打包後的檔案與打包前的檔案的對應關係就一目瞭然了:
一般,單頁面應用對應的是單入口,多頁面的對應的是多入口;
2.使用optimization
單入口時
在webpack.config.js
中新增:
entry: './src/js/index.js',
//...
optimization: {
splitChunks: {
chunks: 'all',
}
},
然後在入口檔案index.js
中引入jquery
:
import $ from 'jquery'
執行webpack
進行打包,可以看到輸出了兩個檔案,另外一個檔案用於儲存jquery
:
這種方式的好處是:可以將node_modules
中程式碼單獨打包一個chunk
最終輸出;
多入口時
配置如下:
entry: {
index: './src/js/index.js',
test: './src/js/test.js'
},
//...
optimization: {
splitChunks: {
chunks: 'all',
}
},
再次打包過後生成了三個chunk
:
如果沒有在配置中新增optimization
屬性,那麼在多入口的情況下,如果兩個js
檔案都引入了jquery
,那麼打包出來的兩個js
檔案,大小是相近的:
這是因為兩個js
檔案各自打包一份jquery
,這種重複打包比較不好;
解決方法為新增optimization
屬性,它的好處為:它就會分析多入口chunk
中有沒有公共的檔案,如果有就會打包成單獨的chunk
總結:optimization
的作用:
-
單入口時:可以將
node_modules
中程式碼單獨打包一個chunk
最終輸出; -
多入口時:會分析多入口
chunk
中有沒有公共的檔案,如果有就會打包成單獨的chunk
;
也就是說多入口時一般配合著optimization
使用;
3.單入口動態引入(常用)
當若想要只使用單入口就能實現多入口配合optimization
使用時達到的效果的話,可以採用這種方法:
通過js
程式碼,讓某個檔案被單獨打包成一個chunk
。import
動態匯入語法,能將某個檔案單獨打包。比如在入口檔案index.js
中動態引入test.js
:
test.js
程式碼:
export function mul(x, y) {
return x * y;
}
export function count(x, y) {
return x - y;
}
隨後在index.js
中動態引入:
import('./test')
.then(({mul, count}) => {
//檔案載入成功
// eslint-disable-next-line
console.log(mul(2, 4))
})
.catch(() => {
// eslint-disable-next-line
console.log('檔案載入失敗')
})
import
返回的是一個Promise
物件,then
方法代表載入成功,catch
方法代表載入失敗;
隨後打包,輸出了兩個js
檔案,其中的1.js
代表test.js
:
打包出來的js
檔名字是webpack
打包過程生成的隨機id
,不太美觀,我們通過給import
增加引數設定打包後輸出的js
檔案的名字:
//設定輸出檔名為test
import(/* webpackChunkName: 'test' */'./test')
.then(({mul, count}) => {
// eslint-disable-next-line
console.log(mul(2, 4))
})
.catch(() => {
// eslint-disable-next-line
console.log('檔案載入失敗')
})
再次打包:
可見,自定義了test.js
打包後輸出檔案的名字;
八.懶載入和預載入
1.懶載入
指的是觸發了某些條件才載入,而不是一開始就載入;這裡講的是js
檔案的懶載入;
可以通過import().then
的方式實現js
檔案的懶載入,比如在入口檔案index.js
中這樣寫:
//懶載入
document.getElementById('btn').onclick = function(){
import((/* webpackChunkName: 'test' */ './test').then((mul) => {
console.log(mul(3, 5));
})
}
一般將import
語句,放在一些判斷語句中,當滿足一定的條件時才執行import
動態引入;如上面的程式碼,將引入程式碼放在了按鈕btn
的點選事件中,只有點選按鈕btn
才會引入test.js
檔案;
首先,原始檔目錄src
結構如下:
test.js
程式碼如下:
console.log('test.js檔案被載入了');
export function mul(x, y) {
return x * y;
}
index.js
的程式碼如下:
console.log('index.js檔案被載入了');
document.getElementById('btn').onclick = function(){
//懶載入
import(/* webpackChunkName: 'test'*/ './test').then(() => {
console.log('test.js被執行了');
})
}
index.html
程式碼如下,設定了一個按鈕:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>hello lazy-loading</h1>
<button id="btn">按鈕</button>
</body>
</html>
打包後,開啟經過打包的index.html
檔案:
可以看到,此時只加載了index.js
,當點選了按鈕後,才會載入test.js
檔案:
當我們多次點選按鈕時,發現只會再次執行test.js
而不會再次載入。因為第一次載入之後該檔案就被快取起來了,之後使用的時候就可以直接從快取中讀取了:
2.預載入
為入口檔案index.js
新增webpackPrefetch
:
console.log('index.js檔案被載入了');
document.getElementById('btn').onclick = function(){
//懶載入
//預載入prefetch
import(/* webpackChunkName: 'test', webpackPrefetch: true*/ './test').then(() => {
console.log('test.js被執行了');
})
}
打包後,開啟經過打包的index.html
檔案:可以看到test.js
檔案已經提前載入好了:
但是還沒有輸出:
當我們點選按鈕後,會從快取中直接讀取,所以會大大提升test.js
的載入速度:
3.區別
-
懶載入:當檔案需要使用時才載入;
-
預載入:在使用之前,提前載入
js
檔案;正常載入可以認為是並行載入:同一時間載入多個檔案;這些檔案沒有優先順序,只是按程式碼順序載入,這樣就會導致提前載入還不需要用到的檔案;
而預載入
prefetch
:是等其他資源載入完畢,瀏覽器空閒了,才會偷偷地載入資源;不會阻礙重要資源的優先載入;
總的來說:懶載入用的比較多,但是預載入存在相容性問題,需要慎用;
九.PWA
1.簡介
PWA
:漸進式網路開發應用程式,簡而言之就是離線訪問技術;
實現它需要用到外掛:workbox-webpack-plugin
;
首先全域性下載該外掛:
npm i workbox-webpack-plugin -D
然後在webpack.config.js
中引入該外掛:
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
然後直接在plugins
屬性中使用就可以了:
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true
})
],
GenerateSW
中的兩個引數作用為:
- 幫助
Service Worker
快速啟動; - 刪除舊的
Service Worker
;
最終會生成一個Service Worker
的配置檔案;
2.配置
需要在入口檔案index.js
中註冊Service Worker
:
// 註冊Service Worker
// 處理相容性問題
if('serviceworker' in navigator){//如果有這個屬性,才在頁面全域性資源載入完後進行註冊
window.addEventListener('load', () => {
navigator.serviceW
orker.register('/service-worker.js')
//成功
.then(() => {
console.log('sw註冊成功了');
})
//失敗
.catch(() => {
console.log('sw註冊失敗了');
})
})
}
3.打包
此時執行打包會報eslint
的錯誤:
原因:eslint
不認識window
、navigator
這些全域性變數;
解決方法:需要修改webpack.json
中eslint
的配置。
"eslintConfig": {
"extends": "airbnb-base",
"env": {
"browser": true
}
},
其中的"browser": true
表示:支援瀏覽器端全域性變數;
再次執行webpack
構建就不會報錯了,成功輸出了service-worker.js
等檔案:
4.驗證Server Worker
sw
程式碼必須執行在伺服器上,所以需要在伺服器檔案中啟動構建後生成的sw
檔案;有多種方式可以選擇:
-
nodejs
-
通過
npm i serve -g
全域性安裝serve
包,安裝之後可以直接通過serve -s build
啟動伺服器,將build
目錄下的所有資源作為靜態資源暴露出去:
開啟這個連結:
可以看到,成功註冊了serviceWorker
,隨後就可以在Application
選項中的Service Workers
中看到註冊的檔案了:
並且可以在Cache
中看到儲存的資源:
隨後在Network
中將網路設定為離線Offline
:
隨後重新整理網頁,網頁還是可以顯示。這是因為,這些檔案都是直接從Service Worker
中直接讀取的:
這就是PWA
:離線訪問技術;
十.多程序打包
1.安裝
我們知道js
引擎是單執行緒的,同一時間只能幹一件事。所以可以通過多程序來優化打包速度;
首先需要下載thread-loader
:
npm i thread-loader -D
2.配置
一般應用在babel
中,一旦需要需要使用多個loader
時,就要放在use
中:
//4.js相容性處理
{
test: /\.js$/,
exclude: /node_modules/,
use: [
//開啟多程序打包
'thread-loader',
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {version: 3},
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
//開啟babel快取
cacheDirectory: true
}
}
]
},
缺點:程序啟動需要時間:600ms
,程序通訊也需要時間開銷;所以只有在工作消耗時間比較長時,才需要多程序打包;由於開發中絕大多數都是js
檔案,babel
除了語法檢查外還有進行相容性處理,生成大量相容性程式碼,所以應該給babel
開啟多程序打包;
3.打包
不加thread-loader
的打包時間:
加了thread-loader
後的打包時間:
需要花費更多的時間。這是因為當前原始檔中的js
程式碼量比較少,而開啟多程序需要時間,所以程式碼量小的時候不建議使用多程序打包;只有實際開發中,程式碼量上去了它的加速效果才會凸顯出來。
如果想調整的話,可以將 'thread-loader'
改成物件形式:
{
loader: 'thread-loader',
options: {
workers: 2//指定使用2個程序
}
}
開啟多程序會增大開銷,千萬不能亂用!
十一.Externals和Dll
1.Externals
在webpack.config.js
中新增externals
屬性,該屬性與五大基本屬性同級:
externals: {
//拒絕jquery被打包進來
jquery: 'jQuery'
}
作用為:可以讓webpack
不對某些庫或包進行打包;不過忽略了之後,比如刪除的jquery
,就需要在html
檔案中通過srcipt
標籤手動引入。
注意寫法:字串內指定包名,不能寫錯;
2.Dll
表示動態連結庫。可以將多個功能(包)打包成一個chunk
。比如之前我們都是將node_modules
目錄下的所有檔案打包成一個chunk
,而使用dll
可以將它分為多個不同的部分,分別打包出多個chunk
。
也就是對某些庫進行單獨打包;
生成dll
檔案
使用dll
需要在根目錄下新增一個webpack.dll.js
配置檔案;配置如下:
/**
* 使用dll技術,對某些庫(vue、jquery、react等)進行單獨打包
*/
const { resolve } = require('path')
//引入webpack外掛
const webpack = require('webpack')
module.exports = {
entry: {
//最終打包生成的[name] --> jquery
//['jquery] --> 要打包的庫是jquery
jquery: ['jquery']
},
output: {
//這裡的[name]就是上面的jquery
filename: '[name].js',
path: resolve(__dirname, 'dll'),
library: '[name]_[hash]',//打包的庫裡面向外暴露的內容叫什麼名字
},
plugins: [
//該外掛作用為:打包生成一個manifest.json檔案 --> 提供和jquery的對映
new webpack.DllPlugin({
name: '[name]_[hash]',//對映的庫暴露的內容名稱
path: resolve(__dirname, 'dll/manifest.json')//輸出的名稱
})
],
mode: 'production'
}
總結以下就是:entry
和output
中的配置作用為:單獨打包jquery
檔案,並將打包輸出的檔案命名為jquery_hash
。外掛的作用為,生成一個mainfest.json
檔案,管理與jquery
的對映關係;
當執行webpack
時,預設查詢的是 webpack.config.js
配置檔案;需求:需要執行webpack.dll.js
檔案。這就需要:執行以下指令手動指定執行的配置檔案:
webpack --config webpack.dll.js
執行該命令後,打包生成一個dll
目錄,裡面有兩個檔案:jquery.js
和manifest.json
:
並且這兩個檔案內都會拼接上一個hash
值,改值與webpack
打包時生成的hash
一致:
第一個檔案:為指定的庫單獨打包出來的檔案;
第二個檔案:manifest.json
檔案記錄了哪些庫不用經過了單獨打包了,不需要重複打包的對映關係;
改變webpack
配置
同時,相應的也要修改webpack.config.js
的配置,新增兩個外掛:webpack
和add-asset-html-webpack-plugin
(記住要先全域性下載外掛):
const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
//1.引入webpack外掛
const webpack = require('webpack')
//3.引入add-asset-html-webpack-plugin外掛
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
module.exports = {
entry : './src/index.js',
output : {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
plugins: [
//plugins的配置
new HtmlWebpackPlugin({
template: './src/index.html'
}),
//2.使用webpack外掛告訴webpack哪些庫不參與打包,同時使用時的名稱也得改變
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
//4.將該外掛指定的檔案打包輸出,並在html中自動引入該資源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
mode: 'development'
}
具體新增配置如程式碼中的1~4
:
-
第一個外掛的作用為:根據第一步中生成的
manifest.json
告知webpack
不用打包該檔案指定的庫(這裡是jquery
),這樣打包出來的built.js
也就不含有這些庫(jquery
)的內容了。注意:由於
jquery
被指定忽略了,即使入口檔案index.js
中引入了jquery
,打包時也就不會打包jquery
-
第二個外掛的作用為:將第一步被單獨打包生成的庫檔案(這裡是
jquery
)自動引入html
檔案中。
配置後之後,再次執行webpack
打包,可以發現,在第一步中單獨打包的jquery
檔案被再次打包了出來:
並且html
檔案中自動引入了新增的打包檔案jquery
,這是第二個外掛的作用:
使用了webpack
外掛,打包時就會忽略外掛指定的jquery
檔案。由於打包出來的html
只自動引入了打包後的built.js
檔案,而built.js
因為外掛的原因並沒有打包jquery
;這時候就需要在html
檔案中手動引入jquery
檔案了。此時有兩種方法:
- 第一:在
html
檔案中直接通過script
標籤引入; - 第二:使用上述的
add-asset-html-webpack-plugin
外掛打包引入;
3.總結
經過以上兩步操作之後,原始碼發生改變只需要再次執行webpack.config.js
即可,不需要執行webpack.dll.js
,也就是說不需要再重複打包jquery
了,達到了效能優化的效果;今後專案中會有很多的第三方庫,我們都可以使用dll
的方式對它們進行單獨打包。在使用webpack
重新打包的時候就不用再打包這些第三方的庫了,這樣能讓webpack
第二次打包的速度快很多;
externals
:只忽略某些指定的庫,不對它們進行打包,使用時需要通過script
標籤引入;需要依賴外鏈引入。dll
:需要打包一次,將來就不需要重複打包了。如果想要將一些第三方庫整合在一起,不依賴外鏈,就使用這種方式。