你一定見(jiàn)過(guò)這些導(dǎo)入方式,無(wú)論是 ESM 還是 CommonJS 模塊龟劲,或是其他模塊規(guī)范胃夏。
import react from 'react'
import button from './components/button'
const path = require('path')
那么 webpack 是如何去解析查找它們的呢?
模塊解析
resolver 是一個(gè)庫(kù)(library)昌跌,用于幫助找到模塊的絕對(duì)路徑仰禀。一個(gè)模塊可以作為另一個(gè)模塊的依賴(lài)模塊,然后被后者引用避矢。例如:
import foo from 'path/to/module'
所依賴(lài)的模塊可以是來(lái)自應(yīng)用程序或者第三方庫(kù)(library)悼瘾。resolver 幫助 webpack 找到 bundle 中需要引入的模塊代碼,這些代碼在每個(gè) import/require 語(yǔ)句中审胸。
webpack 使用 enhanced-resolve 來(lái)解析文件路徑亥宿。
解析規(guī)則
使用 enhanced-resolve 解析模塊,支持三種形式:絕對(duì)路徑砂沛、相對(duì)路徑烫扼、模塊路徑。
1. 絕對(duì)路徑
不建議使用碍庵。
由于已經(jīng)取得文件的絕對(duì)路徑映企,因此不需要進(jìn)一步再做解析了。
在實(shí)際項(xiàng)目中静浴,除了設(shè)置別名 resolve.alias 時(shí)采用絕對(duì)路徑的方式堰氓,其他的我?guī)缀鯖](méi)見(jiàn)過(guò)使用絕對(duì)路徑的。(也可能我讀的項(xiàng)目太少了)
import button from '/Users/frankie/component/button'
2. 相對(duì)路徑
在這種情況下苹享,使用 import/require 的資源文件(resource file)所在的目錄被認(rèn)為是上下文目錄(context directory)双絮。在 import/require 中給定的相對(duì)路徑,會(huì)添加此上下文路徑(context path),以產(chǎn)生模塊的絕對(duì)路徑(absolute path)囤攀。
import button from './component/button'
3. 模塊路徑
上面兩種方式软免,應(yīng)該沒(méi)有太多理解難度,而模塊名才是我們要重點(diǎn)理解的焚挠。
直接引入模塊名膏萧,首先查找當(dāng)前文件目錄,若查找不到蝌衔,會(huì)繼續(xù)往父級(jí)目錄一個(gè)一個(gè)地查找榛泛,直至到項(xiàng)目根目錄下的 node_modules 目錄(默認(rèn))。若再查找不到胚委,則會(huì)拋出錯(cuò)誤挟鸠。
import 'react'
import 'module/lib/file'
注意:
- 默認(rèn)的
node_modules
可以通過(guò) resolve.modules 進(jìn)行更改。- 查找中會(huì)根據(jù) resolve.extensions 自動(dòng)補(bǔ)全擴(kuò)展名亩冬,默認(rèn)是
['.wasm', '.mjs', '.js', '.json']
。- 查找中會(huì)根據(jù) resolve.alias 替換掉別名硼身。
模塊將在 resolve.modules 中指定的目錄內(nèi)搜索硅急。可以通過(guò) resolve.alias 配置創(chuàng)建一個(gè)別名來(lái)替換初始模塊路徑佳遂。
一旦上述規(guī)則解析路徑之后营袜,解析器(resolver)將檢查路徑是否指向文件或目錄。
-
指向文件
- 如果路徑具有文件擴(kuò)展名丑罪,則被直接打包荚板。
- 否則,將使用 resolve.extensions 選項(xiàng)作為文件擴(kuò)展名來(lái)解析吩屹。
-
指向目錄
按以下步驟找到具有正確擴(kuò)展名的文件:
- 如果文件夾中包含 package.json 文件跪另,則按順序查找 resolve.mainFields 配置選項(xiàng)中指定的字段,并且 package.json 中的第一個(gè)這樣的字段確定文件路徑煤搜。
- 如果 package.json 文件不存在或者 package.json 文件中 main 字段沒(méi)有返回一個(gè)有效路徑免绿,則按順序查找 resolve.mainFields 配置選項(xiàng)中指定的文件名,看是否能在 import/require 目錄下匹配到一個(gè)存在的文件名擦盾。
- 文件擴(kuò)展名通過(guò) resolve.extensions 選項(xiàng)采用類(lèi)似的方法進(jìn)行解析嘲驾。
若使用 webpack-dev-server 3.x 版本,建議不要隨意修改 resolve.mainFields 配置項(xiàng)迹卢,它會(huì)報(bào)錯(cuò)辽故。已確認(rèn)是 webpack-dev-server 的 bug,將在不久要發(fā)布的 4.x 版本修復(fù)腐碱。詳請(qǐng) #2801
解析與緩存
Loader 解析遵循與文件解析器指定的規(guī)則相同的規(guī)則誊垢。resolveLoader 配置選項(xiàng)可以用來(lái)為 Loader 提供獨(dú)立的解析規(guī)則。
每個(gè)文件系統(tǒng)訪問(wèn)都被緩存,以便更快觸發(fā)對(duì)同一文件的多個(gè)并行或者串行請(qǐng)求彤枢。在觀察模式下狰晚,只有修改過(guò)的文件會(huì)從緩存中摘出。如果關(guān)閉觀察模式缴啡,在每次編譯前清理緩存壁晒。
Resolve 配置
該選項(xiàng)用于配置模塊如何解析。例如业栅,當(dāng)在 ES6 中調(diào)用 import 'lodash'
秒咐,resolve
選項(xiàng)能夠?qū)?webpack 查找 lodash
的方式去做修改。
1. resolve.alias
創(chuàng)建 import 或 require 的別名碘裕,來(lái)確保模塊引入變得更簡(jiǎn)單携取。
例如,一些位于 src/ 文件夾下的常用模塊:
// webpack.config.js
const path = require('path')
module.exports = {
//...
resolve: {
alias: {
// 可以是絕對(duì)路徑帮孔,或者是相對(duì)路徑雷滋。
// 據(jù)我不完全觀察,結(jié)合 path 模塊和 __dirname 拼接成“絕對(duì)路徑”的方案更多文兢。
// 以下為模糊匹配
Utilities: path.resolve(__dirname, 'src/utilities/'),
Templates: path.resolve(__dirname, 'src/templates/')
}
}
}
現(xiàn)在晤斩,你可以這樣使用別名了:
import Utility from '../../utilities/utility'
// 別名
import Utility from 'Utilities/utility'
也可以在給定的對(duì)象的鍵后的末尾添加 $
,以表示精準(zhǔn)匹配姆坚。這里不展開(kāi)贅述澳泵,詳細(xì)請(qǐng)看這里。
注意兼呵,采用別名引入模塊時(shí)兔辅,先替換后解析。先將模塊路徑中匹配
alias
中的key
替換成對(duì)應(yīng)的value
击喂,再做查找维苔。
2. resolve.extensions
自動(dòng)解析確定的擴(kuò)展。
// webpack.config.js
module.exports = {
//...
resolve: {
// 使用此選項(xiàng)茫负,會(huì)覆蓋默認(rèn)數(shù)組蕉鸳,默認(rèn)值:['.wasm', '.mjs', '.js', '.json']。
// 注意不要少了符號(hào)(.)忍法,有些人配置不成功潮尝,就是因?yàn)樯倭怂? // 從左到右(從上到下)先后匹配擴(kuò)展名,選項(xiàng)中沒(méi)有的后綴饿序,是不會(huì)自動(dòng)補(bǔ)全的勉失。
extensions: ['.js', '.json']
}
}
3. resolve.modules
告訴 webpack 解析模塊時(shí)應(yīng)該搜索的目錄≡剑可以是絕對(duì)路徑或者相對(duì)路徑乱凿,但是它們之間有一點(diǎn)差異顽素。
通過(guò)查看當(dāng)前目錄以及祖先路徑(即 ./node_modules
,../node_modules
等等)徒蟆,相對(duì)路徑將類(lèi)似于 Node 查找 node_modules
的方式進(jìn)行查找胁出。
當(dāng)使用絕對(duì)路徑,將只在給定目錄中搜索段审。
// webpack.config.js
const path = require('path')
module.exports = {
//...
resolve: {
// 默認(rèn)值
modules: ['node_modules']
// 添加一個(gè)目錄到模塊搜索目錄全蝶,此目錄優(yōu)先于 node_modules 搜索。
modules: [path.resolve(__dirname, 'src'), 'node_modules']
}
}
一般地寺枉,不要去更改該選項(xiàng)抑淫。
4. resolve.mainFields
當(dāng)從 npm 包中導(dǎo)入模塊時(shí)(例如,import * as D3 from 'd3'
)姥闪,此選項(xiàng)將決定在 package.json
中使用哪個(gè)字段導(dǎo)入模塊始苇。根據(jù) webpack 配置中指定的 target 不同,默認(rèn)值也會(huì)有所不同筐喳。
// webpack.config.js
module.exports = {
//...
resolve: {
// 不建議修改
// target 為 webworker催式、web 或沒(méi)有指定時(shí),默認(rèn)值為:
mainFields: ['browser', 'module', 'main'],
// 除去上述幾個(gè) target疏唾,對(duì)于其他任意 target(包括 node)蓄氧,默認(rèn)值為:
mainFields: ['browser', 'module', 'main']
}
}
通常情況下,模塊的
package.json
都不會(huì)聲明browser
或module
字段槐脏,所以便是使用main
了。(該選項(xiàng)同樣不建議更改)
5. resolve.mainFiles
解析目錄時(shí)要使用的文件名撇寞。
當(dāng)目錄中沒(méi)有 package.json
時(shí)顿天,結(jié)合 resolve.extensions 來(lái)指明使用該目錄中哪個(gè)文件。
// webpack.config.js
module.exports = {
//...
resolve: {
// 默認(rèn)值
// 可添加多個(gè)蔑担,但不建議修改牌废。
mainFiles: ['index']
}
}
盡可能地,不要去修改該選項(xiàng)啤握。因?yàn)樗瑯訒?huì)影響第三方依賴(lài)包解析鸟缕,可能會(huì)導(dǎo)致部分第三方包解析錯(cuò)誤。例如排抬,我在驗(yàn)證該配置時(shí)懂从,就發(fā)現(xiàn) webpack-dev-server v3 的一個(gè) bug,開(kāi)發(fā)者表示將在 v4 版本中修復(fù)蹲蒲。
所以番甩,不建議隨意修改的配置包括 modules、mainFields届搁、mainFiles缘薛。
6. 更多
它還有其他一些配置項(xiàng)窍育,但比較少用,所以不展開(kāi)贅述宴胧。更多請(qǐng)看這里漱抓。
ResolveLoader 配置
從 webpack 2 開(kāi)始,在配置 loader 時(shí)強(qiáng)烈建議使用全名恕齐。例如 example-loader
乞娄,以盡可能地清晰。
然而檐迟,如果你確實(shí)想省略 -loader
补胚,也就是說(shuō)只使用 example
,則可以使用 resolveLoader.moduleExtensions 此選項(xiàng)來(lái)實(shí)現(xiàn):
// webpack.config.js
module.exports = {
//...
resolve: {
// ...
}
resolveLoader: {
moduleExtensions: ['-loader']
}
}
我使用 webpack 4 在不配置該選項(xiàng)時(shí)追迟,假如將
css-loader
省略為css
溶其,會(huì)報(bào)錯(cuò)提示找不到 loader。為什么我會(huì)單獨(dú)拿出來(lái)介紹一下敦间,因?yàn)榫W(wǎng)上很多文章表示在配置 module.rules 時(shí)可以省略-loader
瓶逃,但我是省略了就不行。所以這里補(bǔ)充一下原因廓块。
小技巧
關(guān)于 webpack 默認(rèn)配置可以從 node_modules/webpack/lib/WebpackOptionsDefaulter.js
查看厢绝。