02.webpack的核心配置選項
一、webpack預設打包
一般來說,對於安裝了webpack和webpack-cli命令列工具的專案,可以直接在根目錄的終端輸入webpack的命令來對專案進行打包,前面說過這種直接在終端輸入webpack命令的方法會導致優先使用的是全域性安裝的webpack來進行打包的,如果要實現使用區域性的webpack打包,目前有三種方法:
- 在根目錄終端輸入命令:./node_modules/.bin/webpack
- 對於安裝了webpack-cli工具的專案,直接在終端使用:npx webpack
- 在package.json配置檔案下的scripts指令碼選項中配置命令項:"build": "webpack",然後在根目錄終端執行:npm run build,此時會優先執行node_modules下的包,如果沒有查到才會去全域性查詢。
{
"name": "webpack-demo",
"scripts": {
"build": "webpack"
},
"devDependencies": {
"webpack": "^5.68.0",
"webpack-cli": "^4.9.2"
}
}
在執行打包命令之後,webpack會預設的去根目錄下的src資料夾下查詢index.js檔案,將此檔案當做入口檔案,並依次查詢相關模組依賴,最終編譯打包後輸出到dist資料夾下的main.js中,打包之後的main.js中的程式碼是被醜化壓縮過的,並且依舊存在ES6的語法,這是因為預設情況下webpack並不會將ES6語法轉化為低版本瀏覽器中可以執行的ES5語法,要實現這一需求,需要專門的babel工具來將ES6+程式碼轉化為ES5語法。
有的時候我們並不一定要將src資料夾下的index.js檔案當做打包入口,那麼此時可以在終端執行命令,比如:
npx webpack --entry ./src/main.js --output-path ./build
上述程式碼表示:啟動webpack打包的時候,以src目錄下的main.js檔案當做打包入口,並將打包後的程式碼輸出到當前目錄的build資料夾下。
如果覺得每次在終端輸入命令麻煩,還可以在package.json的scripts指令碼選項下配置:
{ "name": "webpack-demo", "scripts": { "build": "webpack --entry ./src/main.js --output-path ./build" }, "devDependencies": { "webpack": "^5.68.0", "webpack-cli": "^4.9.2" } }
更多webpack命令列工具的用法參見:文件-API-命令列介面(CLI)-Flags
二、webpack的配置檔案
在實際的專案開發中,一般都會採用配置檔案的方式來對webpack打包時的諸多選項進行配置。首先我們需要在專案的根目錄下新建一個配置檔案:webpack.config.js,然後將配置項依次寫在裡面,然後全部匯出即可,比如基於配置檔案來宣告打包的入口檔案和出口檔案:
const path = require('path');
module.exports = {
entry:"./src/main.js",
output:{
filename:"bundle.js",
path:path.resolve(__dirname,'./build');
}
}
使用配置檔案時要注意兩點:
- webpack.config.js檔案是執行在node環境下的,所以使用的是CommonJS模組化規範
- 指定打包檔案出口的output的path路徑必須是一個絕對路徑,path.resolve(__dirname)方法可以獲取當前檔案所在目錄的絕對路徑,然後再拼接第二個引數的路徑形成最終出口檔案的絕對路徑。
path.resolve(__dirname);
C:\Users\克林辣舞\Desktop\webpack\webpack-demo
path.resolve(__dirname,'./build');
C:\Users\克林辣舞\Desktop\webpack\webpack-demo\build
三、自定義專案配置檔案
在執行npm run build命令打包的時候,webpack會按照以下流程來確定打包的入口:
- 查詢根目錄下是否存在webpack.config.js配置檔案,如果有就按照配置檔案資訊來打包;
- 如果沒有配置檔案,那麼就查詢src目錄下的index.js當做打包入口進行打包
但是在實際的開發中,有的時候可能會專門新建一個config資料夾來存放各類配置檔案,此時如果採用webpack預設的查詢配置檔案的方法是會報錯的,要解決這一問題有兩個方法:
- 直接在終端執行命令
npx webpack --config ./config/webpack.config.js
- 在package.json配置檔案中的scripts指令碼項中進行配置:
{
"name": "webpack-demo",
"scripts": {
"build": "webpack --config ./config/webpack.config.js"
},
"devDependencies": {
"webpack": "^5.68.0",
"webpack-cli": "^4.9.2"
}
}
四、webapck依賴圖(dependency graph)
- 什麼是webapck依賴圖?
webpack在對應用程式打包的時候,無論是基於類似--entry的命令還是基於配置檔案,都會首先確定打包的入口檔案;在找到入口檔案之後,webpack就會遞迴的構建一個依賴關係圖(圖是一種資料結構),這個依賴關係圖包含了應用程式中所需要的所有模組,包含js、css、圖片、字型等;然後將所有模組基於不同的loader來解析後打包為最終的bundle檔案,這個檔案是可以被瀏覽器載入的。
所以如果一個檔案自始至終沒有存在於依賴圖中,那麼該檔案是不會被打包的。
- tree shaking
如果一個檔案存在於依賴圖中但是該檔案中宣告的函式或者變數沒有被使用,那麼在打包的過程中webpack會使用tree shaking特性,將未使用的程式碼不進行打包,只打包那些使用的程式碼。
五、認識loader及配置方式
當我們在一個js檔案中引入一個css檔案之後執行打包操作,此時webpack會報錯,報錯資訊為:
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.
意思是提示我們需要找到一個合適的loader來載入這個css檔案,因為webpack預設只可以處理js、ts以及json檔案,其他型別的檔案webpack在打包的時候並不知道該如何處理,此時loader就派上用場了。
loader是什麼?
loader英文翻譯過來是“載入程式”的意思,在webpack中的loader可以實現對於模組的原始碼進行替換,正是基於這一原理,可以將webpack原本不識別的css檔案基於css-loader處理為可以識別的檔案。
loader的3種配置方式
- 內聯方式
可以直接通過在import語句中直接指定當前匯入資源所需的loader,以css檔案為例:通過感嘆號!來宣告loader名稱,多個loader用!依次隔開即可。
import "css-loader!style-loader!./css/index.css"
- CLI命令列方式(v4還在使用,v5版本已經廢棄)
--module-bind 'css=css-loader'
{
"name": "webpack-demo",
"scripts": {
"build": "webpack --config ./config/webpack.config.js --module-bind 'css=css-loader' "
},
"devDependencies": {
"webpack": "^5.68.0",
"webpack-cli": "^4.9.2"
}
}
- 基於webpack.config.js配置檔案來宣告.
- module.rules選項中允許我們配置多個不同的loader,這種配置的方式便於管理和維護,推薦使用
- rules屬性的值是一個數組,陣列中的每一個物件被稱之為Rule物件,每一個Rule物件中可以設定多個屬性來配置loader使用規則:
- test屬性:一般情況下值為正則表示式,專門用於匹配不同的資源;
- use屬性:
1.值為一個數組的時候,陣列中每一項又是一個物件,被稱之為UseEntry物件,每一個UseEntry物件中又有一些屬性,用於配置laoder:
a:loader屬性:必須屬性,對應的值是一個字串也就是loader名稱
b:options屬性:可選屬性,值是一個字串或者物件,值會傳入到loader中
c:query屬性:目前在v5中已經被options屬性取代
2.值為一個字串的時候,如use:['css-loader']就等價於:use:[{loader:'css-loader'}] - loader屬性:值為一個字串,一般只有單個loader的時候可以這樣寫,其實本質也就是use:[{loader:'css-loader'}]的簡寫。
module.exports = {
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, '../build')
},
module: {
rules: [
/* 寫法一 */
{
test: /\.css$/, // 匹配規則
loader: "css-loader"
},
/* 寫法二 */
{
test: /\.css$/, // 匹配規則
use: [{
loader: "css-loader"
}]
},
/* 寫法三 */
{
test: /\.css$/, // 匹配規則
use:["css-loader"]
},
]
}
}
不同場景下配置loader的不同選擇
通過webpack.config.js檔案來配置loader的時候,我們已經知道有三種不同的寫法,但是一般遵循下面的配置規則:
- 如果只有一個loader且沒有options配置項
{
test: /\.css$/, // 匹配規則
use:["css-loader"]
}
// 或者
{
test: /\.css$/, // 匹配規則
loader:"css-loader"
}
- 如果有多個loader但是都沒有options配置項,直接以字串簡寫的方式依次宣告
{
test: /\.css$/, // 匹配規則
use:["css-loader","style-loader"]
}
- 如果有多個loader並且需要options配置項,必須採用物件寫法來宣告
{
test:/\.css$/,
use:[
{loader:"css-loader",options:{}},
{loader:"style-loader",options:{}}
]
}
多loader的載入順序
以css-loader和style-loader為例,配置在use陣列中的loader載入順序不是我們想當然的從前向後依次載入的,比如使用webpack來載入css檔案,如果按照如下宣告方式會報錯,報錯原因是:因為css-loader只負責將css檔案進行解析,並不會將解析後的css檔案插入到頁面中。所以要想樣式生效還需要一個style-loader來將解析好的css插入到頁面中。但由於use陣列中loader載入順序是從後向前依次載入的,所以下面的配置就等於先插入css然後解析,順序就反了。
{
test: /\.css$/, // 匹配規則
use:["css-loader","style-loader"]
}
Module build failed (from ./node_modules/css-loader/dist/cjs.js):CssSyntaxError
正確的做法是先解析css,然後再將解析後的css載入到頁面中:
{
test: /\.css$/, // 匹配規則
use:["style-loader","css-loader"]
}
style-loader的內部原理
style-loader的原理是先使用js建立一個style標籤,然後將css-loader解析好的css程式碼插入到這個style標籤內部,最後將這個style標籤插入到頁面的head標籤中,樣式就會生效。
六、less-loader的處理
在專案中我們會使用less、sass、styles等CSS預處理語言來編寫樣式檔案,因為這相比於css檔案可以更加高效和便捷,但是瀏覽器並不能直接識別這些css預處理語言,這中間less檔案和如何被轉化為css並最終被瀏覽器載入的呢?
less檔案是基於less工具轉化為css的
首先less檔案轉化為css檔案,所使用的其實是一個less工具,這和webpack是沒有關係的,在webpack中使用的less-loader在處理less檔案的時候,本質也是去載入這個less包並執行對應的命令將less轉化為css:
npm i less -D
npx less ./src/css/component.less > ./src/css/component.css
必須先安裝less包,然後執行如下命令:執行該命令代表著會去node_modules下的.bin資料夾中查詢一個名為lessc的包,然後對less檔案進行編譯,將其轉化為對應的css檔案。
npx less 要轉化的less檔案路徑 > 轉化後的css檔案路徑
如何配置來處理less檔案
可以將處理less檔案的過程看做一個流水線:
- 首先基於less-loader自動執行npx less 命令將less檔案轉化為css檔案;
- 然後將css檔案通過css-loader進行處理解析;
- 最後通過style-loader將處理後css檔案插入到頁面的head中讓樣式生效
{
test:/\.less$/,
use:[
"style-loader",
"css-loader",
"less-loader"
]
}
七、前端工程化中瀏覽器相容性的問題
在前端開發中,瀏覽器的相容性問題一直是一個比較重要的問題,這裡的瀏覽器相容性指的是:
- 不同廠商的瀏覽器對於不同的css特性和js語法的支援情況不同;
- 同一廠商的不同版本的瀏覽器對於不同的css特性和js語法的支援情況不同;
以往我們解決一個css樣式在不同瀏覽器中的相容性的時候,會通過加上瀏覽器字首的方法來解決:
div {
-webkit-transition-delay: time;
-moz-transition-delay: time;
-ms-transition-delay: time;
-o-transition-delay: time;
transition-delay: time;
}
為了避免我們在多個地方都這樣加瀏覽器字首的方法來解決相容性問題,就出現了autoorefixer工具用於自動加上相容性的瀏覽器字首,還有比如babel工具將高版本的js語法轉化為低版本瀏覽器也可以識別的程式碼。
但是這裡有一個問題:那就是現在市面上的瀏覽器有很多,廠商很多並且版本也很多,諸如autoorefixer工具、babel這些前端工具難道需要為所有廠商和版本的瀏覽器都進行適配麼?答案是否定的。
日常通過前端框架搭建的專案中就有一個檔案叫做.broswerslistrc,這個檔案中定義了以下規則:
> 1%
last 2 versions
not dead
還有比如使用React專案腳手架搭建的專案中的package.json檔案中會有一項配置:
module.exports = {
"browserslist":[
"> 1%",
"last 2 versions",
"not dead"
]
}
這些配置資訊是做什麼的呢?
每一條配置資訊其實都是一個個的查詢瀏覽器的條件,這些查詢條件會提供給諸如autoorefixer和babel這些工具,告訴這些工具當前專案到底需要適配哪些瀏覽器。
比如"> 1%"這個條件就告訴autoorefixer和babel這些工具,當前專案所要適配的瀏覽器是市場佔有率大於1%的瀏覽器:
- 如果某個瀏覽器市場佔有率大於1%,並且這個瀏覽器還暫時不支援ES6+等新特性,那麼babel工具將ES6+語法轉化為ES5語法,以便於這個瀏覽器來適配。
- 如果某個瀏覽器市場佔有率小於1%,那麼babel不用工作,因為這些瀏覽器不屬於此專案的適配瀏覽器範圍。
瀏覽器的市場佔有率
但是這時候又有問題來了,應該怎麼查詢到哪些瀏覽器的市場佔有率大於1%呢?
目前瀏覽器最官方權威的資料來自於caniuse網站caniuse,在caniuse網站的usage-table這個頁面就展示了當前市場上所有瀏覽器的市場佔有率資料。
認識browserslist工具
現在又有新的問題:比如我設定了一個條件:> 1%,其實表達的意思是:
- 這個專案中的css要相容市場佔有率大於1%的瀏覽器
- 這個專案中的js語法要相容市場佔有率大於1%的瀏覽器
-
以上要求我們要如何才能實現呢?
所幸現在前端工程化的程式非常高,已經有了autoorefixer、babel、postcss-preset-env這些工具,基於這些前端工程化的工具就可以解決本專案瀏覽器相容性的問題。 -
但是要如何讓這些工具都能共享此專案關於瀏覽器條件查詢的配置呢?
答案是Broswerslist工具:它是一個專門用於在不同的前端工程化工具之間,共享目標瀏覽器和nodejs版本配置的工具,它本身也是一個工具。 -
Broswerslist工具使用注意事項
Broswerslist這個工具單獨是沒有任何用的,在Vue腳手架建立的專案中,browserslist欄位中宣告的查詢條件會被其搭配的工具比如Autoprefixer和@babel/preset-env用來確定哪些瀏覽器的js特性需要被轉譯以及哪些css樣式需要新增對應的瀏覽器字首,Broswerslist工具搭配的常見的工程化工具有:
- Autoprefixer
- Babel
- postcss-preset-env
- eslint-plugin-compat
- postcss-normalize
- stylelint-no-unsupported-browser-features
-
使用vue-cli建立專案的時候會訊問是想將配置資訊做一個單獨的檔案還是寫在package.json中,這裡選擇的不同就會導致browserslist配置資訊的位置不同
-
autoprefixer現在已經在postcss-preset-env中被內建了
-
webpack在安裝的時候就自動安裝browserslist和caniuse-lite這兩個包,當前端工具在工作的時候就會自動讀取到browserslist工具的配置檔案中的查詢條件,然後啟動caniuse-lite工具來查詢到具體的瀏覽器版本。
-
在終端輸入npx browserslist命令之後
- 先查詢到專案中是否存在.browserslistrc檔案或者package.json檔案中的browserlist欄位,如果有就讀取其配置資訊並啟動caniuse-lite工具進行查詢,並在控制檯進行列印;
- 如果以上兩個地方都沒有找到,那麼就會採用預設查詢條件的配置,也就是等於npx browserslist defaults如下所示,並將查詢結果打印出來。
> 0.5%
last 2 version // 每個瀏覽器的最後2個版本
Firefox ESR // 最新的Firefox ESR版本
not dead // 24個月內任然有官方支援或者更新的瀏覽器
browserslist的編寫規則
其他更多編寫規則見下圖:
現在browserslist這個工具的查詢規則配置好之後,該去那個地方查詢符合條件的瀏覽器版本呢?
其實browserslist配置中的條件查詢是基於caniuse這個網站提供的一個小工具:caniuse-lite來實現的.
在終端輸入命令:npx browserslist defaults,便打印出了很多符合預設查詢條件的瀏覽器版本,如下所示:
npx browserslist defaults
and_chr 97
and_ff 96
and_qq 10.4
and_uc 12.12
android 97
baidu 7.12
chrome 98
chrome 97
chrome 96
edge 98
edge 97
edge 96
firefox 96
firefox 95
firefox 91
firefox 78
ie 11
...
這就意味著當我們在專案中的.broswerslistrc檔案或者package檔案中配置了適配的瀏覽器條件為defaults的時候,其實css和js適配的就是上面這些瀏覽器版本。配置broswerslist的地方有兩個:
- 單獨新建一個.broswerslistrc檔案
- 在package.json檔案中配置
如果這兩個地方都沒有配置的話,那麼就認定為預設配置defaults:
// default broswer query
">0.5%"
"last 2 version"
"FireFox ESR"
"not dead"
多個配置條件的關係
- 多個條件用or或者逗號隔開的時候,或者換行也代表並集。意思每個條件滿足的瀏覽器都會被選中:
"browserslist":["> 1%","last 2 versions"]
"browserslist":["> 1%" or "last 2 versions"]
- 多個條件用and連線代表交集,也就是多個條件同時滿足的瀏覽器才會被選中:
">0.5%"
"and last 2 version"
"and FireFox ESR"
- 條件之前有not代表取反,意思是此條件滿足的瀏覽器都不可以被選中
">0.5%"
"and last 2 version"
"not dead"