前言
隨着vite的誕生,webpack似乎漸漸的被大家拋棄。前陣子我也用vue@3.x + vite@4.x開發了一個後台管理系統,體驗了一把,確實有被vite飛快的啓動速度給驚豔到。
但是畢竟webpack已經誕生了許久,也經過市場的一些考驗,並且它有着豐富的插件,豐富的功能,一些大型的項目也使用過它,目前來説,它是一個相對於vite來説更穩定的打包工具。
基於以下原因:
- 有些公司由於歷史原因,項目構建也是基於
webpack,所以如果去到一些公司要做腳手架遷移升級什麼的,會使用webpack就顯得十分有必要了。 - 前端構建工具,使用步驟,思想基本差不多,因此學會
webpack,對其他構建工具的學習也有幫助
所以,思來想去,還是決定開個webpack的專欄,跟大家一起學習webpack怎麼配置(雖然專欄開的有點晚了)。
專欄會帶着大家循序漸進的學習webpack,從比較實際的例子出發,讓大家更好的理解webpack,到最後自己配出一個完整的webpack腳手架。當我們學會了自己配webpack後,可以根據自己不同的需求,高度定製屬於自己的腳手架,這還是十分實用的。
本篇文章主要核心,是跟大家一起搞懂使用webpack時,涉及到的幾個概念,讓我們對webpack編譯流程有個更好的理解。
webpack是什麼
它就是一個打包工具:
- 它可以把我們源碼中的
es6+js、less、sass、img、font等多個模塊資源文件,經過一系列處理,編譯出我們瀏覽器能識別並運行css、js、html等前端資源 - 同時,它還提供很多可高度配置的功能(
tree shaking、代碼分割、模塊熱替換)等,幫助我們更好的優化管理我們的代碼和資源
webpack.config.js
webpack在v4.0.0以後擁有開箱即用的功能,即我們安裝了webpack、webpack-cli兩個依賴以後,無需任何配置文件,就可以直接進行打包。這是因為:
webpack默認入口為:根目錄/src/index.jswebpack默認輸出編譯後的文件及文件名為:根目錄/dist/main.js
但我們一般會在我們的項目根目錄新建一個名為webpack.config.js的配置文件。這樣可以指示webpack如何處理我們項目中的文件、資源等;通過更高度的自定義配置來滿足我們項目的需求。
我們看看這個配置文件大概得框架是什麼樣,心裏有個印象:
// webpack.config.js
module.exports = {
// 入口
entry: {},
// 打包輸出
output: {},
// 配置模塊如何解析
resolve: {},
// 配置各種loader
module: {},
// 配置插件
plugins: [],
// 優化(可以進行代碼分割)
optimization: {},
// webpack-dev-server 開發時的配置,一般用於development模式
devServer: {}
};
webapck當然還支持很多配置項,只是我們平時用到的一般就這幾個,其他配置項可以看文檔
注意
webpack配置文件,裏面的代碼隨便大家怎麼玩,配置文件名字也不一定一定要為webpack.config.js,只要保證最後輸出的是一個webpack配置的對象就可以了:
// myconfig.js
// 各種邏輯代碼
...
...
// 保證最後導出一個`webpack`配置的對象就可以
module.exports = {
// 各種webpack配置
entry: {},
output: {},
....
}
"build": "webpack --config myconfig.js"
優化
所有配置都寫在一個文件,這不利於我們維護與開發;而且我們開發項目時,會有多個環境(本地開發環境,測試環境,生產環境),所以我們一般會有不同環境的配置文件,跟所有環境都通用的配置文件。
我們一般會將"通用的配置文件"與“不同環境的配置文件”合併,最後再由webpack運行這個合併後的這個配置文件。
配置文件一般有:
- 開發與生產環境通用的配置(
webpack.common.js) - 開發環境配置(
webpack.dev.js) - 生產環境配置(
webpack.pro.js)
後續文章會教大家如何進行這種配置方法
entry
它指的是webpack開始解析,構建依賴圖的起點。我們一般用{key: value}對象的形式,來配置entry,例如:
module.exports = {
entry: {
index: './src/index.js',
share: './src/share.js',
},
}
這表示:
- 我們項目中有兩個入口
index、share webpack會從index、share兩個入口開始構建它們相關依賴的模塊,從而形成一個依賴圖(後面會有更詳細的解釋)- 打包後會以
key為打包後的文件名字
output
它十分好理解,它可以設置經過webpack打包後的編譯文件的名稱,及應該輸出到哪個位置。
需要注意的是,即使我們設置了多個entry的入口,但是隻能指定一個 output 配置。
Module
我們通過import或require進來的資源文件,或我們項目中的每個文件,都可以看作為一個獨立的模塊。因此,它可以是js文件、css文件、也可以是圖片等任何一種資源。所以我們開發中經常會看到以下語句:
import 'index.css'import A from './x.js'import pic from './x.png'- ...
模塊之間的組合又會形成一個Chunk。Chunk是一個很重要的概念,後文會詳細講。
參考文章:Modules
Loader
通過上文我們知道,項目中每個一個文件都可以看作是一個模塊,又由於我們的文件類型多種多樣,因此我們需要某個東西,它可以把這些模塊解析成webpack能夠識別的有效模塊,並將他們添加到依賴圖中,這個東西就是Loader。
webpack也很貼心的給我們提供了一個module的配置項,它專門用來配置不同的loader以至於解析不同的文件類型(模塊)。這是因為webpack默認只能解析js和json文件,所以如果要解析不同的類型的文件(模塊),我們就要安裝相應的loader:
// webpack.config.js
module.exports = {
...,
modules: {
rules: [
// 解析.txt文件,使用raw-loader
{ test: /.txt$/, use: 'raw-loader' },
],
}
}
loader有兩個屬性:
test屬性,識別出哪些文件會被轉換。use屬性,定義出在進行轉換時,應該使用哪個loader。
webpack中,還有哪些loader可以看這裏
Plugin
Loader用於轉化模塊,Plugin則用來加強webpack打包編譯時的功能。所以它一般有打包優化,資源管理,注入環境變量等功能。
因此webpack也很貼心的給我們提供了一個plugin的配置項,我們想增強什麼功能,去plugin裏面配置就好了,例如我們在我們項目中直接定義一些全局變量:
// webpack.config.js
module.exports = {
...,
plugins: [
new webpack.DefinePlugin({
AUTHOR_NAME: JSON.stringify('Lee'),
})
]
}
// index.js
console.log(AUTHOR_NAME); // Lee
webpack中,還有哪些plugin可以看這裏
Dependency graph(依賴圖)
當我們一個文件依賴另一個文件時,webpack都會將文件視為直接存在“依賴關係”。
舉個簡單的例子,假設我們有三個文件,它們的關係如下:
// main.js
import './index.js';
// index.js
import 'plugin.js'
console.log('I am from index.js');
// plugin.js
console.log('I am plugin.js');
我們來分析一下:
main.js、index.js、plugin.js相當於三個模塊;- 然後
main.js引入了index.js,index.js又引入了plugin.js; - 因此這三個文件都存在相互引用關係;
- 這時形成了這樣一個有引用關係的圖譜:
main.js→index.js→plugin.js
總結:
webpack會以entry為起點,並把它作為依賴圖的起始點;然後分析處理entry裏面內部的import,不斷遞歸查詢類似上述示例的依賴關係,這個過程最後會形成一個具有依賴關係的圖譜,這個圖譜就是“依賴圖”。
webpack會根據這個依賴圖,再進一步操作。
⭐️ Chunk
在我們查閲webpack中文文檔時,我們經常會看到Chunk這個單詞,它並沒有被翻譯成中文。這是因為Chunk是webpack打包過程中產生的一個邏輯概念,需要結合上下文才能理解出它的意思。
Chunk是webapck裏面比較重要的概念,如果我們弄懂它,對webpack打包整個流程,代碼分割也會有很好的理解。
解釋
在webpack打包過程中,會將一個或一組模塊(我們上面説到的webpack中的任何一個文件,都可以看作是一個模塊)組合成一個整體,那麼這個整體就可以當做一個Chunk。一般來説,webpack打包過程中,有幾個Chunk,最後就會輸出幾個js文件。
我們通過 learn-01 這個案例,裏面是最簡單的配置,最簡單的代碼,這樣更好理解Chunk是什麼。
我們有三個js文件:
index.js:入口文件async.js:用來異步引入的文件vendors.js:可以把它當做某個第三方依賴文件
文件代碼及webpack配置如下:
// webpack.config.js
module.exports = {
entry: {
index: './src/index.js'
},
output: {
filename: '[name]-bundle.js'
}
}
// index.js
import './vendors';
import(/* webpackChunkName: "async" */ './async');
console.log('I am from index.js');
// async.js
console.log('I am from async.js');
// vendors.js
console.log('I am from vendors.js');
看到這,我們可以先猜一下打包出來的文件有幾個。如果猜對了,説明大家對Chunk也有一定了解了。
分析
我們先分析一下打包後的文件及結構:
dist
├── async-bundle.js
└── index-bundle.js
一共輸出了兩個js文件。
通過上文,我們知道:
- 每個文件都可以看成一個
module(模塊) - 在
webpack打包過程中,會將一個或一組模塊組合成一個整體,那麼這個整體就可以當做一個Chunk
我們接着分析:
- 通過查看
dist/index-bundle.js文件,我們會發現裏面包含了非異步引入的js文件:入口文件index.js、vendors.js(還有一些webpack打包時,自己加入的代碼 runtime)。這説明index.js、vendors.js這兩個模塊組成了一個Chunk。這裏我們稱它為chunk[initial]; - 通過查看
dist/async-bundle.js文件,裏面只包含了async.js的代碼。這説明異步引入的模塊,會被單獨分成一個Chunk。這裏我們把這個Chunk稱為chunk[no-initial] chunk[initial]與chunk[no-initial]都來自於同一個入口index.js,所以這兩個Chunk組合起來,可以看成一個Chunk組,或者説入口文件會組成一個整體的Chunk組。我們稱它為Chunk[index]
好,相信到這裏,大家應該對Chunk形成有個大致印象了,我們再來捋一下這個過程。
webpack編譯時,通過我們的配置:
- 會先找到
enrty,有幾個entry,就會以這些entry組成一個Chunk組(示例中的Chunk[index]) - 再分析這些
Chunk組,將入口js及這個入口所有相關的依賴模塊,組成一個chunk(示例中的chunk[initial]) - 如果有異步引入的模塊,則這個模塊單獨再組成一個
Chunk(示例中的chunk[no-initial]) - 最後打包輸出
chunk[initial](index-bundle.js)、chunk[no-initial](async-bundle.js)
上述示例,chunk、module(模塊)的關係如下:
形式
通過上述分析,我們可以知道,Chunk有兩種形式:
initial:初始的。我們的入口js及這個入口所有相關的依賴模塊,組合成的一個集合,可以看成一個Chunk。(即上文index.js、vendors.js組成的chunk[initial])non-initial:非初始的。説明它是異步加載的模塊,如果我們在代碼中用到了類似import('./A.js')的語句,這這個js會被單獨分成一個異步的Chunk。(即上文async.js組成的chunk[no-initial])
如何產生Chunk
- 通過
entry配置,產生一個以入口為整體的Chunk組(這個組不會被打包出來,只是會形成這個Chunk組) - 我們的入口
js及這個入口所有相關的依賴模塊,產生initialChunk - 通過異步
import(),產生non-initialChunk - 通過
webpack強大的代碼分割,產生其他chunk
通過上面的解釋與分析,希望大家以後用webpack時,可以在腦海中有一個Chunk形成的大概過程,這對我們使用代碼分割是十分有幫助的。
參考文章:揭示內部原理
Bundle
Bundle指的是webpack打包後的所有產物。
如果我們output配置打包後輸出的文件目錄是dist,我們的Bundle就是dist文件夾裏面的所有產物。
一般來説有幾個Chunk,就會打包出多少個js bundle文件。
打包過程
淺析
為了大家更好的理解上文解析的概念,我們淺析webpack的打包流程,看看上文的概念體現在哪些流程中。
我們在終端運行webpack後,它會經歷以下過程:
- 讀取我們指定的配置文件(
webpack.config.js) - 從入口
entry開始,分析我們的Module(模塊)並遞歸我們整個項目模塊間的依賴關係 - 加載相應的
Loader,將這些Module(模塊)解析成webpack能夠識別的有效模塊,並它們加入到依賴圖(Dependency graph) - 編譯過程會觸發多個事件,執行配置的
Plugin(插件) - 將分析好的模塊進行分組,形成
Chunk - 根據配置文件(
output),輸出最後的Bundle
上述過程可以看作三個階段:
- 初始化階段(過程1)
- 編譯階段(過程2-過程5)
- 輸出階段(過程6)
可總結為下圖:
webpack實際打包的過程當然複雜得多,這裏為了更好的理解,簡化了
體驗
我們從實際出發,通過 learn-02 這個案例,用實際代碼再深入體驗理解一下webpack打包過程,跟涉及到的概念。
我們來看看 learn-02 項目結構:
learn-02
├── index.html
├── package-lock.json
├── package.json
├── project.config.js
└── src
├── assets
│ └── style.less
├── index.js
├── plugin
│ ├── common.js
│ ├── index-vendors.js
│ └── share-vendors.js
└── share.js
我們來介紹一下相應的文件:
index.html:用來運行我們打包後的文件,查看效果project.config.js:webpack配置文件(為了區別webpack.config.js,專門另起一個名字)style.less:樣式index.js:入口文件share.js:入口文件common.js:存放公用方法,分別會被兩個入口文件,引用兩次index-vendors.js:可以當做index.js的一些依賴share-vendors.js:可以當做share.js的一些依賴
以下項目中的相關代碼:
我們看到project.config.js的配置後,以後一樣可以猜猜打包後會輸出幾個文件。
好,現在我們開始分析:
1️⃣ 當我們在終端運行npm run build後,webpack會讀取我們指定的文件project.config.js
2️⃣ 從entry開始,分析我們的模塊。我們的入口有兩個index、share,所以這時會形成兩個Chunk組:Chunk[index]、Chunk[share],並且遞歸我們模塊相應的依賴關係。
3️⃣ index.js引入了style.less,所以會加載相應的Loader,將它解析成webpack能識別的有效模塊,並將其加入到依賴圖中。這時會形成兩個依賴圖:
- 一個由入口
index.js及其依賴組成的依賴圖(index.js -> style.less,common.js、index-vendors.js) - 一個由入口
share.js及其依賴組成的依賴圖(share.js -> common.js、share-vendors.js)
4️⃣ 然後將這些模塊進行分組:
- 入口
index.js及其依賴組成的一個chunk[initial-index]; - 入口
share.js及其依賴組成的chunk[initial-share]
5️⃣ 發現我們的配置中,還利用代碼分割把commonjs也獨立分割出來,因此它獨立組成了一個chunk[initial-common]
6️⃣ 至此,webpack已經分出了三個chunk:
chunk[initial-index]chunk[initial-share]chunk[initial-common]
7️⃣ 根據output最後輸出Bundle
同樣,實際打包過程肯定要複雜得多
最後
- 這篇文章分析講解了
webpack裏面涉及到的一些概念,尤其是Chunk的知識比較重要。理解了Chunk,大家一定會對webpack有一個更好的理解。希望讀完這篇文章後,我們在使用webpack時,腦海會有一個大致的過程,跟分辨出大概有幾個Chunk - 後續的文章會開始教大家怎麼配置
webpack,如果感興趣的話可以關注一下這個👉🏻專欄 - 文章涉及到的案例已經上傳到 github,非常歡迎
star、fork學習
最後的最後,如果大家覺得文章有幫助到,創作不易,還請大家多點贊轉發,如果有異同點,歡迎評論討論。