Webpack配置全解析(基礎(chǔ)篇)

??Webpack憑借強(qiáng)大的功能,成為最流行和最活躍的打包工具,也是面試時(shí)高級(jí)程序員必須掌握的“軟技能”;筆者結(jié)合在項(xiàng)目中的使用經(jīng)驗(yàn),介紹webpack的使用;本文是入門篇,主要介紹webpack的入口、輸出和各種loader、plugins的使用以及開發(fā)環(huán)境的搭建。本文所有的demo代碼均在https://github.com/acexyf/WebpackDemo
概念
??來看一下官網(wǎng)對(duì)webpack的定義:

本質(zhì)上,webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler)。當(dāng) webpack 處理應(yīng)用程序時(shí),它會(huì)遞歸地構(gòu)建一個(gè)依賴關(guān)系圖(dependency graph),其中包含應(yīng)用程序需要的每個(gè)模塊,然后將所有這些模塊打包成一個(gè)或多個(gè)bundle。
??首先webpack是一個(gè)靜態(tài)模塊打包器,所謂的靜態(tài)模塊,包括腳本、樣式表和圖片等等;webpack打包時(shí)首先遍歷所有的靜態(tài)資源,根據(jù)資源的引用,構(gòu)建出一個(gè)依賴關(guān)系圖,然后再將模塊劃分,打包出一個(gè)或多個(gè)bundle。再次白piao一下官網(wǎng)的圖,生動(dòng)的描述了這個(gè)過程:

??提到webpack,就不得不提webpack的四個(gè)核心概念

入口(entry):指示 webpack 應(yīng)該使用哪個(gè)模塊,來作為構(gòu)建其內(nèi)部依賴圖的開始

輸出(output):在哪里輸出它所創(chuàng)建的 bundles

loader:讓 webpack 能夠去處理那些非 JavaScript 文件

插件(plugins):用于執(zhí)行范圍更廣的任務(wù)
你的第一個(gè)打包器
??我們首先在全局安裝webpack:
npm install webpack webpack-cli –g
??webpack可以不使用配置文件,直接通過命令行構(gòu)建,用法如下:
webpack
??這里的entry和output就對(duì)應(yīng)了上述概念中的入口和輸入,我們來新建一個(gè)入口文件:

??有了入口文件我們還需要通過命令行定義一下輸入路徑dist/bundle.js:
webpack index.js -o dist/bundle.js
??這樣webpack就會(huì)在dist目錄生成打包后的文件。

??我們也可以在項(xiàng)目目錄新建一個(gè)html引入打包后的bundle.js文件查看效果。
配置文件
??命令行的打包方式僅限于簡(jiǎn)單的項(xiàng)目,如果我們的項(xiàng)目較為復(fù)雜,有多個(gè)入口,我們不可能每次打包都把入口記下來;因此一般項(xiàng)目中都使用配置文件來進(jìn)行打包;配置文件的命令方式如下:
webpack [--config webpack.config.js]
??配置文件默認(rèn)的名稱就是webpack.config.js,一個(gè)項(xiàng)目中經(jīng)常會(huì)有多套配置文件,我們可以針對(duì)不同環(huán)境配置不同的文件,通過--config來進(jìn)行切換:
//生產(chǎn)環(huán)境配置
webpack --config webpack.prod.config.js
//開發(fā)環(huán)境配置
webpack --config webpack.dev.config.js
多種配置類型
??config配置文件通過module.exports導(dǎo)出一個(gè)配置對(duì)象:

??除了導(dǎo)出為對(duì)象,還可以導(dǎo)出為一個(gè)函數(shù),函數(shù)中會(huì)帶入命令行中傳入的環(huán)境變量等參數(shù),這樣可以更方便的對(duì)環(huán)境變量進(jìn)行配置;比如我們?cè)诖虬€上正式環(huán)境和線上開發(fā)環(huán)境可以通過env進(jìn)行區(qū)分:

??另外還可以導(dǎo)出為一個(gè)Promise,用于異步加載配置,比如可以動(dòng)態(tài)加載入口文件:



??正如在上面提到的,入口是整個(gè)依賴關(guān)系的起點(diǎn)入口;我們常用的單入口配置是一個(gè)頁(yè)面的入口:

??它是下面的簡(jiǎn)寫:

??但是我們一個(gè)頁(yè)面可能不止一個(gè)模塊,因此需要將多個(gè)依賴文件一起注入,這時(shí)就需要用到數(shù)組了,代碼在demo2中:

??有時(shí)候我們一個(gè)項(xiàng)目可能有不止一個(gè)頁(yè)面,需要將多個(gè)頁(yè)面分開打包,entry支持傳入對(duì)象的形式,代碼在demo3中:

??這樣webpack就會(huì)構(gòu)建三個(gè)不同的依賴關(guān)系。


??output選項(xiàng)用來控制webpack如何輸入編譯后的文件模塊;雖然可以有多個(gè)entry,但是只能配置一個(gè)output:

??這里我們配置了一個(gè)單入口,輸出也就是bundle.js;但是如果存在多入口的模式就行不通了,webpack會(huì)提示Conflict: Multiple chunks emit assets to the same filename,即多個(gè)文件資源有相同的文件名稱;webpack提供了占位符來確保每一個(gè)輸出的文件都有唯一的名稱:

??這樣webpack打包出來的文件就會(huì)按照入口文件的名稱來進(jìn)行分別打包生成三個(gè)不同的bundle文件;還有以下不同的占位符字符串:

??在這里引入Module、Chunk和Bundle的概念,上面代碼中也經(jīng)常會(huì)看到有這兩個(gè)名詞的出現(xiàn),那么他們?nèi)叩降子惺裁磪^(qū)別呢?首先我們發(fā)現(xiàn)module是經(jīng)常出現(xiàn)在我們的代碼中,比如module.exports;而Chunk經(jīng)常和entry一起出現(xiàn),Bundle總是和output一起出現(xiàn)。

module:我們寫的源碼,無論是commonjs還是amdjs,都可以理解為一個(gè)個(gè)的module

chunk:當(dāng)我們寫的module源文件傳到webpack進(jìn)行打包時(shí),webpack會(huì)根據(jù)文件引用關(guān)系生成chunk文件,webpack 會(huì)對(duì)這些chunk文件進(jìn)行一些操作

bundle:webpack處理好chunk文件后,最后會(huì)輸出bundle文件,這個(gè)bundle文件包含了經(jīng)過加載和編譯的最終源文件,所以它可以直接在瀏覽器中運(yùn)行。
??我們通過下面一張圖更深入的理解這三個(gè)概念:

總結(jié):
??module,chunk 和 bundle 其實(shí)就是同一份邏輯代碼在不同轉(zhuǎn)換場(chǎng)景下的取了三個(gè)名字:我們直接寫出來的是module,webpack處理時(shí)是chunk,最后生成瀏覽器可以直接運(yùn)行的bundle。


??理解了chunk的概念,相信上面表中chunkhash和hash的區(qū)別也很容易理解了;

hash:是跟整個(gè)項(xiàng)目的構(gòu)建相關(guān),只要項(xiàng)目里有文件更改,整個(gè)項(xiàng)目構(gòu)建的hash值都會(huì)更改,并且全部文件都共用相同的hash值。

chunkhash:跟入口文件的構(gòu)建有關(guān),根據(jù)入口文件構(gòu)建對(duì)應(yīng)的chunk,生成每個(gè)chunk對(duì)應(yīng)的hash;入口文件更改,對(duì)應(yīng)chunk的hash值會(huì)更改。

contenthash:跟文件內(nèi)容本身相關(guān),根據(jù)文件內(nèi)容創(chuàng)建出唯一hash,也就是說文件內(nèi)容更改,hash就更改。


??在webpack2和webpack3中我們需要手動(dòng)加入插件來進(jìn)行代碼的壓縮、環(huán)境變量的定義,還需要注意環(huán)境的判斷,十分的繁瑣;在webpack4中直接提供了模式這一配置,開箱即可用;如果忽略配置,webpack還會(huì)發(fā)出警告。

??開發(fā)模式是告訴webpack,我現(xiàn)在是開發(fā)狀態(tài),也就是打包出來的內(nèi)容要對(duì)開發(fā)友好,便于代碼調(diào)試以及實(shí)現(xiàn)瀏覽器實(shí)時(shí)更新。

??生產(chǎn)模式不用對(duì)開發(fā)友好,只需要關(guān)注打包的性能和生成更小體積的bundle。看到這里用到了很多Plugin,不用慌,下面我們會(huì)一一解釋他們的作用。
??相信很多童鞋都曾有過疑問,為什么這邊DefinePlugin定義環(huán)境變量的時(shí)候要用JSON.stringify("production"),直接用"production"不是更簡(jiǎn)單嗎?
??我們首先來看下JSON.stringify("production")生成了什么;運(yùn)行結(jié)果是""production"",注意這里,并不是你眼睛花了或者屏幕上有小黑點(diǎn),結(jié)果確實(shí)比"production"多嵌套了一層引號(hào)。
??我們可以簡(jiǎn)單的把DefinePlugin這個(gè)插件理解為將代碼里的所有process.env.NODE_ENV替換為字符串中的內(nèi)容。假如我們?cè)诖a中有如下判斷環(huán)境的代碼:

??這樣生成出來的代碼就會(huì)編譯成這樣:

??但是我們代碼中可能并沒有定義production變量,因此會(huì)導(dǎo)致代碼直接報(bào)錯(cuò),所以我們需要通過JSON.stringify來包裹一層:

??這樣編譯出來的代碼就沒有問題了。


??在上面的代碼中我們發(fā)現(xiàn)都是手動(dòng)來生成index.html,然后引入打包后的bundle文件,但是這樣太過繁瑣,而且如果生成的bundle文件引入了hash值,每次生成的文件名稱不一樣,因此我們需要一個(gè)自動(dòng)生成html的插件;首先我們需要安裝這個(gè)插件:
npm install --save-dev html-webpack-plugin
??在demo3中,我們生成了三個(gè)不同的bundle.js,我們希望在三個(gè)不同的頁(yè)面能分別引入這三個(gè)文件,如下修改config文件:

??我們以index.html作為模板文件,生成home、list、detail三個(gè)不同的頁(yè)面,并且通過chunks分別引入不同的bundle;如果這里不寫chunks,每個(gè)頁(yè)面就會(huì)引入所有生成出來的bundle。
??html-webpack-plugin還支持以下字段:

??上面設(shè)置title后需要在模板文件中設(shè)置模板字符串:
loader
??loader用于對(duì)模塊module的源碼進(jìn)行轉(zhuǎn)換,默認(rèn)webpack只能識(shí)別commonjs代碼,但是我們?cè)诖a中會(huì)引入比如vue、ts、less等文件,webpack就處理不過來了;loader拓展了webpack處理多種文件類型的能力,將這些文件轉(zhuǎn)換成瀏覽器能夠渲染的js、css。
??module.rules允許我們配置多個(gè)loader,能夠很清晰的看出當(dāng)前文件類型應(yīng)用了哪些loader,loader的代碼均在demo4中。

??我們可以看到rules屬性值是一個(gè)數(shù)組,每個(gè)數(shù)組對(duì)象表示了不同的匹配規(guī)則;test屬性時(shí)一個(gè)正則表達(dá)式,匹配不同的文件后綴;use表示匹配了這個(gè)文件后調(diào)用什么loader來處理,當(dāng)有多個(gè)loader的時(shí)候,use就需要用到數(shù)組。
??多個(gè)loader支持鏈?zhǔn)絺鬟f,能夠?qū)Y源進(jìn)行流水線處理,上一個(gè)loader處理的返回值傳遞給下一個(gè)loader;loader處理有一個(gè)優(yōu)先級(jí),從右到左,從下到上;在上面demo中對(duì)css的處理就遵從了這個(gè)優(yōu)先級(jí),css-loader先處理,處理好了再給style-loader;因此我們寫loader的時(shí)候也要注意前后順序。


??css-loader和style-loader從名稱看起來功能很相似,然而兩者的功能有著很大的區(qū)別,但是他們經(jīng)常會(huì)成對(duì)使用;安裝方法:
npm i -D css-loader style-loader
??css-loader用來解釋@import和url();style-loader用來將css-loader生成的樣式表通過
