在第 1 小節,我們提到過,webpack 的 loader 用于處理不同的文件類型,在日常的項目中使用 loader 時,可能會遇到比較復雜的情況,本小節我們來深入探討 loader 的一些配置細節。
## loader 匹配規則
當我們需要配置 loader 時,都是在 `module.rules` 中添加新的配置項,在該字段中,每一項被視為一條匹配使用 loader 的規則。
先來看一個基礎的例子:
```
module.exports = {
// ...
module: {
rules: [
{
test: /\.jsx?/, // 條件
include: [
path.resolve(__dirname, 'src'),
], // 條件
use: 'babel-loader', // 規則應用結果
}, // 一個 object 即一條規則
// ...
],
},
}
```
loader 的匹配規則中有兩個最關鍵的因素:一個是匹配條件,一個是匹配規則后的應用。
匹配條件通常都使用請求資源文件的絕對路徑來進行匹配,在官方文檔中稱為 `resource`,除此之外還有比較少用到的 `issuer`,則是聲明依賴請求的源文件的絕對路徑。舉個例子:在 /path/to/app.js 中聲明引入 `import './src/style.scss'`,`resource` 是 /path/to/src/style.scss,`issuer` 是 /path/to/app.js,規則條件會對這兩個值來嘗試匹配。
上述代碼中的 `test` 和 `include` 都用于匹配 `resource` 路徑,是 `resource.test` 和 `resource.include` 的簡寫,你也可以這么配置:
```
module.exports = {
// ...
rules: [
{
resource: { // resource 的匹配條件
test: /\.jsx?/,
include: [
path.resolve(__dirname, 'src'),
],
},
// 如果要使用 issuer 匹配,便是 issuer: { test: ... }
use: 'babel-loader',
},
// ...
],
}
```
> issuer 規則匹配的場景比較少見,你可以用它來嘗試約束某些類型的文件中只能引用某些類型的文件。
當規則的條件匹配時,便會使用對應的 loader 配置,如上述例子中的 `babel-loader`。關于 loader 配置后面再詳細介紹,這里先來看看如何配置更加復雜的規則匹配條件。
## 規則條件配置
大多數情況下,配置 loader 的匹配條件時,只要使用 `test` 字段就好了,很多時候都只需要匹配文件后綴名來決定使用什么 loader,但也不排除在某些特殊場景下,我們需要配置比較復雜的匹配條件。webpack 的規則提供了多種配置形式:
* `{ test: ... }` 匹配特定條件
* `{ include: ... }` 匹配特定路徑
* `{ exclude: ... }` 排除特定路徑
* `{ and: [...] }`必須匹配數組中所有條件
* `{ or: [...] }` 匹配數組中任意一個條件
* `{ not: [...] }` 排除匹配數組中所有條件
上述的所謂條件的值可以是:
* 字符串:必須以提供的字符串開始,所以是字符串的話,這里我們需要提供絕對路徑
* 正則表達式:調用正則的 `test` 方法來判斷匹配
* 函數:(path) => boolean,返回 `true` 表示匹配
* 數組:至少包含一個條件的數組
* 對象:匹配所有屬性值的條件
通過例子來幫助理解:
```
rules: [
{
test: /\.jsx?/, // 正則
include: [
path.resolve(__dirname, 'src'), // 字符串,注意是絕對路徑
], // 數組
// ...
},
{
test: {
js: /\.js/,
jsx: /\.jsx/,
}, // 對象,不建議使用
not: [
(value) => { /* ... */ return true; }, // 函數,通常需要高度自定義時才會使用
],
},
],
```
上述多個配置形式結合起來就能夠基本滿足各種各樣的構建場景了,通常我們會結合使用 `test/and` 和 `include&exclude` 來配置條件,如上述那個簡單的例子。
## module type
webpack 4.x 版本強化了 module type,即模塊類型的概念,不同的模塊類型類似于配置了不同的 loader,webpack 會有針對性地進行處理,現階段實現了以下 5 種模塊類型。
* `javascript/auto`:即 webpack 3 默認的類型,支持現有的各種 JS 代碼模塊類型 —— CommonJS、AMD、ESM
* `javascript/esm`:ECMAScript modules,其他模塊系統,例如 CommonJS 或者 AMD 等不支持,是 `.mjs` 文件的默認類型
* `javascript/dynamic`:CommonJS 和 AMD,排除 ESM
* `javascript/json`:JSON 格式數據,`require` 或者 `import` 都可以引入,是 `.json` 文件的默認類型
* `webassembly/experimental`:WebAssembly modules,當前還處于試驗階段,是 `.wasm` 文件的默認類型
如果不希望使用默認的類型的話,在確定好匹配規則條件時,我們可以使用 `type` 字段來指定模塊類型,例如把所有的 JS 代碼文件都設置為強制使用 ESM 類型:
```
{
test: /\.js/,
include: [
path.resolve(__dirname, 'src'),
],
type: 'javascript/esm', // 這里指定模塊類型
},
```
上述做法是可以幫助你規范整個項目的模塊系統,但是如果遺留太多不同類型的模塊代碼時,建議還是直接使用默認的 `javascript/auto`。
webpack 后續的開發計劃會增加對更多模塊類型的支持,例如極其常見的 CSS 和 HTML 模塊類型,這個特性值得我們期待一下。
## 使用 loader 配置
當然,在當前版本的 webpack 中,`module.rules` 的匹配規則最重要的還是用于配置 loader,我們可以使用 `use` 字段:
```
rules: [
{
test: /\.less/,
use: [
'style-loader', // 直接使用字符串表示 loader
{
loader: 'css-loader',
options: {
importLoaders: 1
},
}, // 用對象表示 loader,可以傳遞 loader 配置等
{
loader: 'less-loader',
options: {
noIeCompat: true
}, // 傳遞 loader 配置
},
],
},
],
```
我們看下上述的例子,先忽略 loader 的使用情況,單純看看如何配置。`use` 字段可以是一個數組,也可以是一個字符串或者表示 loader 的對象。如果只需要一個 loader,也可以這樣:`use: { loader: 'babel-loader', options: { ... } }`。
我們還可以使用 `options` 給對應的 loader 傳遞一些配置項,這里不再展開。當你使用一些 loader 時,loader 的說明一般都有相關配置的描述。
## loader 應用順序
前面提到,一個匹配規則中可以配置使用多個 loader,即一個模塊文件可以經過多個 loader 的轉換處理,執行順序是從最后配置的 loader 開始,一步步往前。例如,對于上面的 `less` 規則配置,一個 style.less 文件會途徑 less-loader、css-loader、style-loader 處理,成為一個可以打包的模塊。
loader 的應用順序在配置多個 loader 一起工作時很重要,通常會使用在 CSS 配置上,除了 style-loader 和 css-loader,你可能還要配置 less-loader 然后再加個 postcss 的 autoprefixer 等。
上述從后到前的順序是在同一個 rule 中進行的,那如果多個 rule 匹配了同一個模塊文件,loader 的應用順序又是怎樣的呢?看一份這樣的配置:
```
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "eslint-loader",
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
},
],
```
這樣無法法保證 eslint-loader 在 babel-loader 應用前執行。webpack 在 `rules` 中提供了一個 `enforce` 的字段來配置當前 rule 的 loader 類型,沒配置的話是普通類型,我們可以配置 `pre` 或 `post`,分別對應前置類型或后置類型的 loader。
> eslint-loader 要檢查的是人工編寫的代碼,如果在 babel-loader 之后使用,那么檢查的是 Babel 轉換后的代碼,所以必須在 babel-loader 處理之前使用。
還有一種行內 loader,即我們在應用代碼中引用依賴時直接聲明使用的 loader,如 `const json = require('json-loader!./file.json')` 這種。不建議在應用開發中使用這種 loader,后續我們還會再提到。
顧名思義,所有的 loader 按照**前置 -> 行內 -> 普通 -> 后置**的順序執行。所以當我們要確保 eslint-loader 在 babel-loader 之前執行時,可以如下添加 `enforce` 配置:
```
rules: [
{
enforce: 'pre', // 指定為前置類型
test: /\.js$/,
exclude: /node_modules/,
loader: "eslint-loader",
},
]
```
當項目文件類型和應用的 loader 不是特別復雜的時候,通常建議把要應用的同一類型 loader 都寫在同一個匹配規則中,這樣更好維護和控制。
## 使用 `noParse`
在 webpack 中,我們需要使用的 loader 是在 `module.rules` 下配置的,webpack 配置中的 module 用于控制如何處理項目中不同類型的模塊。
除了 `module.rules` 字段用于配置 loader 之外,還有一個 `module.noParse` 字段,可以用于配置哪些模塊文件的內容不需要進行解析。對于一些**不需要解析依賴(即無依賴)** 的第三方大型類庫等,可以通過這個字段來配置,以提高整體的構建速度。
> 使用 `noParse` 進行忽略的模塊文件中不能使用 `import`、`require`、`define` 等導入機制。
```
module.exports = {
// ...
module: {
noParse: /jquery|lodash/, // 正則表達式
// 或者使用 function
noParse(content) {
return /jquery|lodash/.test(content)
},
}
}
```
`noParse` 從某種程度上說是個優化配置項,日常也可以不去使用。
## 小結
webpack 的 loader 相關配置都在 `module.rules` 字段下,我們需要通過 `test`、`include`、`exclude` 等配置好應用 loader 的條件規則,然后使用 `use` 來指定需要用到的 loader,配置應用的 loader 時還需要注意一下 loader 的執行順序。
除此之外,webpack 4.x 版本新增了模塊類型的概念,相當于 webpack 內置一個更加底層的文件類型處理,暫時只有 JS 相關的支持,后續會再添加 HTML 和 CSS 等類型。
## 例子
本小節提及的一些簡單的 Demo 可以在 [webpack-examples](https://github.com/teabyii/webpack-examples) 找到。