透過現象看webpack處理css檔案中圖片路徑轉換的具體過程
webpack是目前使用比較流行的一個前端模組打包器,前端的任何資源都被當成一個模組來處理,如圖片、css檔案等等。在基於webpack構建的前端專案中,一般都會配置有關css檔案處理的規則,這其中也包括css檔案中圖片資源的處理,那麼webpack到底是怎麼處理它的呢?筆者之前也遇到過類似圖片路勁的問題,為此還寫過一篇博文webpack生成的css檔案background-image url圖片無法載入。今天就來說說webpack是怎麼處理css檔案中的圖片路徑的,首先上一個具體的例子。
一個具體問題
最近使用umi搭建前端的一個專案,在使用過程可能遇到一個umi的bug,為此還提出了一個issue 專案配置css module影響到css-loader對第三方庫css檔案中圖片url的處理。順便在簡述下:
在專案中通過設定
cssLoaderOptions.modules
為true來開啟css module專案中引入了第三方庫kindeditor的css檔案。
import 'kindeditor/themes/default/default.css'
該default.css檔案中有通過
url
引入圖片資源,並且圖片資源非相對路徑寫法,例如其中一處的寫法:.ke-toolbar-icon-url { background-image: url(background.png); }
然後通過npm start開啟本地服務進行預覽時,編譯報錯,如下圖:
奇怪,明明對應的圖片資源是存在的,webpack編譯時為啥找不到呢?苦苦尋思了一番沒有找到答案,於是一頭扎進webpack和css-loader原始碼的海洋中開啟"尋寶"之旅。
不賣關子了,導致上述的直接原因:
css-loader沒有對css檔案的
url
方法進行處理(轉化為相對路徑)
這樣導致webpack在整合經過loader處理後的default.css模組時,因為模組用到require(background.png)
來引用圖片資源,此時就用到Nodejs的模組載入機制,其具體可以檢視本部落格另一篇文章談談npm依賴管理,也可以檢視nodejs官網module章節。nodejs在解析background.png圖片路徑時,會將其解析為第三方模組,這樣會從node_modules
中查詢,通過在webpack中列印錯誤日誌,可以從其中看出一些端倪,如下圖,missing欄位表示查詢過的路徑均沒有找到對應的資原始檔。
umi內部其實使用css-loader-1
(fork [email protected]而來)來處理css檔案的, 導致css-loader
沒有對css檔案圖片路徑進行處理的底層原因:
專案開啟css module後,不該影響到node_modules中css檔案的css module的情況而實際上產生了影響;導致沒有對第三方庫中的css檔案中圖片路徑進行處理
webpack是怎麼轉化css中的圖片路徑的?
如果單純為了解決上面問題就可以到此為止,但是處於好奇,畢竟被坑了幾次,想知道webpack是怎麼處理css檔案中的圖片路徑的。例如我們在專案中這樣寫過css:
.xxa {
background: url(background.png)
}
或者這樣:
.xxb {
background: url(~alias/background.png)
}
在專案中,不論我們用less、stylus還是sass等css預處理庫編寫css,其最終是通過對應的loader如less-loader將編寫的樣式轉換變換為css,然後通過css-loader
來處理css中有關路徑的轉換,其作用拿其官網的介紹來說:
The
css-loader
interprets@import
andurl()
likeimport/require()
and will resolve them.
最常見處理css樣式的專案,一般經過以下幾個loader從右到左順序執行,拿less編寫的樣式來說,以內聯loader的展示形式來說明:
!!css-loader!post-loader!less-loader!./xxx/xx.less
當然css-loader處理後還要經過style-loader或者mini-css-extract-plugin提供的loader處理,但是這不在本次談論範圍。
下面通過一幅圖來看看經過webpack解析模組到css產出這一過程,webpack幫我們做了什麼。
具體就來簡單分析整個流程,可能分析有不正確的地方,還請大家批評指正
NormalModuleFactory解析並建立模組
首先從入口檔案(entry配置的檔案)開始構建,使用NormalModuleFactory來解析並建立模組
NormalModuleFactory使用
enhance-resolve
來解析依賴的模組絕對地址,如果模組地址解析錯誤就會如文章開頭的問題丟擲錯誤,解析正確則會建立依賴模組。如上圖中的./index.css,模組地址解析成功後,webpack為index.css建立的模組屬性如下圖:建立的一個模組,一般包括模組的type、context、request、userRequest、rawRequest、resource、dependencies和loaders等模組相關資訊。
模組建立後,會用其依賴處理的loader來編譯模組內容,模組依賴的loader存放在模組的loader屬性陣列中;對於css檔案最後是用
css-loader
來處理。
css-loader編譯css檔案
css-loader官網說的會對css檔案的url/@import
進行處理,但是具體實現細節並沒有詳細闡述。下面來簡單說說對css-loader的主要功能:
轉換css中的
url
和@import
為require/import
;例如
url
中的地址(絕對地址除外)會被解析為相對地址,防止webpack在解析模組地址時出錯;這其中包括webpack alias別名組成的地址和node_moduels庫中地址。順便說下:css-loader內部是通過
postcss
生成css的ast並遍歷找出其url方法來完成轉換的。按comonjs模組的形式生成css檔案模組內容
css檔案最終轉換後的commonjs模組形式,模組的字尾還是.css,其內容如下圖所示:
css-loader還處理css module,也是通過遍歷css的ast來完成轉換
這樣通過css-loader完成了css檔案中圖片url路徑的轉換,有助於webpack尋找圖片資源的具體位置。
url-loader處理圖片資源
其實,在css-loader處理完css模組過程中,會再次通過NormalModuleFactory來解析並建立其內部的圖片模組,webpack模組對應的屬性如下圖:
生成圖片對應的commonjs模組內容可能為base64的內容,如下圖:
也可能為圖片資源產出的引用地址,如下圖:
這主要取決於url-loader在處理圖片資源時是否指定limit
配置項值,該值會跟圖片內容大小進行比較。在limit
值小於圖片內容大小時,則使用file-loader
來實現圖片提出到webpack編譯產出的對應位置下。
上面圖片模組內容為圖片產出地址,正是file-loader
處理的結果,其實現以下幾項功能:
圖片內容會抽離到webapck的編譯產出位置。
按照loader的
name
配置和webpack的publicPath
配置項生成最終的url。因為圖片的內容被抽離掉,那麼webpack生成的圖片模組內容應該為該圖片的引用地址。這涉及到兩部分
根據file-loader的name配置項生成相對地址部分。
如下file-loader配置項:
{ test: /\.(png|jpe?g|gif)$/i, loader: 'file-loader', options: { name: 'static/[name].[hash:8].[ext]', }, }
然後根據
loader-utils
的interpolateName
方法解析對應的url。例如上面css檔案的圖片地址轉換結果:./background.png
會轉換為:static/background.a9153e95.png
根據
webpackConfig.output.publishPath
生成圖片的引用地址。最終生成的地址為:
__webpack_public_path__ + "static/background.a9153e95.png";
經過上面步驟的處理,我們看到產出的最終css檔案的效果如下圖:
順便說一下,如果產出的css檔案經過mini-css-extract-plugin
提供的loader進行統一抽離,那麼它也可能會影響css檔案中圖片的引用路徑,尤其該loader配置了publicPath
內容,如下面loader的配置:
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: 'public/path/to/'
}
}
那麼css檔案中圖片的路徑最終結果如下圖:
這就是webapck通過各種loader處理css中圖片路勁的過程,通過這一過程我們可能只是大概對這一過程有一個大概的認知,如果要深入理解還是需要花時間研究。
參考文獻
css-loaderr
url-loader
file-loader
mini-css-extract-plugin