在瞬息萬變的前端開發世界中,很難找到一個真正有意義的概念,並且將其清晰明瞭的向廣大人民群眾普及。
把目光投向CSS,一個重大轉折就是CSS前處理器的出現(在工具方面來看),其中,Sass應該是最為著名的一個。此外,還有 PostCSS,它和Sass略有不同,但是殊途同歸——都是用瀏覽器不能解析的語法編寫,並且最終編譯成瀏覽器能夠理解的語法。
現在,又有一位新的成員出現了,它就是CSS模組。本文就將介紹CSS模組化的諸多優點,以及如何編寫模組化的CSS。
什麼是CSS模組
首先,讓我們從官方文件入手:
A CSS Module is a CSS file in which all class names and animation names are scoped locally by default. CSS模組就是所有的類名都只有區域性作用域的CSS檔案。
事實稍微有一些複雜。由於類名需要預設具有區域性作用域,這就涉及到一些初始設定、一個編譯過程,以及其他一些難以琢磨的東西。
但是最終,我們會為CSS模組化帶來的好處而開心:CCS模組將作用域限制於元件中,從而避免了全域性作用域的問題。我們再也不用操心為元件尋找一個好的命名了,因為編譯過程已經幫你完成了這個任務。
它是如何工作的
CSS模組需要在構建步驟進行管道化,這也就是說,它不是自動驅動的。它可以看成是webpack 或 Browserify的一個外掛。其基本工作方式是:當你在一個JavaScript模組中匯入一個CSS檔案時(例如,在一個 React 元件中),CSS模組將會定義一個物件,將檔案中類的名字動態的對映為JavaScript作用域中可以使用的字串。舉個具體的例子:
如下是一個簡單的CSS檔案。其中,.base
類名不需要是工程中唯一的,因為它將不會是真正被解析的類名。它可以看成是在JavaScript模組中使用的類在樣式表中的別名。
1 2 3 4 5 |
.base { color: deeppink; max-width: 42em; margin: 0 auto; } |
下面是該CSS類在JavaScript元件中的使用方式:
1 2 3 4 5 |
import styles from './styles.css'; element.innerHTML = `<div class="${styles.base}"> CSS Modules are fun. </div>`; |
最終,它將生成下面這個東西(當使用webpack的預設步驟時):
1 |
`<div class="_20WEds96_Ee1ra54-24ePy">CSS Modules are fun.</div>` |
1 2 3 4 5 |
._20WEds96_Ee1ra54-24ePy { color: deeppink; max-width: 42em; margin: 0 auto; } |
當然,生成的類名可以通過配置,使得它的長度更短或者遵循一些特定的模式。當然了,這些最終都不重要(雖然短的類名意味著更短的樣式表),重點在於這些類名是動態生成的、唯一的且和正確的樣式表一一對應的。
一些需要注意的地方
這就是CSS模組工作的方式了。這時,你可能會想,“這到底是個什麼玩意兒,我甚至。。。”。OK,停下!我知道你想說什麼。現在就讓我一一解答你可能有的疑慮。
這看起來太醜了
確實如此。但是類名並不要求一定要長的好看對不對?只要可以將樣式正確的應用於元素就可以了嘛。而CSS模組化方法完成的非常好,所以我覺得,這不是一個問題。
這非常難debug啊
由於需要有一個編譯的步驟,所以直接debug是非常困難的。其實,像Sass直接debug也是相當不容易的,所以我們才有了 sourcemaps。對於CSS模組,我們也可以設定sourcemap。
其實,我還想說的是,雖然在模組中,類的名字是自動生成而不可預知的,但是對於模組來說,它還是比樣式表更容易debug的。只要你知道當前在開發者工具中檢視的樣式屬於哪個模組,在相應的樣式表中也是很容易定位。
這使得樣式不容易複用啦!
這句話既對也不對。一方面來說,確實如此。但這是因為模組將CSS樣式和元件相繫結,從而不會發生全域性樣式的衝突。這其實是一件好事,我相信你也會同意的對不對。
另一方面,要定義全域性樣式也是可以的,只要使用:global()
就好了。比如,作者需要保留的全域性輔助樣式。
1 2 3 4 5 |
:global(.clearfix::after) { content: ''; clear: both; display: table; } |
CSS模組還可以從其他模組中繼承樣式,這和Sass中的@extend
方法其實是一樣的。它不會拷貝樣式,只是將選擇器連線到繼承的樣式中。
1 2 3 |
.base { composes: appearance from '../AnoherModule/styles.css'; } |
它需要webpack,Browserify或者其他工具!
這和Sass需要將.scss
檔案編譯成CSS檔案,PostCSS需要將樣式表處理成瀏覽器能夠識別的樣式其實是一樣的。無論如何,都需要一個構建步驟。
我們究竟為什麼要討論這個東西?
其實,我甚至不確定CSS模組在未來到底會不會繼續存在,不過,我確定這是一種編寫樣式的正確方式。試想,在拆分成許多細小元件的龐大站點中,卻擁有一個臃腫的全域性樣式表,這肯定是不合適的。
CSS統一的名空間使得它既強大又脆弱。而CSS模組化或者未來延續這個思想的其他工具可以在支援樣式複用的同時,避免命名衝突,這是一個雙贏的選擇。
入門
如前面所說的,你需要有webpack或者Browserify來實現CSS模組化。
Webpack
先從webpack版本的模組化開始。在webpack.config.js
中,加上如下配置,使得webpack將CSS檔案作為CSS模組來看待:
1 2 3 4 |
{ test: /\.css$/, loader: 'style-loader!css-loader?modules' } |
這時,它將把樣式注入到頁面中的元素中。這可能不是我們想要的,使用extract text plugin for webpack,我們可以很方便的抽取出樣式表:
1 2 3 4 |
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?modules') } |
對於webpack,要講的就是這麼多了。
Browserify
我只在命令列中用過Browserify,所以我猜使用起來會更復雜一些。在package.json
檔案中,加入 npm script :
1 2 3 4 5 |
{ "scripts": { "build": "browserify -p [ css-modulesify -o dist/main.css ] -o dist/index.js src/index.js" } } |
這條命令告訴Browserify執行src/index.js
,返回dist/index.js
,並且使用 css-modulesify將樣式表編譯至dist/main.css
。如果你想再加上Autoprefixer,那麼命令可以寫成這樣:
1 2 3 4 5 |
{ "scripts": { "build": "browserify -p [ css-modulesify --after autoprefixer -o dist/main.css ] -o dist/index.js src/index.js" } } |
如你所見,使用--after
選項可以在編譯完成樣式表時候,繼續對它進行處理。
### 閱讀更多
- Setting up a Living Styleguide in Jekyll
- JavaScript Functional Testing with Nightwatch.js
- SitePoint’s Tiles: A Case Study in Components, Theming and Flexbox
總結
從今天看來,CSS模組化系統和生態確實有些原始了,從Browserify中的配置就能看出來。不過,我確信CSS模組化將變得更好,並且越來越多的人將意識到不管對小專案還是大專案來說,這都是一個很好的方法。
我認為CSS模組化背後的思想是正確的。當然,我不是說這個庫就是最佳解決方案,但是,它確實包含了一些CSS應該採用的寫法:模組化、作用域隔離、同時支援複用。
最後,我向大家推薦專案作者Glen Maddern的文章 this introduction to CSS Modules。