Glen Maddern: CSS 模塊

CSS 模塊

原文:http://glenmaddern.com/articles/css-modules

歡迎來到未來

  • 2015-08-19

假如你想要弄清楚在最近 CSS 思維發(fā)展中的拐點(diǎn) ,很有可能你就會挑選 Christopher Chedeau 在去年十一月份 “CSS in JS” 的講話。這是一個(gè)分水嶺,如同經(jīng)歷過高度碰撞后的粒子般急速前進(jìn),從原本的方向中確立了不同的思想分支。例如,React Style, jsxstyleRadium 是目前用來設(shè)計(jì) React 樣式及其與之相關(guān)的項(xiàng)目的方法中最新,最聰明和可行的方法。如果創(chuàng)新是用來探索臨近可能 adjacent possible 的其中一種情形,那么 Christopher 則會靠近更多的可能性。

Christopher Chedeau's 7 problems with CSS at scale

這一頁幻燈片讓很多人有一種似曾相識的感覺。

這里全都是從一方面以上影響大部分 CSS 代碼庫的合法性問題, Christopher 指出只要你愿意把樣式放進(jìn) JavaScript,這些都能得到很好的解決,盡管如此但是它也擁有自己的復(fù)雜之處和特質(zhì)??纯次抑疤岬降捻?xiàng)目中處理 :hover 狀態(tài)的一系列辦法,而在 CSS 里面已經(jīng)早就解決了。

CSS Modules team 覺得我們可以跟問題死磕,保持我們所喜歡的 CSS 并在 styles-in-JS 社區(qū)的佳作上繼續(xù)創(chuàng)作。所以,我們在看好我們自身的方法并堅(jiān)定捍衛(wèi) CSS 優(yōu)點(diǎn)的同時(shí),也由衷的感謝在其他方向不斷有所突破的人。謝謝!

讓我來告訴你們?yōu)槭裁?CSS 模塊化是未來。

This is how intensely we’ve been thinking about CSS.

我們是這么描述 CSS 模塊的

第一步 本地默認(rèn)

在 CSS 模塊中,每一個(gè)文件都是單獨(dú)編譯的,所以你可以用一些簡單的類名選擇器和通用名稱,而不必?fù)?dān)心污染全局變量、比方說,我們正在構(gòu)建一個(gè)簡單的提交按鈕具有下列 4 種狀態(tài):正常、不可用、錯(cuò)誤、處理中。

開始CSS模塊之前

我們也許會這樣寫代碼,使用 普通古老的 CSS 和 HTML 的 Suit/BEM 風(fēng)格的類名:

/* components/submit-button.css */  
.Button  { /* all styles for Normal */ }  
.Button--disabled  { /* overrides for Disabled */ }  
.Button--error  { /* overrides for Error */ }  
.Button--in-progress  { /* overrides for In Progress */
<button class="Button Button--in-progress">Processing...</button>

這看起來確實(shí)挺好的,我們有這四種變體,然而 BEM 風(fēng)格的命名意味著我們不用沒有了可以嵌套的選擇器。我們用大寫字母開頭的 Button 避免前置的樣式或我們放進(jìn)的依賴。并且我們采用 --modifier 的類型所以我們可以清楚這個(gè)變體是需要基礎(chǔ)類名來應(yīng)用的。

總之,這是合理明確并且可維護(hù)的代碼,但是這需要對圍繞命名規(guī)范有可怕的認(rèn)知理解。然而,這是我們在標(biāo)準(zhǔn)的 CSS 所能做的最好的了。

利用 CSS 模塊

CSS模塊以為這你從不需要擔(dān)心你的命名空間變得普遍,就用在任何覺得有意義的地方就可以了。

/* components/submit-button.css */
.normal { /* all styles for Normal */ }
.disabled { /* all styles for Disabled */ }
.error { /* all styles for Error */ }
.inProgress { /* all styles for In Progress */

注意到我們并不到處使用 "button" 這個(gè)詞。為什么呢?這個(gè)文件早就命名為 "submit-button.css",在其他的的語言里,你并不需要去對擁有命名空間的本地變量進(jìn)行預(yù)處理,CSS 當(dāng)然也是。

這就讓 CSS 模塊編譯的方式變得可能 - 通過使用 requireimport 從 JavaScript 中加載這些文件:

/* components/submit-button.js */
import styles from './submit-button.css';

buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

實(shí)際上命名空間是自動生成并且唯一的。 CSS 模塊讓已經(jīng)為你考慮好了,并且編譯文件為 ICSS閱讀我之前的博客 的格式,介紹了 CSS 和 JS 是如何溝通的。所以,當(dāng)你運(yùn)行應(yīng)用的時(shí)候,會看到下面的東西:

<button class="components_submit_button__normal__abc5436">
  Processing...
</button>

如果你在 DOM 看到這些東西,說明已經(jīng)成功了!

你是大猩猩,CSS 模塊是鯊魚

命名約定

再回來思考我們按鈕的例子:

/* components/submit-button.css */
.normal { /* all styles for Normal */ }
.disabled { /* all styles for Disabled */ }
.error { /* all styles for Error */ }
.inProgress { /* all styles for In Progress */

注意到所有的類型都是獨(dú)立的,與其一部分變成“基本類”,剩下的部分變成“覆蓋類”。在 CSS 模塊里,任何一個(gè)類名都需要對應(yīng)改變量的所有樣式(不止是短期的用處)。在 JavaScript 中,使用這些樣式會大有不同:

/* Don't do this */
`class=${[styles.normal, styles['in-progress']].join(" ")}`

/* Using a single name makes a big difference */
`class=${styles['in-progress']}`

/* camelCase makes it even better */
`class=${styles.inProgress}`

一個(gè) React 的例子

React 陣營本身和 CSS 模塊并沒有什么聯(lián)系。但是 React 卻提供了一個(gè)使用 CSS 模塊絕佳的經(jīng)歷,所以展示一個(gè)比較復(fù)雜的例子是值得的:

/* components/submit-button.jsx */
import { Component } from 'react';
import styles from './submit-button.css';

export default class SubmitButton extends Component {
  render() {
    let className, text = "Submit"
    if (this.props.store.submissionInProgress) {
      className = styles.inProgress
      text = "Processing..."
    } else if (this.props.store.errorOccurred) {
      className = styles.error
    } else if (!this.props.form.valid) {
      className = styles.disabled
    } else {
      className = styles.normal
    }
    return <button className={className}>{text}</button>
  }
}

在使用你自己樣式的時(shí)候,你可以不必?fù)?dān)心產(chǎn)生一個(gè)全局安全的 CSS 變量名,這樣會讓你更專注于組件而非樣式。并且一旦擺脫了這種持續(xù)的上下文切換,你會對于曾經(jīng)你的忍受感到驚訝。

第二步 組成是一切

早些時(shí)候我提過,每一個(gè)類名應(yīng)該包含一個(gè)按鈕不同狀態(tài)的所有樣式,而在 BEM 樣式里面你必須假設(shè)它不止有一個(gè)類名:

/* BEM Style */
innerHTML = `<button class="Button Button--in-progress">`

/* CSS Modules */
innerHTML = `<button class="${styles.inProgress}">`

等等,但是你要怎樣代表所有狀態(tài)共有的樣式?答案是 CSS 模塊最為給力的功能,組成 (composition):

.common {
  /* all the common styles you want */
}
.normal {
  composes: common;
  /* anything that only applies to Normal */
}
.disabled {
  composes: common;
  /* anything that only applies to Disabled */
}
.error {
  composes: common;
  /* anything that only applies to Error */
}
.inProgress {
  composes: common;
  /* anything that only applies to In Progress */
}

composes 關(guān)鍵詞說明 .normal 包括了所有來自 .common 樣式,很像 Sass 里面的 @extend 關(guān)鍵詞。但是 Sass 會重寫你的 CSS 選擇器達(dá)到這個(gè)目的, 而 CSS 模塊會改變導(dǎo)出到 JavaScript 的類名

在 Sass

讓我們舉一個(gè) BEM 的例子,并且應(yīng)用一些 Sass 的 @extend


.Button--common { /* font-sizes, padding, border-radius */ }
.Button--normal {
  @extends .Button--common;
  /* blue color, light blue background */
}
.Button--error {
  @extends .Button--common;
  /* red color, light red background */
}

編譯到 CSS:


.Button--common, .Button--normal, .Button--error {
  /* font-sizes, padding, border-radius */
}
.Button--normal {
  /* blue color, light blue background */
}
.Button--error {
  /* red color, light red background */
}

你可以在你的標(biāo)簽 <button calss="Button--error"> 只用一個(gè)類名來獲得你想要的公有 & 特殊的樣式。這是一個(gè)非常強(qiáng)大的概念,但是實(shí)踐起來卻有一些你需要注意的邊緣的案例 & 陷阱。感謝 Hugo Giraudel 這里有一個(gè)很好的問題總結(jié)和鏈接。

包含 CSS 模塊

composes 關(guān)鍵詞在概念上類似于 @extends,但是執(zhí)行起來是不相同。為了證明,先看一個(gè)例子:

.common { /* font-sizes, padding, border-radius */ }
.normal { composes: common; /* blue color, light blue background */ }
.error { composes: common; /* red color, light red background */ }

到達(dá)瀏覽器之后看起來會像下面這個(gè)樣子:

.components_submit_button__common__abc5436 { /* font-sizes, padding, border-radius */ }
.components_submit_button__normal__def6547 { /* blue color, light blue background */ }
.components_submit_button__error__1638bcd { /* red color, light red background */ }

在你的 JS 代碼里面, import styles from "./submit-button.css 返回:


styles: {
  common: "components_submit_button__common__abc5436",
  normal: "components_submit_button__common__abc5436 components_submit_button__normal__def6547",
  error: "components_submit_button__common__abc5436 components_submit_button__error__1638bcd"
}

所以我們?nèi)匀豢梢栽谖覀兊拇a里面使用styles.normal 或者 styles.error , 我們會在渲染完的 DOM 里面獲得不同的類


<button class="components_submit_button__common__abc5436 
               components_submit_button__normal__def6547">
  Submit
</button>

這就是 composes 的力量,你可以組合不同的獨(dú)立樣式組,而無需改變你的標(biāo)簽并且重寫你的 CSS 選擇器??。

第三步 在不同的文件共享

使用 Sass 或者 Less,每一個(gè)你 @import 的文件會在同一個(gè)全局工作區(qū)被處理。這就決定了你在一個(gè)文件如何定義變量和混合塊并在你所有的組件文件使用。這很實(shí)用,然而一旦你的變量名與其他的變量名產(chǎn)生了沖突(由于它們共享了同樣的命名空間),你會不可避免的重構(gòu)一個(gè) variables.scss 或者 settings.scss,并且對于你來說,哪個(gè)模塊依賴哪個(gè)變量變得不可見。而且你的 settings 文件會變得笨重

這里有一個(gè)更好的方法(實(shí)際上 Ben Smithett 的post about using Sass & Webpack together 在 CSS 模塊項(xiàng)目有一個(gè)直接的影響,我推薦你去讀一讀)但是你同樣會受限于 Sass 的全局命名。

CSS 模塊在單一的文件運(yùn)行一次,所以并沒有全局空間的污染。就像在 JavaScript 中我們可以 import 或者 require 我們的依賴, CSS 模塊也可以讓我們從其他的文件 compose

/* colors.css */
.primary {
  color: #720;
}
.secondary {
  color: #777;
}
/* other helper classes... */
/* submit-button.css */
.common { /* font-sizes, padding, border-radius */ }
.normal {
  composes: common;
  composes: primary from "../shared/colors.css";
}

使用組成(composes),我們得以使用命名普通的文件 color.css 并且使用它本地的名字來引用其中的一個(gè)類。由于組成改變了 exported 的類名而非 CSS 本身, composes 的聲明本身在到達(dá)瀏覽器之前就會從 CSS 本身刪除了:

/* colors.css */
.shared_colors__primary__fca929 {
  color: #720;
}
.shared_colors__secondary__acf292 {
  color: #777;
}
/* submit-button.css */
.components_submit_button__common__abc5436 { /* font-sizes, padding, border-radius */ }
.components_submit_button__normal__def6547 {}
<button class="shared_colors__primary__fca929
               components_submit_button__common__abc5436 
               components_submit_button__normal__def6547">
  Submit
</button>

實(shí)際上,在它到達(dá)瀏覽器的那一刻,我們本地名 “normal” 并沒有它自己的樣式。這是好事!因?yàn)檫@意味著我們可以使用一個(gè)新的具有本地意義的對象(一個(gè)稱為 “normal” 的實(shí)體)而無需添加新一行 CSS。我們越是能做到這一點(diǎn),蔓延在我們網(wǎng)站的視覺不一致以及到達(dá)瀏覽器之后的臃腫就會越少。

<small data-reactid=".0.1.1.1x.0">除此之外:這些空的類名可以很容易的被檢測并且被類似 csso 之類的檢查器刪除。 </small>

第四步 單一合理的模塊

組成是十分給力的,因?yàn)樗屇闳ッ枋鲆粋€(gè)元素而非它組成的樣式。這是另一種不同的概念上的實(shí)體到樣式的實(shí)體的映射。讓我們來看一看一個(gè) 樸素老舊的 CSS 的簡單例子:

.some_element {
  font-size: 1.5rem;
  color: rgba(0,0,0,0);
  padding: 0.5rem;
  box-shadow: 0 0 4px -2px;
}

這個(gè)元素以及樣式,簡單,然而卻有一個(gè)問題:colour, font-size, box-shadow, the padding,這些所有的東西都是有詳盡的細(xì)節(jié)規(guī)范的,即使我們想要在其他地方重用這些樣式。讓我們在 Sass 重構(gòu)一次:

$large-font-size: 1.5rem;
$dark-text: rgba(0,0,0,0);
$padding-normal: 0.5rem;
@mixin subtle-shadow {
  box-shadow: 0 0 4px -2px;
}

.some_element {
  @include subtle-shadow;
  font-size: $large-font-size;
  color: $dark-text;
  padding: $padding-normal;
}

這是一個(gè)進(jìn)步,但我們只提取了大多數(shù)行的一半。$large-font-size 是排版和 $padding-normal 是布局的事實(shí)不過是通過其名稱,而不是在任何地方執(zhí)行表示。當(dāng)有一個(gè)類似于 box-shadow 的聲明的值并不會讓它成為一個(gè)變量,我們不得不用一個(gè) @mixin@extends 來表示。

使用 CSS 模塊

通過使用組成,我們可以在可復(fù)用的部分聲明自己的組件。

.element {
  composes: large from "./typography.css";
  composes: dark-text from "./colors.css";
  composes: padding-all-medium from "./layout.css";
  composes: subtle-shadow from "./effect.css";
}

這種格式自然而然的會產(chǎn)生大量含有單一目的的文件,通過文件系統(tǒng)劃定不同的風(fēng)格而不是命名空間。加入你想要把不同的類型放在一個(gè)文件里,可以試一下下面的簡寫:

/* this short hand: */
.element {
  composes: padding-large margin-small from "./layout.css";
}

/* is equivalent to: */
.element {
  composes: padding-large from "./layout.css";
  composes: margin-small from "./layout.css";
}

這樣使得通過極端粒度的類名為你的網(wǎng)站上每一個(gè)視覺效果添加別名提供了可能性:

.article {
  composes: flex vertical centered from "./layout.css";
}

.masthead {
  composes: serif bold 48pt centered from "./typography.css";
  composes: paragraph-margin-below from "./layout.css";
}

.body {
  composes: max720 paragraph-margin-below from "layout.css";
  composes: sans light paragraph-line-height from "./typography.css";
}

這是一項(xiàng)我十分感興趣去探索的技術(shù)。在我看來,它結(jié)合了 Tachyons 原子 CSS 技術(shù),Semantic UI 的可讀性和獨(dú)立性等等最好的方面。

但我們現(xiàn)在只是在 CSS 模塊故事的開始,我們十分歡迎你在目前或者接下來的項(xiàng)目嘗試并與我們共創(chuàng)未來。

開始吧!

通過 CSS 模塊,我們希望我們可以幫你和你的團(tuán)隊(duì)保留你們現(xiàn)有盡可能多的 CSS 知識與產(chǎn)品,變得更為舒服和專業(yè)。我們已經(jīng)把語法添加到最低限度,努力確保有例子是接近你已經(jīng)工作的方式。我們在 Webpack, JSPM 以及 Browserify 都有示范項(xiàng)目,如果您使用的其中之一,我們總是在了望 CSS 模塊可以生效的新環(huán)境:服務(wù)器端支持的 NodeJS 正在 happening 而 Rails 正在初始。

為了然事情更為簡單,我在這里建了個(gè)例子而你無需安裝任何東西:

只要你準(zhǔn)備好了,可以去 CSS Modules 倉庫看一看,如果你有問題,請?zhí)峤?issue 來討論。CSS Modules team 規(guī)模較小,我們并不能知道所有的問題,所以希望能夠聽到你的想法。

Style happy, friends!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 編寫css是前端工作中,一項(xiàng)普通而又頻繁的勞動,由于css并不是一門語言,所以在程序設(shè)計(jì)上顯得有些簡陋。對于小型項(xiàng)...
    Jack_Lo閱讀 5,874評論 15 39
  • 再談CSS 預(yù)處理器2016-09-09 Justineo JavaScript轉(zhuǎn)自:http://efe.bai...
    抓住時(shí)間的尾巴吧閱讀 1,786評論 0 2
  • 在現(xiàn)在的前端開發(fā)中,前后端分離、模塊化開發(fā)、版本控制、文件合并與壓縮、mock數(shù)據(jù)等等一些原本后端的思想開始...
    Charlot閱讀 5,690評論 1 32
  • 今天參觀了一下萬科的萬物倉,發(fā)現(xiàn)還是挺有意思的,整個(gè)環(huán)境整潔干凈,配備空調(diào)和除濕系統(tǒng),門口有保安24小時(shí)保護(hù),給人...
    蔡明洲閱讀 2,803評論 0 0
  • 我有一位同齡的好伙伴,名叫阿寶。十八歲那年,他第一次遠(yuǎn)離家鄉(xiāng),去外省一個(gè)城市打工,遭遇不測,從此再也沒能回來。 那...
    向往森林閱讀 218評論 0 1

友情鏈接更多精彩內(nèi)容