不要誤會,CSS Modules可不是在說「css模塊化」這個好像在某些地方見過的詞,它實際上是特指一種近期纔出現的技術手段。javascript
什麼技術手段呢?請待後文說明。css
咱們知道,css的全名叫作層疊樣式表,這個「層疊」究竟是什麼意思呢?html
有一種解釋是,若是你先寫了一條樣式規則(選手1):java
.title { color: silver; }
而後又在後邊寫了一條相似的(選手2):react
.title { color: gold; }
由於名字相同,選手2就會和選手1打起來(讓你丫冒充我!)。結果是選手2獲勝,class名爲title
的元素,最終的color
值爲gold
。webpack
css裏就像這樣,隨時可能一言不和就發生戰爭,結果輸掉的一方就會被勝利的一方所覆蓋。「層疊」一詞能夠說形象地描述了這個過程。git
那麼,爲何會有這樣的層疊(zhàn zhēng )呢?github
在javascript裏,能夠作到這樣的搭配:web
var title = "silver"; (function(){ var title = "gold"; console.log(title); // gold }()); console.log(title); // silver
利用javascript的函數做用域,兩位一樣名爲title
的選手能夠友好相處。sass
但回到css裏的樣式規則,狀況就徹底不是這麼回事了。
css不是程序語言,但若是說要給它加一個做用域的概念的話,那就是:只有全局做用域。
不管分拆爲多少個css文件,不管用怎樣的方式引入,全部的樣式規則都位於同一做用域,只要選擇符近似,就有發生覆蓋的可能。
爲減小相互影響,避免預料以外的樣式覆蓋,咱們一直以來想過不少辦法。
好比你接手一個別人留下來的舊項目,接下來要新增一個標題元素的時候,你會有意識地不去使用.title
這樣模糊的class名,由於它太容易重名了。最終,你用的名稱多是:
.module-sp-title { color: deepskyblue; }
即便你決定要用.title
這個名字,你也會加上包含選擇符做爲限定:
.module-1 .title { font-size: 18px; } /* ... */ .module-2 .title { font-size: 14px; }
其中.module-1
和.module-2
的名字應該是惟一的,這樣的代碼在組件化(模塊化)的開發風格里很常見。
此外,一些有名的css理論,如SMACSS,會建議你爲全部佈局樣式使用l-
或layout-
的前綴,以示區分。
相似的作法還有不少,但歸結起來,都是在嘗試提供一種合理的命名約定。而合理的命名約定,的確是組織css代碼的有效策略。
如今,咱們有了新的可用策略,CSS Modules就是其中之一。
CSS Modules是一種技術流的組織css代碼的策略,它將爲css提供默認的局部做用域。
CSS Modules是如何作到的呢?來看一個CSS Modules的簡單例子吧。
有這樣的一個html元素:
<h2 id="example_title" class="title">a title for CSS Modules</h2>
按照普通css的寫法,咱們能夠這樣爲它添加樣式:
.title { background-color: snow; }
如今咱們改用CSS Modules。首先,css保持不變。而後,修改html的寫法。再也不這樣直接寫html,而是改成在javascript文件裏動態添加,這樣作(css文件名爲main.css
):
var styles = require("./main.css"); var el = document.getElementById("example_title"); el.outerHTML = '<h2 class="' + styles.title + '">a title for CSS Modules</h2>';
咦,require
了一個css文件?對的,因此要用到webpack。編譯後,html和css會變成這樣:
看到這樣不太美觀的class名你大概就明白了,CSS Modules沒法改變css全局做用域的本性,它是依靠動態生成class名這一手段,來實現局部做用域的。顯然,這樣的class名就能夠是惟一的,無論本來的css代碼寫得有多隨便,均可以這樣轉換獲得不衝突的css代碼。
模擬的局部做用域也沒有關係,它是可靠的。
這個CSS Modules的例子說完了,但你必定跟我最初看到的時候同樣有不少問題。
「webpack編譯css我也用過,怎麼我用的時候不長這樣?」
通常來講,require
一個css文件的寫法是:
require("./main.css");
但在前面的例子中,用了var styles = require("./main.css");
的寫法。這就好像是在說,我要這個css文件裏的樣式是局部的,而後我根據須要自行取用。
在項目裏應用CSS Modules有不少方法,目前比較經常使用的是使用webpack的css-loader。在webpack配置文件裏寫css-loader?modules
就能夠開啓CSS Modules,例如前面的例子所用的:
module: { loaders: [{ test: /\.css$/, loader: 'style!css?modules' }] }
才發現一直用着的css-loader原來有這功能?其實,CSS Modules確實是一個後來才併入css-loader的新功能。
「名字都這樣了,還怎麼調試?」
爲css-loader增長localIdentName
參數,是能夠指定生成的名字。localIdentName
的默認值是[hash:base64]
,通常開發環境建議用相似這樣的配置:
{ test: /\.css$/, loader: 'style!css?modules&localIdentName=[name]__[local]___[hash:base64:5]' }
一樣應用到前面的例子裏,這時候就會變成這樣的結果:
這樣是否是要有意義多了?
若是是線上環境,能夠考慮用更短的名字進一步減少css文件大小。
(看了前面例子裏的el.outerHTML = ...
後)
「什麼,outerHTML?class名還要拼接?你家html才這麼寫呢!」
很遺憾,CSS Modules官方的例子,也是這個意思:要使用CSS Modules,必須想辦法把變量風格的class名注入到html中。也就是說,html模板系統是必需的,也正是如此,相比普通css的狀況,CSS Modules的html寫起來要更爲費勁。
若是你搜一下CSS Modules的demo,能夠發現大部分都是基於React的。顯然,虛擬DOM風格的React,搭配CSS Modules會很容易(ES6):
import styles from './ScopedSelectors.css'; import React, { Component } from 'react'; export default class ScopedSelectors extends Component { render() { return ( <div className={ styles.root }> <p className={ styles.text }>Scoped Selectors</p> </div> ); } };
若是不使用React,仍是那句話,只要有辦法把變量風格的class名注入到html中,就能夠用CSS Modules。原始的字符串拼接的寫法顯然很糟糕,但咱們能夠藉助各類模板引擎和編譯工具作一些改進。下面請看一個用Jade的參考示例。
想象一下你有一個用普通css的頁面,但你想在一小塊區域使用CSS Modules。這一塊區域在一個容器元素裏:
<div id="module_sp_container"></div>
而後用jade來寫html(關聯的css文件爲module_sp.css
):
- styles = require("./module_sp.css"); h2(class=styles.title) a title for CSS Modules
接下來,仍然是在javascript裏添加這段jade生成的html:
var el = document.getElementById("module_sp_container"); var template = require("./main.jade"); el.innerHTML = template();
最後,記得在css-loader啓用CSS Modules的同時,增長jade-loader:
{ test: /\.jade$/, loader: 'jade' }
編譯運行,就能夠獲得想要的結果。除Jade之外,還有些其餘CSS Modules的html應用方案,推薦參考github上的這篇issue。
目前CSS Modules還在發展中,並且也在考慮改進CSS Modules下的html寫做體驗。CSS Modules團隊成員有提到一個叫CSS Modules Injector的將來規劃項目,目的是讓開發者不用javascript也可使用CSS Modules(這就很接近原生html + css的組合了)。
「樣式都是惟一的了,怎麼複用?」
咱們已經說了挺多普通css單個全局做用域的壞處。但對應的,這也有一個很大的好處,就是便於實現樣式的複用。css理論OOCSS也是在追求這一點。
CSS Modules提供一個composes
方法用於樣式複用。例如,你有一個btn.css
裏有一條:
.btn{ display: inline-block; }
而後,你在另外一個CSS Module的module_sp.css
裏能夠這樣引入它:
.btn-sp{ composes: btn from "./btn.css"; font-size: 16px; }
那麼,這個div.btn-sp
的DOM元素將會是:
能夠看到,composes
的用法比較相似sass的@extend
,但不一樣於@extend
的是,composes
並不增長css裏的選擇符總量,而是採用組合多個class名的形式。在這個例子裏,本來僅有1個class的div.btn-sp
,變成了2個class。
所以,CSS Modules建議只使用1個class就定義好對應元素所需的所有樣式。它們會再由CSS Modules轉換爲適當的class組合。
CSS Modules團隊成員認爲composes
是CSS Modules裏最強大的功能:
For me, the most powerful idea in CSS Modules is composition, where you can deconstruct your visual inventory into atomic classes, and assemble them at a module level, without duplicating markup or hindering performance.
更詳細的composes
的用法及其理解,推薦閱讀CSS Modules: Welcome to the Future。
不少項目會引入Bootstrap、Materialize等框架,它們是普通的、全局的css。此外,你也可能本身會寫一些普通css。如何共存呢?CSS Modules團隊成員對此提到過:
a CSS Module should only import information relative to it
意思是,建議把CSS Modules看作一種新的css,和原來的普通css區分開來。好比,composes
的時候,不要從那些普通的css裏去取。
在css-loader裏經過指定test
、include
、exclude
來區分它們。保持CSS Modules的純淨,只有想要應用CSS Modules的css文件,才啓用CSS Modules。
通過我本身的測試,CSS Modules只轉換class和id,此外的標籤選擇符、僞類等都不會被轉換。
建議只使用class。
簡單用console.log()
就能夠查看CSS Module的輸出:
var styles = require("./main.css"); console.log("styles = ", styles);
結果相似這樣:
{ "btn-sp": "_2SCQ7Kuv31NIIiVU-Q2ubA _2r6eZFEKnJgc7GLy11yRmV", title: "_1m-KkPQynpIso3ofWhMVuK" }
這能夠幫助理解CSS Modules是怎樣工做的。
sass等預編譯器也能夠用CSS Modules,對應的loader多是這樣:
{ test: /\.scss$/, loader: 'style!css?modules!resolve-url!sass?sourceMap' }
注意不要由於是sass就習慣性地用嵌套寫法,CSS Modules並不適合使用包含選擇符。
CSS Modules會把.title
轉換爲styles.title
,因爲後者是用在javascript中,所以駝峯命名會更適合。
若是像我以前那樣寫.btn-sp
,須要注意在javascript中寫爲styles["btn-sp"]
。
此外,你還能夠爲css-loader增長camelCase
參數來實現自動轉換:
{ test: /\.css$/, loader: 'style!css?modules&camelCase', }
這樣即使你寫.btn-sp
,你也能夠直接在javascript裏用styles.btnSp
。
不管是一直以來咱們認真遵循的命名約定,仍是這個新的CSS Modules,目的都是同樣的:可維護的css代碼。我以爲就CSS Modules基本仍是在寫css這一點來講,它仍是很友好的。
雖然本文爲了嚴謹,結果寫了至關長的篇幅,但但願你讀過以後,還能以爲CSS Modules是簡單易懂的。由於這樣,我就達成個人目的:扣題,了。
(從新編輯自個人博客,原文地址:http://acgtofe.com/posts/2016/04/css-modules-made-simple)