博客 / 詳情

返回

弄懂這幾個概念後,我對webpack有了新的理解

前言

隨着vite的誕生,webpack似乎漸漸的被大家拋棄。前陣子我也用vue@3.x + vite@4.x開發了一個後台管理系統,體驗了一把,確實有被vite飛快的啓動速度給驚豔到。

但是畢竟webpack已經誕生了許久,也經過市場的一些考驗,並且它有着豐富的插件,豐富的功能,一些大型的項目也使用過它,目前來説,它是一個相對於vite來説更穩定的打包工具。

基於以下原因:

  • 有些公司由於歷史原因,項目構建也是基於webpack,所以如果去到一些公司要做腳手架遷移升級什麼的,會使用webpack就顯得十分有必要了。
  • 前端構建工具,使用步驟,思想基本差不多,因此學會webpack,對其他構建工具的學習也有幫助

所以,思來想去,還是決定開個webpack的專欄,跟大家一起學習webpack怎麼配置(雖然專欄開的有點晚了)。

專欄會帶着大家循序漸進的學習webpack,從比較實際的例子出發,讓大家更好的理解webpack,到最後自己配出一個完整的webpack腳手架。當我們學會了自己配webpack後,可以根據自己不同的需求,高度定製屬於自己的腳手架,這還是十分實用的。

本篇文章主要核心,是跟大家一起搞懂使用webpack時,涉及到的幾個概念,讓我們對webpack編譯流程有個更好的理解

webpack是什麼

它就是一個打包工具:

  • 它可以把我們源碼中的es6+ jslesssassimgfont等多個模塊資源文件,經過一系列處理,編譯出我們瀏覽器能識別並運行cssjshtml等前端資源
  • 同時,它還提供很多可高度配置的功能(tree shaking代碼分割模塊熱替換)等,幫助我們更好的優化管理我們的代碼和資源

webpack.config.js

webpackv4.0.0以後擁有開箱即用的功能,即我們安裝了webpackwebpack-cli兩個依賴以後,無需任何配置文件,就可以直接進行打包。這是因為:

  • webpack默認入口為:根目錄/src/index.js
  • webpack默認輸出編譯後的文件及文件名為:根目錄/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',
    },
}

這表示:

  • 我們項目中有兩個入口indexshare
  • webpack會從indexshare兩個入口開始構建它們相關依賴的模塊,從而形成一個依賴圖(後面會有更詳細的解釋)
  • 打包後會以key為打包後的文件名字

output

它十分好理解,它可以設置經過webpack打包後的編譯文件的名稱,及應該輸出到哪個位置。

需要注意的是,即使我們設置了多個entry的入口,但是隻能指定一個 output 配置。

Module

我們通過importrequire進來的資源文件,或我們項目中的每個文件,都可以看作為一個獨立的模塊。因此,它可以是js文件、css文件、也可以是圖片等任何一種資源。所以我們開發中經常會看到以下語句:

  • import 'index.css'
  • import A from './x.js'
  • import pic from './x.png'
  • ...

模塊之間的組合又會形成一個ChunkChunk是一個很重要的概念,後文會詳細講。

參考文章:Modules

Loader

通過上文我們知道,項目中每個一個文件都可以看作是一個模塊,又由於我們的文件類型多種多樣,因此我們需要某個東西,它可以把這些模塊解析成webpack能夠識別的有效模塊,並將他們添加到依賴圖中,這個東西就是Loader

webpack也很貼心的給我們提供了一個module的配置項,它專門用來配置不同的loader以至於解析不同的文件類型(模塊)。這是因為webpack默認只能解析jsjson文件,所以如果要解析不同的類型的文件(模塊),我們就要安裝相應的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');

我們來分析一下:

  1. main.jsindex.jsplugin.js相當於三個模塊;
  2. 然後main.js引入了index.jsindex.js又引入了plugin.js
  3. 因此這三個文件都存在相互引用關係;
  4. 這時形成了這樣一個有引用關係的圖譜:main.jsindex.jsplugin.js

總結:

webpack會以entry為起點,並把它作為依賴圖的起始點;然後分析處理entry裏面內部的import,不斷遞歸查詢類似上述示例的依賴關係,這個過程最後會形成一個具有依賴關係的圖譜,這個圖譜就是“依賴圖”。

webpack會根據這個依賴圖,再進一步操作。

⭐️ Chunk

在我們查閲webpack中文文檔時,我們經常會看到Chunk這個單詞,它並沒有被翻譯成中文。這是因為Chunkwebpack打包過程中產生的一個邏輯概念,需要結合上下文才能理解出它的意思。

Chunkwebapck裏面比較重要的概念,如果我們弄懂它,對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.jsvendors.js(還有一些webpack打包時,自己加入的代碼 runtime)。這説明index.jsvendors.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編譯時,通過我們的配置:

  1. 會先找到enrty,有幾個entry,就會以這些entry組成一個Chunk組(示例中的Chunk[index]
  2. 再分析這些Chunk組,將入口js及這個入口所有相關的依賴模塊,組成一個chunk(示例中的chunk[initial]
  3. 如果有異步引入的模塊,則這個模塊單獨再組成一個Chunk(示例中的chunk[no-initial]
  4. 最後打包輸出chunk[initial]index-bundle.js)、chunk[no-initial]async-bundle.js

上述示例,chunkmodule(模塊)的關係如下:

chunk_pic

形式

通過上述分析,我們可以知道,Chunk有兩種形式:

  • initial:初始的。我們的入口js及這個入口所有相關的依賴模塊,組合成的一個集合,可以看成一個Chunk。(即上文index.jsvendors.js組成的chunk[initial]
  • non-initial:非初始的。説明它是異步加載的模塊,如果我們在代碼中用到了類似import('./A.js')的語句,這這個js會被單獨分成一個異步的Chunk。(即上文async.js組成的chunk[no-initial]

如何產生Chunk

  • 通過entry配置,產生一個以入口為整體的Chunk組(這個組不會被打包出來,只是會形成這個Chunk組)
  • 我們的入口js及這個入口所有相關的依賴模塊,產生initial Chunk
  • 通過異步import(),產生non-initial Chunk
  • 通過webpack強大的代碼分割,產生其他chunk

通過上面的解釋與分析,希望大家以後用webpack時,可以在腦海中有一個Chunk形成的大概過程,這對我們使用代碼分割是十分有幫助的。

參考文章:揭示內部原理

Bundle

Bundle指的是webpack打包後的所有產物。

如果我們output配置打包後輸出的文件目錄是dist,我們的Bundle就是dist文件夾裏面的所有產物。

一般來説有幾個Chunk,就會打包出多少個js bundle文件。

打包過程

淺析

為了大家更好的理解上文解析的概念,我們淺析webpack的打包流程,看看上文的概念體現在哪些流程中。

我們在終端運行webpack後,它會經歷以下過程:

  1. 讀取我們指定的配置文件(webpack.config.js
  2. 從入口entry開始,分析我們的Module(模塊)並遞歸我們整個項目模塊間的依賴關係
  3. 加載相應的Loader,將這些Module(模塊)解析成webpack能夠識別的有效模塊,並它們加入到依賴圖(Dependency graph
  4. 編譯過程會觸發多個事件,執行配置的Plugin(插件)
  5. 將分析好的模塊進行分組,形成Chunk
  6. 根據配置文件(output),輸出最後的Bundle

上述過程可以看作三個階段:

  • 初始化階段(過程1)
  • 編譯階段(過程2-過程5)
  • 輸出階段(過程6)

可總結為下圖:

webpack_process

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.jswebpack配置文件(為了區別webpack.config.js,專門另起一個名字)
  • style.less:樣式
  • index.js:入口文件
  • share.js:入口文件
  • common.js:存放公用方法,分別會被兩個入口文件,引用兩次
  • index-vendors.js:可以當做index.js的一些依賴
  • share-vendors.js:可以當做share.js的一些依賴

以下項目中的相關代碼:

directory

我們看到project.config.js的配置後,以後一樣可以猜猜打包後會輸出幾個文件。

好,現在我們開始分析:

1️⃣ 當我們在終端運行npm run build後,webpack會讀取我們指定的文件project.config.js

2️⃣ 從entry開始,分析我們的模塊。我們的入口有兩個indexshare,所以這時會形成兩個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,非常歡迎starfork學習

最後的最後,如果大家覺得文章有幫助到,創作不易,還請大家多點贊轉發,如果有異同點,歡迎評論討論。

user avatar 1023 頭像 dujing_5b7edb9db0b1c 頭像 flymon 頭像 _raymond 頭像 shaochuancs 頭像 zhangxishuo 頭像 buxia97 頭像 suporka 頭像 mulander 頭像 pugongyingxiangyanghua 頭像 lesini 頭像 musicfe 頭像
40 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.