CSS Modules:babel-plugin-react-css-modules 小記
14 Aug 2018樣式管理一直是前端工程師的痛點(很煩 (╯‵□′)╯︵┴─┴),因此 CSS 的模組化方法從過去的 OOCSS、SMACSS,到近代的 BEM,還有最近的 CSS Modules 與 CSS in JS,企圖讓程式碼簡潔易懂、可重用,進而有效率地開發和維護。
這裡記錄我的玩具「吃什麼,どっち」使用 CSS Modules 之 babel-plugin-react-css-modules 的實作過程。
前言
一開始我是使用 BEM 來規範程式碼,希望利用規則來做到 CSS 模組化。BEM 的確是很好的 CSS class 命名的設計模式,但命名這件事是由人工來做的,而人的腦袋很殘,一但命名重複就導致模組化失敗,像是被覆寫這種杯具。為了解決這個問題,我又加上一些規則,例如
- 不要對標籤撰寫樣式,像是
img { display: block; }
這種寫法就要完全避免。 - 命名不能太通用、太簡單或太短,例如:header、content、container、footer、btn 就是不行的。
可是,這依舊是依賴人為操作,規則還愈來愈多 (●▼●;),因此我參考了目前主流市場上的兩種解法
- CSS Modules / Scoped CS:希望能改為自動產生不同命名的方式,像是自動在該元件的 class 後加上 hash。React.js 的方案有 react-css-modules 和 babel-plugin-react-css-modules。
- CSS in JS,將樣式包在元件裡,意即直接在 JavaScript 中撰寫 inline-style,比較有名的就是 Styled Components。
好的,來看本篇的重點-babel-plugin-react-css-modules 吧,下次再看有沒有機會來討論其他解法。
安裝與設定
安裝。
npm install babel-plugin-react-css-modules --save
Webpack 設定檔如下,localIdentName 是指產生名稱的規則,點此看完整檔案。
{
loader: 'css-loader',
options: {
// 省略
localIdentName: '[path]___[name]__[local]___[hash:base64:5]',
},
},
.babelrc 設定檔如下,在 plugins 加入 react-css-modules,點此看完整檔案。
"plugins": [
// 省略
"react-css-modules"
]
樣式撰寫
樣式撰寫方面,必須以 :global
或 :local
作為產生全域或區域樣式的記號。
若在 class 前加入 :global 會產生全域的樣式。
:global .align-row {
...
}
產出結果。
.align-row {
...
}
若在 class 前加入 :local 會產生區域的樣式。
:local .slideshow {
...
}
產出結果,前面加上 path 和 component name,最後加入的亂亂的 hash。
.style-components-___slideshow__slideshow___2jOdi {
...
}
若引用多個樣式檔且含有相同 class name,可用 Named styleName resolution 解決,也就是使用時增加 style object 來辨別。
import foo from 'foo.css';
import bar from 'bar.css';
<div styleName="foo.a"></div>
<div styleName="bar.a"></div>
備註,這裡的程式碼和官方範例檔不同,而不同之處在於官方範例預設是 local 而本文預設是 global。這是由於 css-loader 的 modules 的設定不同所致,當 modules 為 true 時,babel-plugin-react-css-modules 的預設就是 local,即官網範例與說明所呈現的樣子,設定可參考這裡。但本文的 modules 是採用預設的狀態,也就是 false,所以 babel-plugin-react-css-modules 預設就是 global 了。感謝網友托尼的夏天,請見討論串。
總結
優點
- 關注點分離,JS 和 CSS 分開寫。這其實是使用 CSS Modules 的優點,也是我選擇這個方案的最大誘因。
- 滿足同時擁有全域和區域 CSS 的需求,這對想用全站通用的樣式或整合第三方套件很有幫助;並且,比起 react-css-modules 更來得輕巧方便,像是不用強制使用 camelCase 命名、不需重複使用 style object、利用 styleName 即可區分區域樣式等。
- 若比較載入效能,babel-plugin-react-css-modules 的表現較 react-css-modules 來得好,原因是 react-css-modules 對於 styleName 的解析是在執行時期(by HOC),但 babel-plugin-react-css-modules 將此提前到 Webpack 編譯與打包的時期來做的緣故。
缺點
- 必須在 CSS 中標註
:global
和:local
,這比較麻煩,稍微增加管理和偵錯的複雜度。
疑難雜症
至於研究過程解答了一些過去的疑惑,在這裡提供找到的解法…
若想使用 stylelint 這類的檢查工具…
問:是不是只有在 JS 和 CSS 分開寫的狀況下才能使用 stylelint 這類的檢查工具?CSS in JS 不能用嗎?
答:還是有工具支援的,可使用-stylelint-processor-styled-components。
擔心無法使用 CSS 的完整功能…
問:是不是只有在 JS 和 CSS 分開寫的狀況下才能使用 CSS 的完整功能?CSS in JS 不能用嗎?例如:Media Query、Pseudo Class(:hover?)。
答:這裡列出 Styled Components 對於 Media Query 和 Pseudo Class 的解法,現在連巢狀結構都可以用了。