本文主要討論基於webpack5+TypeScript的React專案(cra、craco底層本質都是使用webpack,所以同理)在2023年的今天是如何在專案中使用svg資源的。
首先,假定您已經完成基於webpack5+TypeScript的React專案的搭建工作(如果您不太清楚搭建的背景,可以參考這篇筆記:【個人筆記】2023年搭建基於webpack5與typescript的react專案 - 知乎 (zhihu.com))。
SVG:可縮放向量圖形 | MDN (mozilla.org)
要在一般的html中使用SVG,我們可以直接編寫標籤:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512" width="200" height="200">
<path d="M256 32C114.6 32 0 125.1 0 240c0 47.6 19.9 91.2 52.9 126.3C38 405.7 7 439.1 6.5 439.5c-6.6 7-8.4 17.2-4.6 26S14.4 480 24 480c61.5 0 110-25.7 139.1-46.3C192 442.8 223.2 448 256 448c141.4 0 256-93.1 256-208S397.4 32 256 32zm0 368c-26.7 0-53.1-4.1-78.4-12.1l-22.7-7.2-19.5 13.8c-14.3 10.1-33.9 21.4-57.5 29 7.3-12.1 14.4-25.7 19.9-40.2l10.6-28.1-20.6-21.8C69.7 314.1 48 282.2 48 240c0-88.2 93.3-160 208-160s208 71.8 208 160-93.3 160-208 160z"/>
</svg>
</div>
</body>
</html>
在React中,React的jsx標籤與HTML中的標籤幾乎是一一對應的,我們可以通過編寫jsx來描述元件。所以不難想到,我們可以使用svg以及與其關聯的jsx標籤(譬如<path>
、<g>
等)來手寫一個React的SVG元件:
export const IconComment = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
width="200"
height="200">
<path
d="M256 32C114.6 32 0 125.1 0 240c0 47.6 19.9 91.2 52.9 126.3C38 405.7 7 439.1 6.5 439.5c-6.6 7-8.4 17.2-4.6 26S14.4 480 24 480c61.5 0 110-25.7 139.1-46.3C192 442.8 223.2 448 256 448c141.4 0 256-93.1 256-208S397.4 32 256 32zm0 368c-26.7 0-53.1-4.1-78.4-12.1l-22.7-7.2-19.5 13.8c-14.3 10.1-33.9 21.4-57.5 29 7.3-12.1 14.4-25.7 19.9-40.2l10.6-28.1-20.6-21.8C69.7 314.1 48 282.2 48 240c0-88.2 93.3-160 208-160s208 71.8 208 160-93.3 160-208 160z"/>
</svg>
);
}
這個IconComment就是一個普通的React元件,編寫完成後我們就可以在需要使用的地方引入了:
效果如下:
上面我們講到了如何編寫一個svg元件,但一般來說,我們都會讓設計出svg資源,然後存放在專案某個目錄下並進行使用。我們當然可以把設計出的svg的內容複製到我們的專案中,以元件的方式來使用:
但是每次都需要拷貝一個又一個的元件當然是一件很麻煩的事情,在webpack中我們使用svg資源的時候,其實更希望如同圖片資源一樣以模組的形式引入(import或者是require)並使用,就像下面一樣:
如果要達到上面的目的,我們首先需要弄清楚一件事情,那就是咱們"import"的"IconAbc"到底是個什麼?通過上面的程式碼反推,我們很容易回答,IconAbc肯定需要是一個React元件(函陣列件或類元件)。
瞭解webpack的同學都知道,webpack可以通過loader,來處理一個資源在匯入的時候會變成什麼。但現在在webpack設定中,我們先不新增任何關於svg模組的處理loader,不出意外肯定會報錯:
ERROR in ./src/icon-comment.svg 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts譯文:您可能需要適當的載入程式(loader)來處理此檔案型別,目前沒有設定載入程式來處理此檔案。請參閱 https://webpack.js.org/concepts
問題我們已經很清楚了webpack無法找到處理svg模組的loader,那麼現在的解決方案是什麼呢?我們可以使用svgr
提供的配合webpack的loader(Webpack - SVGR (react-svgr.com))就可以完成這個任務。
首先安裝必要的依賴:yarn add -D @svgr/webpack
;
然後,設定webpack處理svg檔案:
module.exports = {
... ...
module: {
rules: [
{
test: /\.tsx?/,
use: [
'babel-loader'
],
exclude: /node_moudles/
},
... ...
+ {
+ test: /\.svg$/,
+ use: ['@svgr/webpack']
+ }
]
},
... ...
}
完成設定以後,重新經過webpack編譯打包,執行後會看到控制檯的輸出:
<IconComment/>
在螢幕上展示出來了。PS:上圖中import報錯暫時可以不用關心,是IDE型別檢查的語法提示,webpack打包是沒有問題的,想要深入瞭解,可以參考:【長文詳解】TypeScript與Babel、webpack的關係以及IDE對TS的型別檢查 - 知乎 (zhihu.com)
回顧整個過程,我們可以用下面的圖來描述這個過程:
當然,我們有的時候並不想按照React元件的使用。例如,svg同樣可以作為一些元素的背景,這個時候我們需要把svg是為類似於圖片一樣的資源,就像下面的方式:
如果svg的loader設定保持不變,還是@svgr/webpack
,我們會看到沒有起效果,並且,檢視對應生成css樣式檔案,我們可以看到對應的url('./icon-comment.svg')
被編譯為了url(8ed4ed501566520a5cd0.svg)
:
這個8ed4ed501566520a5cd0.svg
是什麼呢?可能看起來還有點懵,我們嘗試打包編譯專案,看一下編譯後的產物就知道了:
通過上圖的結果可知,很明顯svg在這種場景下依然被@svgr/webpack
這個loader處理為了React元件,又因為咱們是在less/css中參照這個svg,loader內部將這種場景回退到了檔案資源存放了。
現在,我們希望webpack在處理這種場景的時候,還是以普通資源的方式進行;同時,在React程式碼中依然能夠將svg資源以元件的形式被引入。好在webpack支援這樣的設定:
module.exports = {
... ...
module: {
rules: [
... ...
{
// 參照的資源如果是 '${svg-path}/icon-comment.svg?abc'
test: /\.svg$/,
resourceQuery: /abc/,
// 以webpack的資源形式載入(普通資原始檔、base64等)
type: 'asset',
},
{
// 除了上面的匹配規則,我們都按照React元件來使用
test: /\.svg$/,
resourceQuery: {not: [/abc/]},
use: ['@svgr/webpack']
}
]
},
... ...
}
webpack5中的 type: "assets" 是什麼?可以看這篇文章:
在上述設定中,我們都將匹配svg資源的參照,不同的是,如果這個參照路徑帶上url query,則使用webpack5的asset資源模組來處理;否則,呼叫@svgr/webpack來將其轉換為React元件。
完成上述的設定以後,我們適當的修改程式碼,如下所示:
關於關鍵程式碼的解釋:
./icon-comment.svg
模組,不同的是第四行的引入路徑我們還新增了與webpac設定中保持一致的url query = "abc"。同時,在下面我們分別列印了IconComment和IconCommentUrl。.app
樣式中,我們新增的背景也使用./icon-comment.svg
,也新增了url query = "abc"。程式碼執行以後,我們首先從UI上能夠看到效果:
其次,從控制檯也能看到對應的IconComment就是React函陣列件;IconComment是svg資源的base64 DataUrl:
本文相關demo已提交至webpack5-react-demo的svg_use_case分支,供讀者參考:
w4ngzhen/webpack5-react-demo at svg_use_case (github.com)