一文徹底解決新手對webpack的恐懼!
作者:KDDA_ (已獲轉(zhuǎn)載授權(quán))
原文:https://juejin.cn/post/6953042611963691021
網(wǎng)上太多文章并不適合新手學(xué)習(xí),甚至?xí)屗麄儗ebpack、對一些插件產(chǎn)生誤解,所以我決定要寫一篇能讓新手不在害怕webpack的文章
前言、你必須知道的知識 是非常重要的,務(wù)必認真閱讀!
前言
首先拋出結(jié)論,webpack是一個非常簡單的工具,毫無難度可言。webpack阻礙很多人的根本原因是大家對一些概念的不熟悉、對webpack豐富的loader和plugin望而怯步。當你把概念弄清楚后,你自然而然的就知道自己需要什么loader和plugin了。
看完后你一定會說:“就這?webpack就這?”。
個人建議:例如create-react-app就是一個很好的腳手架,你可以寫不出這樣的腳手架,但你一定要能看懂,在你有需要的時候有能力去修改源碼。webpack掌握到這個程度很多時候就完全夠用了。在最后我還會補充如何自己寫一個loader和plugin(不過我相信不用到最后,認真閱讀的你就已經(jīng)知道要如何編寫了)
準備環(huán)境、版本
node: ^12.16.1
webpack: ^4.44.2
webpack-cli: ^3.3.12
webpack5升級了一些新特性,所以我們這篇文章以目前常用的webpack4來講解。不過完全不用擔心都出5了怎么還學(xué)4的問題,道理都是相通的,4都會了,5它不就是多點新特性了嗎。
你必須知道的知識
webpack概念
webpack是一個現(xiàn)代JavaScript應(yīng)用程序的靜態(tài)模塊打包器。如果你接觸js夠早的話,你一定知道最初的工程可沒有這樣的打包工具,一個文件就相當于一個模塊,最終這些文件需要按照一定的順序使用script標簽引入html(因為如果順序不對就會導(dǎo)致依賴變量丟失等錯誤問題)。但是這個寫項目不僅麻煩而且不優(yōu)雅,隨著node.js的出現(xiàn)和發(fā)展,才出現(xiàn)了這類基于node.js運行的打包工具(gulp、grunt,以及現(xiàn)在最流行的webpack),因為node.js擁有可對文件操作的能力。所以webpack本質(zhì)就是為我們打包js的引用,而我們常聽到各種loader、各種plugin、熱更新、熱模塊替換等等都是webpack的一個升華,使得webpack能為我們提供更多的幫助。
loader:webpack本身只能打包js和json格式的文件,但實際項目中我們還有會css、scss、png、ts等其他文件,這時我們就需要使用loader來讓它正確打包。
style-loader
css-loader
sass-loader
ts-loader
file-loader
babel-loader
postcss-loader
...總結(jié):loader是處理編譯js和json以外的文件時用的 常見的loader: plugin:plugin可以在webpack運行到某個階段時候,幫你做一些事情,類似react/vue中的生命周期。具體的某個插件(plugin)就是在webpack構(gòu)建過程中的特定時機注入擴展邏輯來改變構(gòu)建結(jié)果,作用于整個構(gòu)建過程。
弄明白這些東西
postcss:postcss是一個用 JavaScript 工具和插件轉(zhuǎn)換 CSS 代碼的工具。你可以把他理解成babel,他本身作用不大,我們很多具體需求的實現(xiàn)都需要他的插件來完成,他本身更像一個平臺。例如,項目中我們需要webpack自動的幫我們?yōu)閏ss樣式加上兼容性前綴,實際幫我們加上前綴的插件是`autoprefixer`[1],但他能為我們加前綴的前提又是我們有postcss。千萬不要把postcss誤解postcss成scss、less替代品 對于“ PostCSS”一詞,我們可以替代地指兩件事: 該工具本身就是PostCSS[2](運行時會得到什么) npm install postcss,以及該工具支持的PostCSS插件生態(tài)系統(tǒng)[3] babel:babel 是一個 JavaScript 編譯器,他可以讓我們不再考慮兼容性,盡情的使用下一代的 JavaScript 語法編程。但是要實現(xiàn)具體的語法轉(zhuǎn)換,我們還是要使用他的插件[4]才能實現(xiàn) 在babel7后為我們提供了預(yù)設(shè)[5],可以讓我們不再自己麻煩的對插件進行組合,想在什么環(huán)境運行就寫什么預(yù)設(shè)即可(相當于每個預(yù)設(shè)選項中都幫你組合好了這個環(huán)境中需要用到的插件) es6+語法:babel默認會轉(zhuǎn)換語法,例如:let、const、() => {}、class es6+特性:babel不會轉(zhuǎn)換特性,特性需要js代碼墊片來兼容低版本的瀏覽器。例如:Iterator、Generator、Set、Map、Proxy、Reflect、Symbol、Promise @babel/core:@babel/core是babel的核心庫,所有的核心api都在這個庫里,這些api可供babel-loader調(diào)用 @babel/preset-env:這是一個預(yù)設(shè)的插件集合,包含了一組相關(guān)的插件,Bable中是通過各種插件來指導(dǎo)如何進行代碼轉(zhuǎn)換。該插件包含所有es6轉(zhuǎn)化為es5的翻譯規(guī)則 @babel/polyfill:@babel/preset-env只是提供了語法轉(zhuǎn)換的規(guī)則,但是它并不能彌補瀏覽器缺失的一些新的功能,如一些內(nèi)置的方法和對象,如Promise,Array.from等,此時就需要polyfill來做js的墊片,彌補低版本瀏覽器缺失的這些新功能。注意:Babel 7.4.0該包將被廢棄 core-js:它是JavaScript標準庫的polyfill,而且它可以實現(xiàn)按需加載。使用@babel/preset-env的時候可以配置core-js的版本和core-js的引入方式。注意:@babel/polyfill依賴core-js regenerator-runtime:提供generator函數(shù)的轉(zhuǎn)碼
補充知識點(重要)
browserslist:browserslist實際上就是聲明了一段瀏覽器的合集,我們的工具可以根據(jù)這個合集描述,針對性的輸出兼容性代碼,browserslist應(yīng)用于babel、postcss等工具當中。
“> 1%”表示兼容市面上使用量大于百分之一的瀏覽,“l(fā)ast 1 chrome version”表示兼容到谷歌的上一個版本,具體的可以使用命令
npx browserslist "> 1%"的方式查看都包含了哪些瀏覽器,browserslist的github地址[6]browserslist可以在package.json文件配置,也可以單出寫一個.browserslistrc文件進行配置
工具會自動查找.browserslistrc中的配置,如果沒有發(fā)現(xiàn).browserslistrc文件,則會去package.json中查找
例子:
//?在.browserslistrc中的寫法
>?1%
last?2?versions
//?還可以配置不同環(huán)境下的規(guī)則(在.browserslistrc中)
[production]
>?1%
ie?10
[development]
last?1?chrome?version
last?1?firefox?version
//?在package.json中的寫法
{
??"browserslist":?[">?1%",?"last?2?versions"]
}
//?還可以配置不同環(huán)境下的規(guī)則(在package.json中)
//?production和development取決你webpack中mode字段的配置
{
??"browserslist":?{
??"production":?[
???">0.2%",
???"not?dead",
???"not?op_mini?all"
??],
??"development":?[
???"last?1?chrome?version",
???"last?1?firefox?version",
???"last?1?safari?version"
??]
?}
}chunk:它不是庫也不是插件,它就是一個名詞,顧名思義就是代碼塊。為什么要單獨把他拎出來說呢,因為你明白它以后你自然就能理解webpack中配置一些參數(shù)的意思了
chunks:一個chunks至少包含一個chunk,chunks是多個chunk的合集
//?index.js
import?{?a?}?from?'a.js'
console.log('我是index文件')
//a.js
const?a?=?'我是a文件'
export?{?a?}上面代碼來說,a.js就是chunk,而index.js就是chunks
在webpack構(gòu)建中入口是chunks,出口是chunk(知道這個概念就行)
搭建webpack項目
初始化項目
新建一個文件夾
mkdir?learnWebpack進入到文件夾中
cd?learnWebpack/初始化package.json文件
#?可以輸入配置
npm?init
#?默認配置創(chuàng)建
npm?init?-y
#?是用npm?init?-y后得到的內(nèi)容
{
??"name":?"learnwebpack",
??"version":?"1.0.0",
??"description":?"",
??"main":?"index.js",
??"scripts":?{
????"test":?"echo?\"Error:?no?test?specified\"?&&?exit?1"
??},
??"author":?"",
??"license":?"ISC"
}
安裝
安裝webpack、webpack-cli依賴
#?我更喜歡yarn,當然你也可以使用npm
[email protected][email protected]?-D
創(chuàng)建目錄
根目錄下創(chuàng)建src、src下創(chuàng)建index.js
在index.js中添加一段代碼:
console.log('hello?webpack')在package.json文件中的scripts字段中添加
"start": "webpack"??"scripts":?{
????"test":?"echo?\"Error:?no?test?specified\"?&&?exit?1",
????"start":?"webpack"
??}在命令行中執(zhí)行
yarn start,我們會發(fā)現(xiàn)編譯成功,但有個警告!在webpack自動在根目錄下創(chuàng)建了一個dist文件夾和dist文件夾中的main.js文件關(guān)于警告:ARNING in configuration The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment. You can also set it to 'none' to disable any default behavior. Learn more: webpack.js.org/configurati…[7]; 警告中已經(jīng)說的很清楚了,我們沒有設(shè)置mode字段。我們只需在webpack的配置中配置mode字段即可消除該警告
關(guān)于dist文件夾和main.js文件 :這是webpack4當初宣傳的零配置使用。很顯然我們這里什么都沒配置,就幫我們成功打包了一個src下的代碼。該功能實際上是wabpack默認幫我們配置了一套簡單的打包配置,讓我們看看webpack默認為我們配置了什么:
const?path?=?require('path')
module.exports?=?{
??extry:?'./src/index.js',
??output:?{
????filename:?'main.js',
????path:?path.resolve(__dirname,?'./dist')
??}
}注意:這份默認配置是不是太過簡單了??戳诉@個配置后,我想你也知道為什么當我們創(chuàng)建的不是src或者src下不是index.js時候無法使用零配置功能的原因了
webpack的零配置
上面我們看到了,零配置很弱,真正的項目中他完全不可能滿足我們的需求
我們想要自定義配置webpack的話,需要在根目錄上創(chuàng)建一個webpack.config.js的文件,這個文件的內(nèi)容可以覆蓋webpack的零配置
使用默認的配置文件:webpack.config.js
#?使用webpack.config.js配置文件時,輸入該命令即可啟動webpack打包
webpack使用其他配置文件:如yzyConfig.js,可以通過
--config yzyConfig.js來指定webpack使用哪個配置文件來執(zhí)行構(gòu)建#?通過--config來指定其他配置文件,并按照指定的配置文件的配置內(nèi)容進行打包
webpack?--config?yzyConfig.js
webpack配置核心概念
chunk:指代碼塊,一個chunk可能由多個模塊組合而成,也用于代碼合并與分割(這里的合并分割主要指指紋策略的判斷),指紋策略簡單來說就是文件名后的hash bundle:資源經(jīng)過webpack流程解析編譯后最終輸出的成果文件(一個.js格式的文件,也就是我們的output文件) entry:文件打包的入口,webpack會根據(jù)entry遞歸的去尋找依賴,每個依賴都將被它處理,最后打包到集合文件中 output:配置打包輸出的位置、文件名等 loader:默認情況下,webpack僅支持js和json文件,通過loader,可以讓它解析其他類型的文件。理論上只要有相應(yīng)的loader,webpack可以處理任何類型的文件 plugin:loader主要的職責是讓webpack認識更多的文件類型,而plugin的職責則是讓其可以控制構(gòu)建流程,從而執(zhí)行一些特殊的任務(wù)。插件的功能非常強大,可以完成各種各樣的任務(wù) mode:目標環(huán)境,不用的目標環(huán)境會影響webpack打包時的決策 production:碼進行壓縮等一系列優(yōu)化操作 development:有利于熱更新的處理,識別哪個模塊變化代 none:什么都不做,打包時會有提示警告
配置webpack.config.js
項目搭建目標
咱們目標不是搭建一個完整全面的項目工程,目標是以一些有代表性的功能作為切入點學(xué)習(xí)webpack,我相信這些你認真看過后一定能做到舉一反三
實現(xiàn)加載css
實現(xiàn)css效果展示
實現(xiàn)css前綴自動補充
實現(xiàn)css以文件形式導(dǎo)出
實現(xiàn)自動生成html文件
實現(xiàn)打包清空dist文件夾
實現(xiàn)圖片在js文件中引入
實現(xiàn)圖片在css文件中引入
實現(xiàn)webpack本地服務(wù)
實現(xiàn)多頁面打包
實現(xiàn)加載css
我們已經(jīng)知道.css文件無法正常被webpack打包進bundle(bundle的解釋可查看“webpack配置核心概念”部分)文件,所以我們需要一個loader作為加載器將它正確打包進bundle文件
文件內(nèi)容

安裝css-loader
?yarn?add?css-loader?-D
配置css-loader
const?path?=?require('path')
module.exports?=?{
??entry:?'./src/index.js',
??output:?{
????filename:?'main.js',
????path:?path.resolve(__dirname,?'./dist')
??},
??mode:?"development",
??module:?{
????rules:?[
??????{
????????test:?/\.css$/,
????????use:?"css-loader"
??????}
????]
??}
}
運行webpack命令,檢查一下dist/main.js中是否包含了css文件中的內(nèi)容

我們發(fā)現(xiàn)css文件的內(nèi)容被成功打包了,這時如果你在dist文件夾下創(chuàng)建了一個html頁面給div元素加上了box類,并引入main.js文件,你會發(fā)現(xiàn)完全看不到樣式效果。
因為此時css中的內(nèi)容只是被作為一段字符串引入了js中(相當于對css文件的內(nèi)容進行了JSON.stringify),所以你自然是看不到效果的。
想要看到效果要怎么辦?當然是將css內(nèi)容放進style標簽啦!不過這步不需要我們做,因為我們有style-loader為我們做這件事情!
實現(xiàn)css效果展示
安裝style-loader
yarn?add?style-loader?-D
配置style-loader
const?path?=?require('path')
module.exports?=?{
??entry:?'./src/index.js',
??output:?{
????filename:?'main.js',
????path:?path.resolve(__dirname,?'./dist')
??},
??mode:?'development',
??module:?{
????rules:?[
??????{
????????test:?/\.css$/,
????????use:?['style-loader',?'css-loader']
??????}
????]
??}
}
這里需要注意的是,對同一種類型文件使用多個 loader的時候,use屬性接收一個數(shù)組,并且從右向左執(zhí)行。所以style-loader要寫在css-loader前面
運行webpack命令,看一下結(jié)果

成功!
但是我們知道,css3在瀏覽器中會存在兼容性問題,我們可以通過給屬性加上前綴來解決該問題。前端豐富的生態(tài)當然不會讓你自己傻傻的做這件事情,我們可以通過autoprefixer這個插件幫我們完成
實現(xiàn)css前綴自動補充
已經(jīng)知道autoprefixer是postcss工具的插件,所以我們需要安裝postcss和postcss-loader
安裝postcss、postcss-loader、autoprefixer,這里postcss-loader需要指定4.x的版本,因為4.x的版本和webpack4會存在報錯問題
[email protected]?postcss?-D
配置postcss-loader和插件autoprefixer
const?path?=?require('path')
module.exports?=?{
??entry:?'./src/index.js',
??output:?{
????filename:?'main.js',
????path:?path.resolve(__dirname,?'./dist')
??},
??mode:?'development',
??module:?{
????rules:?[
??????{
????????test:?/\.css$/,
????????use:?[
??????????'style-loader',
??????????'css-loader',
??????????{
????????????loader:?'postcss-loader',
????????????options:?{
??????????????postcssOptions:?{
????????????????plugins:?[require('autoprefixer')]
??????????????}
????????????}
??????????}
????????]
??????}
????]
??}
}
當loader需要寫配置的時候,我們可以把loader寫成一個對象,loader屬性就是要使用的loader名稱,options屬性就是這個loader的配置對象。autoprefixer是postcss的插件,所以autoprefixer的使用自然就要寫在postcss-loader的配置中了
因為postcss有自己的配置文件,所以這里還可以寫成這樣:
//?webpack.config.js
const?path?=?require('path')
module.exports?=?{
??entry:?'./src/index.js',
??output:?{
????filename:?'main.js',
????path:?path.resolve(__dirname,?'./dist')
??},
??mode:?'development',
??module:?{
????rules:?[
??????{
????????test:?/\.css$/,
????????use:?['style-loader',?'css-loader',?'postcss-loader']
??????}
????]
??}
}
//?根目錄下新建postcss.config.js文件
module.exports?=?{
??plugins:?[require('autoprefixer')],
}
這里我們需要配置一下browserslist,否則插件不知道按照什么樣的規(guī)則進行前綴補全
//?在package.json文件中添加
//?這里的意思表示目標瀏覽器為ie瀏覽器,并需要兼容到ie8以上
"browserslist":?["ie?>?8"]
運行webpack命令看一下結(jié)果

成功!
實現(xiàn)css以文件形式導(dǎo)出
隨著項目的增大,我們不想把那么多的樣式都放在style標簽中,我們想用link標簽引入,這時我們就需要使用mini-css-extract-plugin[8]
安裝mini-css-extract-plugin
yarn?add?mini-css-extract-plugin?-D
配置mini-css-extract-plugin插件和它的loader,這時我們不需要style-loader了,我們要style-loader替換成MiniCssExtractPlugin.loader
const?path?=?require('path')
const?MiniCssExtractPlugin?=?require('mini-css-extract-plugin')
module.exports?=?{
??entry:?'./src/index.js',
??output:?{
????filename:?'main.js',
????path:?path.resolve(__dirname,?'./dist')
??},
??mode:?'development',
??module:?{
????rules:?[
??????{
????????test:?/\.css$/,
????????use:?[
??????????MiniCssExtractPlugin.loader,
??????????'css-loader',
??????????'postcss-loader'
????????]
??????}
????]
??},
??plugins:?[
????new?MiniCssExtractPlugin({
??????filename:?"css/[name].css"
????})
??]
}
MiniCssExtractPlugin可以配置輸出文件名 [name]為占位符,引入的時候是什么名字,導(dǎo)出的時候就是什么名字 css/表示導(dǎo)出到css文件夾下
運行webpack命令看一下結(jié)果

成功!
實現(xiàn)自動生成html文件
我們發(fā)現(xiàn)dist下的html是我們自己手動創(chuàng)建的,這顯然不夠優(yōu)雅。html-webpack-plugin[9]幫你解決!
安裝html-webpack-plugin,這里也要制定一下4.x的版本
[email protected]?-D
配置html-webpack-plugin
const?path?=?require('path')
const?MiniCssExtractPlugin?=?require('mini-css-extract-plugin')
const?HtmlWebpackPlugin?=?require('html-webpack-plugin')
module.exports?=?{
??entry:?'./src/index.js',
??output:?{
????filename:?'main.js',
????path:?path.resolve(__dirname,?'./dist')
??},
??mode:?'development',
??module:?{
????rules:?[
??????{
????????test:?/\.css$/,
????????use:?[
??????????MiniCssExtractPlugin.loader,
??????????'css-loader',
??????????'postcss-loader'
????????]
??????}
????]
??},
??plugins:?[
????new?MiniCssExtractPlugin({
??????filename:?"css/[name].css",
????}),
????new?HtmlWebpackPlugin({
??????template:?'./src/index.html'
????})
??]
}
運行webpack命令看一下結(jié)果

成功!很顯然HtmlWebpackPlugin根據(jù)我們的模版為我們生成了新的html頁面,并自動引入了dist包下的依賴。查看更多HtmlWebpackPlugin配置[10]
實現(xiàn)打包清空dist文件夾
我們會發(fā)現(xiàn)每次打包dist文件夾的內(nèi)容會被覆蓋,但是如果下次打包出來的文件名不同,那舊的打包文件還會存在,這是我們不想要的。clean-webpack-plugin[11]來幫我們解決這個問題
安裝clean-webpack-plugin
yarn?add?clean-webpack-plugin?-D
配置clean-webpack-plugin
const?path?=?require('path')
const?MiniCssExtractPlugin?=?require('mini-css-extract-plugin')
const?HtmlWebpackPlugin?=?require('html-webpack-plugin')
const?{?CleanWebpackPlugin?}?=?require('clean-webpack-plugin')
module.exports?=?{
??entry:?'./src/index.js',
??output:?{
????filename:?'main.js',
????path:?path.resolve(__dirname,?'./dist')
??},
??mode:?'development',
??module:?{
????rules:?[
??????{
????????test:?/\.css$/,
????????use:?[
??????????MiniCssExtractPlugin.loader,
??????????'css-loader',
??????????'postcss-loader'
????????]
??????}
????]
??},
??plugins:?[
????new?MiniCssExtractPlugin({
??????filename:?"css/[name].css",
????}),
????new?HtmlWebpackPlugin({
??????template:?'./src/index.html'
????}),
????new?CleanWebpackPlugin()
??]
}
這時,你也在dist文件夾下隨意建一個其他文件,運行webpack命令看一下結(jié)果,你會發(fā)現(xiàn)你隨意建的文件不在了。驗證了這一點,就說明你成功了
實現(xiàn)圖片在js文件中引入
實現(xiàn)這個功能我們使用url-loader,當然你也可以使用file-loader。url-loader是file-loader的升級版,它內(nèi)部也依賴了file-loader。file-loader和url-loader在webpack5后都被廢棄了,使用asset modules代替
安裝url-loader和file-loader
yarn?add?url-loader?file-loader?-D
你可以會疑問為什么要裝file-loader,因為url-loader依賴file-loader。若不裝,當url-loader將圖片轉(zhuǎn)換為base64導(dǎo)入bundle時不會存在問題,但直接輸出圖片到dist文件夾下就會報錯,告訴你缺少file-loader
配置url-loader
const?path?=?require('path')
const?MiniCssExtractPlugin?=?require('mini-css-extract-plugin')
const?HtmlWebpackPlugin?=?require('html-webpack-plugin')
const?{?CleanWebpackPlugin?}?=?require('clean-webpack-plugin')
module.exports?=?{
??entry:?'./src/index.js',
??output:?{
????filename:?'main.js',
????path:?path.resolve(__dirname,?'./dist')
??},
??mode:?'development',
??module:?{
????rules:?[
??????{
????????test:?/\.css$/,
????????use:?[
??????????MiniCssExtractPlugin.loader,
??????????'css-loader',
??????????'postcss-loader'
????????]
??????},
??????{
????????test:?/\.(png|jpe?g|gif)$/,
????????use:?{
??????????loader:?'url-loader',
??????????options:?{
????????????name:?'[name].[ext]',
????????????limit:?1024?*?3
??????????}
????????}
??????}
????]
??},
??plugins:?[
????new?MiniCssExtractPlugin({
??????filename:?"css/[name].css",
????}),
????new?HtmlWebpackPlugin({
??????template:?'./src/index.html'
????}),
????new?CleanWebpackPlugin()
??]
}
[ext]表示導(dǎo)入時是什么格式,導(dǎo)出時就是什么格式 limit表示圖片大小的閾值,超過閾值的圖片會被原封不動的放置到打包文件夾下(file-loader做的這件事情,在js中引入時會幫你生成一個地址,即打包后對應(yīng)的訪問地址),沒有超過會處理成base64放在bundle文件中
在入口文件中引入圖片
//?index.js
import?'./index.css'
import?mk85?from?'./assets/images/mk85.jpeg'
console.log(mk85)?//?mk85.jpeg
const?img?=?document.createElement('img')
img.src?=?mk85
const?BoxDiv?=?document.getElementsByClassName('box')
BoxDiv[0].appendChild(img)
運行webpack命令看一下結(jié)果

實現(xiàn)圖片在css文件中引入
在css中引入圖片我們依舊使用url-loader,但需要對配置稍微進行修改
css代碼
.box?{
??width:?100px;
??height:?100px;
??/*?background-color:?yellowgreen;?*/
??background-image:?url('./assets/images/mk85.jpeg');
??display:?flex;
}
直接引用并打包,打包成功!打開html頁面,發(fā)現(xiàn)看不到圖片,因為地址不對。打包后mk85圖片在dist文件夾下,而index.css的引用路徑依舊是mk85.jpeg,可index.css是在css文件夾下的,所以自然是無法引用到。那如何才能引用到呢?最簡單的方法就是加上/,但這里有坑(其實也不是坑,這是一個關(guān)于/images、./images、image三者有什么不同的知識點)。補充:create-react-app也是通過/ 實現(xiàn)引用統(tǒng)一的

修改url-loader配置
const?path?=?require('path')
const?MiniCssExtractPlugin?=?require('mini-css-extract-plugin')
const?HtmlWebpackPlugin?=?require('html-webpack-plugin')
const?{?CleanWebpackPlugin?}?=?require('clean-webpack-plugin')
module.exports?=?{
??entry:?'./src/index.js',
??output:?{
????filename:?'main.js',
????path:?path.resolve(__dirname,?'./dist')
??},
??mode:?'development',
??module:?{
????rules:?[
??????{
????????test:?/\.css$/,
????????use:?[
??????????MiniCssExtractPlugin.loader,
??????????'css-loader',
??????????'postcss-loader'
????????]
??????},
??????{
????????test:?/\.(png|jpe?g|gif)$/,
????????use:?{
??????????loader:?'url-loader',
??????????options:?{
????????????name:?'[name].[ext]',
????????????limit:?1024?*?3,
????????????outputPath:?"images/",
????????????publicPath:?"/images",
??????????}
????????}
??????}
????]
??},
??plugins:?[
????new?MiniCssExtractPlugin({
??????filename:?"css/[name].css",
????}),
????new?HtmlWebpackPlugin({
??????template:?'./src/index.html'
????}),
????new?CleanWebpackPlugin()
??]
}
outputPath表示輸出的到哪里(file-loader提供的)
name: images/[name].[ext]這樣寫和用outputPath設(shè)置效果一樣嗎?在配合publicPath字段時不一樣。所以當你不需要配置publicPath字段時,可以通過name設(shè)置輸出路徑(file-loader提供的)options:?{
??name:?'[name].[ext]',
??limit:?1024?*?3,
??outputPath:?"images/",
??publicPath:?"/images",
}
//?等價于
options:?{
??name:?'images/[name].[ext]',
??limit:?1024?*?3,
??publicPath:?"/",
}publicPath表示資源引用的路徑
運行webpack命令看一下結(jié)果

成功了!是我們想要的結(jié)果,不過問題又來了,當你打開html頁面時發(fā)現(xiàn)并不能看到圖片正常顯示,這里就牽扯到關(guān)于/images、./images、image三者有什么不同的知識點
簡單來說,如果我起了服務(wù),我的實際路徑就是“l(fā)ocalhost:8080/images/mk85.jpeg”,如果沒有起服務(wù)那就是“/images/mk85.jpeg”
所以讓我們開啟一個服務(wù)吧!
實現(xiàn)webpack本地服務(wù)
安裝webpack-dev-server
yarn?add?webpack-dev-server?-D
配置url-loader
const?path?=?require('path')
const?MiniCssExtractPlugin?=?require('mini-css-extract-plugin')
const?HtmlWebpackPlugin?=?require('html-webpack-plugin')
const?{?CleanWebpackPlugin?}?=?require('clean-webpack-plugin')
module.exports?=?{
??entry:?'./src/index.js',
??output:?{
????filename:?'main.js',
????path:?path.resolve(__dirname,?'./dist')
??},
??mode:?'development',
??module:?{
????rules:?[
??????{
????????test:?/\.css$/,
????????use:?[
??????????MiniCssExtractPlugin.loader,
??????????'css-loader',
??????????'postcss-loader'
????????]
??????},
??????{
????????test:?/\.(png|jpe?g|gif)$/,
????????use:?{
??????????loader:?'url-loader',
??????????options:?{
????????????name:?'[name].[ext]',
????????????limit:?1024?*?3,
????????????outputPath:?"images/",
????????????publicPath:?"/images",
??????????}
????????}
??????}
????]
??},
??devServer:?{
????open:?true,
????port:?8080,
??},
??plugins:?[
????new?MiniCssExtractPlugin({
??????filename:?"css/[name].css",
????}),
????new?HtmlWebpackPlugin({
??????template:?'./src/index.html'
????}),
????new?CleanWebpackPlugin()
??]
}
只需要加上devServer配置即可 open表示打開瀏覽器 port表示服務(wù)的端口號
注意:這時就不是使用webpack命令來啟動項目了,需使用webpack-dev-server來啟動
實現(xiàn)多頁面打包
顧名思義,多頁面自然是有多個html頁面,每個html頁面都有自己的js文件,那么,有多少個入口就要有多少個出口
我們首先要設(shè)置一下目錄形式,以適應(yīng)多頁面打包的形式(以下形式不是唯一的,但有助于大家的理解)

這里不需要src/index.js 新建一個webpack.multiple.config.js 新建src/pages/login/js/index.js 新建src/pages/main/js/index.js
安裝glob,用于處理文件
yarn?add?glob?-D
配置webpack.multiple.config.js
module.exports?=?{
??entry:?{
????login:?'./src/pages/login/js/index.js',
????main:?'./src/pages/main/js/index.js'
??},
??output:?{
????filename:?'[name].js',
????path:?path.resolve(__dirname,?'./dist')
??},
??plugins:?[
????new?HtmlWebpackPlugin({
??????template:?'./src/index.html',
??????filename:?'login.html',
??????chunks:?['login']?//?chunks的名字對應(yīng)entry中的名字
????}),
????new?HtmlWebpackPlugin({
??????template:?'./src/index.html',
??????filename:?'main.html',
??????chunks:?['main']
????})
??]
}
這樣就完成了!你可以使用webpack --config ./`webpack.multiple.config.js 命令運行一下。結(jié)果會如你所愿的
但是,這時你肯定會想難道我每寫一個頁面就重新配置一次嗎?這也太麻煩了,也不優(yōu)雅!那我們現(xiàn)在解決一下這個問題吧,直接上代碼
//?我們寫一個方法自動做我們上面配置的事情
const?glob?=?require("glob")
const?setMpa?=?()?=>?{
??const?entry?=?{}
??const?htmlwebpackplugins?=?[]
?//?通過glob庫拿到我們的入口文件數(shù)組
??const?entryFiles?=?glob.sync(path.resolve(__dirname,?"./src/pages/*/*/index.js"))
?//?console.log(entryFiles)
??//?打印結(jié)果
??//?[
??//??'/Users/yzy/Desktop/learnSpace/learnWebpack/src/pages/login/js/index.js',
??//??'/Users/yzy/Desktop/learnSpace/learnWebpack/src/pages/main/js/index.js'
??//?]
??entryFiles.forEach((item)?=>?{
????const?entryFile?=?item
????const?match?=?entryFile.match(/src\/pages\/(.*)\/js\/index\.js$/)
????//?console.log(match)
????//?打印結(jié)果
????//?[
????//???'src/pages/login/js/index.js',
????//???'login',
????//???index:?43,
????//???input:?'/Users/yzy/Desktop/learnSpace/learnWebpack/src/pages/login/js/index.js',
????//???groups:?undefined
????//?]
????const?pageName?=?match[1]
????entry[pageName]?=?entryFile
????htmlwebpackplugins.push(
??????new?HtmlWebpackPlugin({
????????template:?`./src/index.html`,
????????filename:?`${pageName}.html`,
????????chunks:?[pageName]
??????})
????)
??})
??return?{
????entry,
????htmlwebpackplugins,
??}
}
有了這個方法以后,我們把它加到配置文件里
const?path?=?require('path')
const?MiniCssExtractPlugin?=?require('mini-css-extract-plugin')
const?HtmlWebpackPlugin?=?require('html-webpack-plugin')
const?{?CleanWebpackPlugin?}?=?require('clean-webpack-plugin')
const?glob?=?require("glob")
const?setMpa?=?()?=>?{
??const?entry?=?{}
??const?htmlwebpackplugins?=?[]
??const?entryFiles?=?glob.sync(path.resolve(__dirname,?"./src/pages/*/*/index.js"))
??entryFiles.forEach((item)?=>?{
????const?entryFile?=?item
????const?match?=?entryFile.match(/src\/pages\/(.*)\/js\/index\.js$/)
????const?pageName?=?match[1]
????entry[pageName]?=?entryFile
????htmlwebpackplugins.push(
??????new?HtmlWebpackPlugin({
????????template:?`./src/index.html`,
????????filename:?`${pageName}.html`,
????????chunks:?[pageName]
??????})
????)
??})
??return?{
????entry,
????htmlwebpackplugins,
??}
}
const?{?entry,?htmlwebpackplugins?}?=?setMpa()
module.exports?=?{
??entry,
??output:?{
????filename:?'[name].js',
????path:?path.resolve(__dirname,?'./dist')
??},
??mode:?'development',
??module:?{
????rules:?[
??????{
????????test:?/\.css$/,
????????use:?[
??????????'style-loader',
??????????'css-loader',
??????????'postcss-loader'
????????]
??????},
??????{
????????test:?/\.(png|jpe?g|gif)$/,
????????use:?{
??????????loader:?'url-loader',
??????????options:?{
????????????name:?'[name].[ext]',
????????????limit:?1024?*?3,
????????????outputPath:?"images/",
????????????publicPath:?"/images",
??????????}
????????}
??????}
????]
??},
??devServer:?{
????open:?true,
????port:?8080,
????hot:?true
??},
??plugins:?[
????new?MiniCssExtractPlugin({
??????filename:?"css/[name].css",
????}),
????new?CleanWebpackPlugin(),
????...htmlwebpackplugins
??]
}
我們再使用webpack --config ./`webpack.multiple.config.js 命令運行一下,成功!
小結(jié)
到這里就算是完成了一個簡單的webpack項目配置,看到這里先不要著急往下看,思考一下是否真的了解了loader和plugin,如果讓你寫一個loader和plugin,你有思路嗎
我想不出意外的話,你應(yīng)該是已經(jīng)有了思路。如果沒有也不用擔心,看看下面的內(nèi)容
實現(xiàn)一個loader
先上鏈接,官網(wǎng)編寫loader文檔[12]
首先loader是一個函數(shù),注意這里不能是箭頭函數(shù)
編寫一個替換字符串的loader
//?replaceLoader.js
module.exports?=?function?(soure)?{
??console.log(soure,?this)?//?這里可以自己打印看一下信息,內(nèi)容太長我就不放進來了
?return?source.replace('hello?webpack',?"你好呀,webpack!")
}
loader是一個函數(shù) 不能使用箭頭函數(shù),因為要用到上下文的this soure接收到的是待處理的文件源碼 return處理后的文件源碼,也是下一個loader接收到的文件源碼
使用loader
{
??test:?/\.js$/,
??use:?path.resolve(__dirname,?'./loader/replaceLoader.js')
}
運行webpack命令看一下結(jié)果

成功!是不是發(fā)現(xiàn)原來自定義loader這么簡單,感興趣可以自己嘗試寫一下css、png等其他文件的loader
實現(xiàn)一個plugin
線上鏈接,官網(wǎng)編寫plugin文檔[13]
同樣簡單,我們已經(jīng)用了很多次plugin了,發(fā)現(xiàn)是不是都需要new一下。很顯然,自定義loader是一個構(gòu)造函數(shù)。
我們看一下格式:
class?PluginName?{
?constructor?(options)?{
?}
?apply(compiler)?{
??...
?}
}
這里一定要寫apply方法,webpack會通過apply方法啟動插件 PluginName可以寫成普通function,apply必須掛載到原型對象上( PluginName.prototype)class類中的apply不能寫成箭頭函數(shù) webpack的compiler鉤子[14],查看鉤子決定你的插件要在哪一步做什么
編寫一個假的html-webpack-plugin,輸出一個fake.html文件
class?HtmlPlugin?{
?constructor?(options)?{
?}
?apply(compiler)?{
??compiler.hooks.emit.tap('HtmlPlugin',?(compolation)?=>?{
??????const?content?=?'fake?html'
??????compolation.assets['fake.html']?=?{
????????source:?function?()?{
??????????return?content
????????},
????????size:?function?()?{
??????????return?content.length
????????}
??????}
????})
?}
}
module.exports?=?HtmlPlugin
使用這個plugin
plugins:?[
??new?HtmlPlugin()
]
運行webpack命令看一下結(jié)果

成功!你也可以試著完善一下這個插件,加上配置,加上引入資源文件等
指紋策略
關(guān)于瀏覽器緩存
現(xiàn)代瀏覽器都會有緩存機制 當我們第一次訪問A網(wǎng)站時,這時加載了y.js的文件進行了緩存 當我們第二次訪問A網(wǎng)站時,瀏覽器發(fā)現(xiàn)緩存中已經(jīng)有y.js了 緩存中有y.js那就用緩存的文件 優(yōu)點:減少了資源的請求 缺點:當y.js的內(nèi)容更新了,若不通過強制刷新瀏覽器的話則無法獲取最新的y.js內(nèi)容 我們加上標識符就可以解決這個問題了 第一次訪問時加載了y.123.js 第二次訪問發(fā)現(xiàn)有緩存就用緩存中的y.123.js 這時服務(wù)器中的y文件內(nèi)容改變了,同時也修改了名字為y.111.js 第三次訪問發(fā)現(xiàn)沒有y.111.js文件,正確加載最新y.111.js
上訴都是比較簡單解釋,具體細節(jié)你可以不用知道,明白這個緩存機制的緩存方式即可
webpack中使用指紋策略
使用:
filename:?'[name].[hash].[ext]',
hash:以項目為單位,項目內(nèi)容改變了,則會生成新的hash,內(nèi)容不變則hash不變 整個工程任何一個需要被打包的文件發(fā)生了改變,打包結(jié)果中的所有文件的hash值都會改變 chunkhash:以chunk為單位,當一個文件內(nèi)容改變,則整個chunk組的模塊hash都會改變 假設(shè)打包出口有a.123.js和c.123.js a文件中引入了b文件 修改了b文件的內(nèi)容 重新的打包結(jié)果為a.111.js和c.123.js a的hash值會被影響,但是c的hash值不受影響 contenthash:以自身內(nèi)容為單位,依賴不算 假設(shè)打包出口有a.123js和b.123.css a文件引入了b文件 修改了b文件的內(nèi)容 重新打包結(jié)果為a.123.js和b.111.css a的hash值不受影響
熱模塊替換
你一定使用過這個功能,只是你不知道罷了!
場景:
啟動項目本地服務(wù) 修改了一個.css文件的內(nèi)容 瀏覽器沒有刷新,但是修改的內(nèi)容還是生效了
這就是熱模塊替換,提示:無論是css還是js都可以做熱模塊替換,但是個人建議只做css的熱模塊替換即可。因為js的熱模塊替換需要寫代碼進行替換,除非特定情況下,否則js的熱模塊替換用處不大。
我們來做一個css的熱模塊替換吧
注:熱模塊替換不支持抽取出的css文件,只能放在style中,所以需要style-loader
配置webpack.config.js
const?path?=?require('path')
const?MiniCssExtractPlugin?=?require('mini-css-extract-plugin')
const?HtmlWebpackPlugin?=?require('html-webpack-plugin')
const?{?CleanWebpackPlugin?}?=?require('clean-webpack-plugin')
const?HtmlPlugin?=?require('./plugin/htmlPlugin')
const?Webpack?=?require('webpack')
module.exports?=?{
??entry:?'./src/index.js',
??output:?{
????filename:?'main.js',
????path:?path.resolve(__dirname,?'./dist')
??},
??mode:?'development',
??module:?{
????rules:?[
??????{
????????test:?/\.js$/,
????????use:?path.resolve(__dirname,?'./loader/replaceLoader.js')
??????},
??????{
????????test:?/\.css$/,
????????use:?[
??????????//?MiniCssExtractPlugin.loader,
??????????'style-loader',
??????????'css-loader',
??????????'postcss-loader'
????????]
??????},
??????{
????????test:?/\.(png|jpe?g|gif)$/,
????????use:?{
??????????loader:?'url-loader',
??????????options:?{
????????????name:?'[name].[ext]',
????????????limit:?1024?*?3,
????????????outputPath:?"images/",
????????????publicPath:?"/images",
??????????}
????????}
??????}
????]
??},
??devServer:?{
????open:?true,
????port:?8080,
????hot:?true
??},
??plugins:?[
????new?MiniCssExtractPlugin({
??????filename:?"css/[name].css",
????}),
????new?HtmlWebpackPlugin({
??????template:?'./src/index.html'
????}),
????new?CleanWebpackPlugin(),
????new?HtmlPlugin(),
????new?Webpack.HotModuleReplacementPlugin()
??]
}
思考了一下,還是附了一份完整的配置文件(有點擔心這樣太占頁面位置了)
首先,將之前的MiniCssExtractPlugin.loader替換回style-loader 給devServer中加上 hot: true引入webpack: const Webpack = require('webpack')在plugin中添加: new Webpack.HotModuleReplacementPlugin()
運行webpack-dev-server命令看看效果吧!
webpack5都更新了什么
話不多說,直接上鏈接,webpack5更新的內(nèi)容[15]
提示:當你使用webpack4的時候,無論是裝plugin(插件)還是loader都盡量看看該插件現(xiàn)在都有什么版本,如果發(fā)現(xiàn)版本從4.x一下子跳到了5.x,那么一定要去安裝4.x的版本,否則在打包的時候會發(fā)生未知的錯誤
參考資料
https://github.com/postcss/autoprefixer: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fpostcss%2Fautoprefixer
[2]https://github.com/postcss/postcss: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fpostcss%2Fpostcss
[3]https://github.com/postcss/postcss#plugins: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fpostcss%2Fpostcss%23plugins
[4]https://www.babeljs.cn/docs/plugins: https://link.juejin.cn?target=https%3A%2F%2Fwww.babeljs.cn%2Fdocs%2Fplugins
[5]https://www.babeljs.cn/docs/presets: https://link.juejin.cn?target=https%3A%2F%2Fwww.babeljs.cn%2Fdocs%2Fpresets
[6]https://github.com/browserslist/browserslist: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fbrowserslist%2Fbrowserslist
[7]https://webpack.js.org/configuration/mode/: https://link.juejin.cn?target=https%3A%2F%2Fwebpack.js.org%2Fconfiguration%2Fmode%2F
[8]https://webpack.docschina.org/plugins/mini-css-extract-plugin/: https://link.juejin.cn?target=https%3A%2F%2Fwebpack.docschina.org%2Fplugins%2Fmini-css-extract-plugin%2F
[9]https://webpack.docschina.org/plugins/html-webpack-plugin/: https://link.juejin.cn?target=https%3A%2F%2Fwebpack.docschina.org%2Fplugins%2Fhtml-webpack-plugin%2F
[10]https://github.com/jantimon/html-webpack-plugin#options: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fjantimon%2Fhtml-webpack-plugin%23options
[11]https://www.npmjs.com/package/clean-webpack-plugin: https://link.juejin.cn?target=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fclean-webpack-plugin
[12]https://webpack.docschina.org/contribute/writing-a-loader/: https://link.juejin.cn?target=https%3A%2F%2Fwebpack.docschina.org%2Fcontribute%2Fwriting-a-loader%2F
[13]https://webpack.docschina.org/contribute/writing-a-plugin/: https://link.juejin.cn?target=https%3A%2F%2Fwebpack.docschina.org%2Fcontribute%2Fwriting-a-plugin%2F
[14]https://webpack.docschina.org/api/compiler-hooks/: https://link.juejin.cn?target=https%3A%2F%2Fwebpack.docschina.org%2Fapi%2Fcompiler-hooks%2F
[15]https://zhuanlan.zhihu.com/p/268925969: https://link.juejin.cn?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F268925969
最后
如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個小忙:
點個「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點在看,都是耍流氓 -_-)
歡迎加我微信「?sherlocked_93?」拉你進技術(shù)群,長期交流學(xué)習(xí)...
關(guān)注公眾號「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時聊騷。

