在 webpack 支持的前端代碼模塊化中,我們可以使用類似 `import * as m from './index.js'` 來引用代碼模塊 `index.js`。
引用第三方類庫則是像這樣:`import React from 'react'`。webpack 構建的時候,會解析依賴后,然后再去加載依賴的模塊文件,那么 webpack 如何將上述編寫的 `./index.js` 或 `react` 解析成對應的模塊文件路徑呢?
> 在 JavaScript 中盡量使用 ECMAScript 2015 Modules 語法來引用依賴。
webpack 中有一個很關鍵的模塊 [enhanced-resolve](https://github.com/webpack/enhanced-resolve/) 就是處理依賴模塊路徑的解析的,這個模塊可以說是 Node.js 那一套模塊路徑解析的增強版本,有很多可以自定義的解析配置。
> 不熟悉 Node.js 模塊路徑解析機制的同學可以參考這篇文章:[深入 Node.js 的模塊機制](http://www.infoq.com/cn/articles/nodejs-module-mechanism)。
## 模塊解析規則
我們簡單整理一下基本的模塊解析規則,以便更好地理解后續 webpack 的一些配置會產生的影響。
* 解析相對路徑
1. 查找相對當前模塊的路徑下是否有對應文件或文件夾
2. 是文件則直接加載
3. 是文件夾則繼續查找文件夾下的 package.json 文件
4. 有 package.json 文件則按照文件中 `main` 字段的文件名來查找文件
5. 無 package.json 或者無 `main` 字段則查找 `index.js` 文件
* 解析模塊名
查找當前文件目錄下,父級目錄及以上目錄下的 `node_modules` 文件夾,看是否有對應名稱的模塊
* 解析絕對路徑(不建議使用)
直接查找對應路徑的文件
在 webpack 配置中,和模塊路徑解析相關的配置都在 `resolve` 字段下:
```
module.exports = {
resolve: {
// ...
}
}
```
接下來的內容會省略上述代碼,直接描述 `resolve` 字段中的內容。
## 常用的一些配置
我們先從一些簡單的需求來闡述 webpack 可以支持哪些解析路徑規則的自定義配置。
### `resolve.alias`
假設我們有個 `utils` 模塊極其常用,經常編寫相對路徑很麻煩,希望可以直接 `import 'utils'` 來引用,那么我們可以配置某個模塊的別名,如:
```
alias: {
utils: path.resolve(__dirname, 'src/utils') // 這里使用 path.resolve 和 __dirname 來獲取絕對路徑
}
```
上述的配置是模糊匹配,意味著只要模塊路徑中攜帶了 `utils` 就可以被替換掉,如:
```
import 'utils/query.js' // 等同于 import '[項目絕對路徑]/src/utils/query.js'
```
如果需要進行精確匹配可以使用:
```
alias: {
utils$: path.resolve(__dirname, 'src/utils') // 只會匹配 import 'utils'
}
```
更多匹配相關的寫法可以參考官方文檔 [Resolve Alias](https://doc.webpack-china.org/configuration/resolve/#resolve-alias),這里不一一舉例說明。
### `resolve.extensions`
在看第 1 小節中的 webpack 配置時,你可能留意到了這么一行:
```
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx'],
// 這里的順序代表匹配后綴的優先級,例如對于 index.js 和 index.jsx,會優先選擇 index.js
```
看到數組中配置的字符串大概就可以猜到,這個配置的作用是和文件后綴名有關的。是的,這個配置可以定義在進行模塊路徑解析時,webpack 會嘗試幫你補全那些后綴名來進行查找,例如有了上述的配置,當你在 src/utils/ 目錄下有一個 common.js 文件時,就可以這樣來引用:
```
import * as common from './src/utils/common'
```
webpack 會嘗試給你依賴的路徑添加上 `extensions` 字段所配置的后綴,然后進行依賴路徑查找,所以可以命中 src/utils/common.js 文件。
但如果你是引用 src/styles 目錄下的 common.css 文件時,如 `import './src/styles/common'`,webpack 構建時則會報無法解析模塊的錯誤。
你可以在引用時添加后綴,`import './src/styles/common.css'` 來解決,或者在 `extensions` 添加一個 `.css` 的配置:
```
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx', '.css'],
```
### `resolve.modules`
前面的內容有提到,對于直接聲明依賴名的模塊(如 `react` ),webpack 會類似 Node.js 一樣進行路徑搜索,搜索 node\_modules 目錄,這個目錄就是使用 `resolve.modules` 字段進行配置的,默認就是:
```
resolve: {
modules: ['node_modules'],
},
```
通常情況下,我們不會調整這個配置,但是如果可以確定項目內所有的第三方依賴模塊都是在項目根目錄下的 node\_modules 中的話,那么可以在 node\_modules 之前配置一個確定的絕對路徑:
```
resolve: {
modules: [
path.resolve(__dirname, 'node_modules'), // 指定當前目錄下的 node_modules 優先查找
'node_modules', // 如果有一些類庫是放在一些奇怪的地方的,你可以添加自定義的路徑或者目錄
],
},
```
這樣配置在某種程度上可以簡化模塊的查找,提升構建速度。
### `resolve.mainFields`
> 4. 有 package.json 文件則按照文件中 `main` 字段的文件名來查找文件
我們之前有提到這么一句話,其實確切的情況并不是這樣的,webpack 的 `resolve.mainFields` 配置可以進行調整。當引用的是一個模塊或者一個目錄時,會使用 package.json 文件的哪一個字段下指定的文件,默認的配置是這樣的:
```
resolve: {
// 配置 target === "web" 或者 target === "webworker" 時 mainFields 默認值是:
mainFields: ['browser', 'module', 'main'],
// target 的值為其他時,mainFields 默認值為:
mainFields: ["module", "main"],
},
```
因為通常情況下,模塊的 package 都不會聲明 `browser` 或 `module` 字段,所以便是使用 `main` 了。
在 NPM packages 中,會有些 package 提供了兩個實現,分別給瀏覽器和 Node.js 兩個不同的運行時使用,這個時候就需要區分不同的實現入口在哪里。如果你有留意一些社區開源模塊的 package.json 的話,你也許會發現 `browser` 或者 `module` 等字段的聲明。
### `resolve.mainFiles`
當目錄下沒有 package.json 文件時,我們說會默認使用目錄下的 index.js 這個文件,其實這個也是可以配置的,是的,使用 `resolve.mainFiles` 字段,默認配置是:
```
resolve: {
mainFiles: ['index'], // 你可以添加其他默認使用的文件名
},
```
通常情況下我們也無須修改這個配置,index.js 基本就是約定俗成的了。
### `resolve.resolveLoader`
這個字段 `resolve.resolveLoader` 用于配置解析 loader 時的 resolve 配置,原本 resolve 的配置項在這個字段下基本都有。我們看下默認的配置:
```
resolve: {
resolveLoader: {
extensions: ['.js', '.json'],
mainFields: ['loader', 'main'],
},
},
```
這里提供的配置相對少用,我們一般遵從標準的使用方式,使用默認配置,然后把 loader 安裝在項目根路徑下的 node\_modules 下就可以了。
## 小結
webpack 依賴 [enhanced-resolve](https://github.com/webpack/enhanced-resolve/) 來解析代碼模塊的路徑,webpack 配置文件中和 `resolve` 相關的選項都會傳遞給 enhanced-resolve 使用,我們介紹了這些選項的作用:
* `resolve.alias`
* `resolve.extensions`
* `resolve.modules`
* `resolve.mainFiles`
* `resolve.resolveLoader`
webpack 提供的這些選項可以幫助你更加靈活地去控制項目中代碼模塊的解析,除了上述的選項外,其他的選項在日常項目中相對比較少用到,如若需要,可以參考官方文檔 [Resolve](https://doc.webpack-china.org/configuration/resolve/)。
## 例子
本小節提及的一些簡單的 Demo 可以在 [webpack-examples](https://github.com/teabyii/webpack-examples) 找到。