CSS Modules實踐

Pines_Cheng發表於2017-07-22

文章同步於Github Pines-Cheng/blog

隨著前端這幾年的風生水起,CSS作為前端的三劍客之一,各種技術方案也是層出不窮。從CSS prepocessor(SASS、LESS、Stylus)到後來的後起之秀 PostCSS,再到 CSS ModulesStyled-Component 等。有人維護了一份完整的 CSS in JS 技術方案的對比,裡面已經有將近50種技術方案。CSS Modules就是其中一種。

CSS Modules 介紹

要弄懂CSS Modules是什麼,可以先看官方介紹:GitHub – css-modules/css-modules: Documentation about css-modules

通過上面介紹可以看出,CSS Modules既不是官方標準,也不是瀏覽器的特性,而是在構建步驟(例如使用Webpack或Browserify)中對CSS類名選擇器限定作用域的一種方式(通過hash實現類似於名稱空間的方法)。例如我們在buttons.js裡引入buttons.css檔案,並使用.btn的樣式,在其他元件裡是不會被.btn影響的,除非它也引入了buttons.css.

CSS模組化

JS已經全面實現了模組化,但是css還處於探索階段。為什麼我們需要css模組化?主要由一下幾個原因。

CSS全域性作用域問題

CSS的規則都是全域性的,任何一個元件的樣式規則,都對整個頁面有效。現在的前端工程大多是基於元件開發,隨著工程的頁面數量好複雜度的提升,相信寫css的人都會遇到樣式衝突(汙染)的問題。一般我們會採用一下幾種方法:

  • class命名寫長一點吧,降低衝突的機率

  • 加個父元素的選擇器,限制範圍

  • 重新命名個class吧,比較保險

可是以上方案只是降低了全域性衝突的概率,並不能徹底解決全域性衝突的問題。並且,實現方式也不夠優雅,還增加了程式碼的複雜和冗餘。

我們的追求

  • 面向元件開發 : 處理 UI 複雜性的最佳實踐就是將 UI 分割成一個個的小元件,React 就鼓勵高度元件化和分割。我們希望有一個 CSS 架構去匹配。

  • 沙箱化(Sandboxed) : 如果一個元件的樣式會對其他元件產生不必要以及意想不到的影響,那麼將 UI 分割成元件並沒有什麼用。就這方面而言,CSS的全域性作用域會給你造成負擔。

  • 方便 :不會增加開發的負擔和程式碼的冗餘。

方案

CSS 模組化的解決方案有很多,但主要有三類。

CSS 命名約定

規範化CSS的模組化解決方案(比如BEM BEM — Block Element Modifier ,OOCSS,AMCSS,SMACSS,SUITCSS)
但存在以下問題:

  • JS CSS之間依然沒有打通變數和選擇器等

  • 複雜的命名

CSS in JS

徹底拋棄 CSS,用 JavaScript 寫 CSS 規則,並內聯樣式。styled-components 就是其中代表。styled-components可以讓CSS真正意義地寫到JS裡面,同時讓標籤更具有語意化,這跟HTML5新標籤思想相同;該框架讓樣式也具備元件化思想,讓前端完全面向元件化程式設計,就像java的包裝型別。
但存在以下問題:

  • 樣式程式碼也會出現大量重複。

  • 不能利用成熟的 CSS 前處理器(或後處理器)

使用 JS 來管理樣式模組

使用JS編譯原生的CSS檔案,使其具備模組化的能力,代表是 CSS Modules。

CSS Module還是JS和CSS分離的寫法,不會改變大家的書寫習慣,CSS Module只需修改構建程式碼和使用模組依賴引入className的方式即可使用,且支援less和sass的語法,

使用CSS Modules可以讓元件className控制權轉交給JS,我們不會去關心命名衝突汙染等問題,同時可以靈活控制生成的命名,樣式程式碼不用修改即可讓使用CSS語法的舊專案零成本接入。

CSS Modules 能最大化地結合現有 CSS 生態(前處理器/後處理器等)和 JS 模組化能力,幾乎零學習成本。只要你使用 Webpack,可以在任何專案中使用。是目前最好的 CSS 模組化解決方案。

使用

配置

CSS Modules配置非常簡單,如果你使用webpack,只需要在配置檔案中改動一行即可。

// webpack.config.js
css?modules&localIdentName=[name]__[local]-[hash:base64:5]
 

加上 modules 即為啟用,localIdentName 是設定生成樣式的命名規則。

編碼

css

/* components/Button.css */
.normal { /* normal 相關的所有樣式 */ }
 

js

// components/Button.js
import styles from `./Button.css`;
console.log(styles);
buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

上例中 console 列印styles的結果是:

Object {
  normal: `button--normal-abc53`,
  disabled: `button--disabled-def886`,
}
 

注意到 button–normal-abc53 是 CSS Modules 按照 localIdentName 自動生成的 class 名。其中的 abc53 是按照給定演算法生成的序列碼。經過這樣混淆處理後,class 名基本就是唯一的,大大降低了專案中樣式覆蓋的機率。同時在生產環境下修改規則,生成更短的 class 名,可以提高 CSS 的壓縮率。

CSS Modules 對 CSS 中的 class 名都做了處理,使用物件來儲存原 class 和混淆後 class 的對應關係。

React實踐

手動引用

import React from `react`;
import styles from `./table.css`;
 
export default class Table extends React.Component {
    render () {
        return <div className={styles.table}>
            <div className={styles.row}>
            </div>
        </div>;
    }
}
 

渲染結果:

<div class="table__table___32osj">
    <div class="table__row___2w27N">
    </div>
</div>

使用babel-plugin-react-css-modules

babel-plugin-react-css-modules 可以實現使用styleName屬性自動載入CSS模組。只需要把className換成styleName即可獲得CSS區域性作用域的能力,babel外掛來自動進行語法樹解析並最終生成className。改動成本極小,不會增加JSX的複雜度,也不會給專案帶來額外的負擔。

import React from `react`;
import styles from `./table.css`;
 
class Table extends React.Component {
    render () {
        return <div styleName=`table`>
        </div>;
    }
}
 
export default Table;
 

CSS Modules 很好的解決了 CSS 目前面臨的模組化難題。支援與 CSS處理器搭配使用,能充分利用現有技術積累。如果你的產品中正好遇到類似問題,非常值得一試。

參考

相關文章