module
webpack 的初衷是讓 js 支持模塊化管理,並且將前端中的各種資源都納入到對應的模塊管理中來,所以在 webpack 的設計中,最重要的部分就是管理模塊和模塊之間的關系。
在 webpack 支持的前端代碼模塊化中,我們可以使用類似 import * as m from './index.js'
來引用代碼模塊 index.js
。webpack 構建的時候,會解析依賴后,然后再去加載依賴的模塊文件。所謂 webpack 構建的起點,本質上也是一個 module,而我們在設置好 webpack 后,開發的過程亦是在寫一個個的業務 module。
在 JavaScript 中盡量使用 ES6 module 的語法來引用依賴。
路徑解析
當我們寫一個 import 語句來引用一個模塊時,webpack 是如何獲取到對應模塊的文件路徑的呢?這其中有十分復雜的實現邏輯和相對繁瑣的配置選擇。
webpack 中有一個很關鍵的模塊 enhanced-resolve 就是處理依賴模塊路徑的解析的,這個模塊可以說是 Node.js 那一套模塊路徑解析的增強版本,有很多可以自定義的解析配置。
不熟悉 Node.js 模塊路徑解析機制的同學可以參考這篇文章:深入 Node.js 的模塊機制。
簡單整理一下基本的模塊解析規則,以便更好地理解后續 webpack 的一些配置會產生的影響。
- 解析相對路徑
- 查找相對當前模塊的路徑下是否有對應文件或文件夾
- 是文件則直接加載
- 是文件夾則繼續查找文件夾下的 package.json 文件
- 有 package.json 文件則按照文件中 main 字段的文件名來查找文件
- 無 package.json 或者無 main 字段則查找 index.js 文件
- 解析模塊名
查找當前文件目錄下,父級目錄及以上目錄下的 node_modules 文件夾,看是否有對應名稱的模塊
- 解析絕對路徑(不建議使用)
直接查找對應路徑的文件
在 webpack 配置中,和模塊路徑解析相關的配置都在 resolve 字段下:
module.exports = { resolve: { // ... } }
resolve 配置
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' }
查看下vue-cli的別名配置代碼如下:
function resolve (dir) { return path.join(__dirname, '..', dir) } // ...省略其它代碼 resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': resolve('src'), '#': resolve('config') } } // ...省略其它代碼
resolve.extensions
我們在頁面中常常這樣引用文件:
import * as common from './src/utils/common'
webpack 會自行補全文件后綴,而這個補全的行為,也是可以配置的。
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx'], // 這里的順序代表匹配后綴的優先級,例如對於 index.js 和 index.jsx,會優先選擇 index.js
webpack 會嘗試給你依賴的路徑添加上 extensions
字段所配置的后綴,然后進行依賴路徑查找,所以可以命中 src/utils/common.js 文件。
vue腳手架的extensions的配置如下:
extensions: ['.js', '.vue', '.json']
resolve.modules
對於直接聲明依賴名的模塊,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
有 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 下就可以了。