全域性CSS的終結(狗帶)

發表於2015-10-24

CSS類名總是作用在同一的全域性作用域裡面。

任何一個跟CSS有長時間打交道的開發者,都不得不接受CSS那具有侵略性的全域性特性,明顯地這是一種文件流時代的設計模型。而對於今天現代web應用,更應該積極提出一種更健全的樣式環境。

每一個CSS類名都有可能與其它元素產生的意想不到副作用,又或者產生衝突。更令人吃驚的是,我們的class的效果可能在全域性作用域的互相影響下(原文這裡比喻為全域性唯一性戰爭),最終在頁面上產生很少的效果或者根本沒有效果。

任何時候我們改變一個CSS檔案,我們都需要小心翼翼地考慮全域性環境是否產生衝突。沒有其他前端技術是需要如此之多的規範和約束,而這僅僅是為了保持最低階別的可維護性。

 

、、、

 

但我們不能一直這樣下去。是時候擺脫這種全域性樣式的折磨。開啟區域性CSS的時代!

“在其他語言,全域性環境的修改需要變動的程式碼很少”

在javascript的社群中,感謝BrowserifyWebpackJSPM,讓我們的程式碼變得模組化,每個模組有明確的依賴及其輸出的API。然而,不知怎麼的,CSS視乎總時被忽略掉。

我們中許多人,包括我自己,一直使用CSS工作這麼長時間,我們都沒有發現缺少區域性性作用域,是一種問題。因為沒有瀏覽器廠商的重大幫助下我們也能夠解決。即使這樣,我們仍然需要等待著,大部分使用者能使用上瀏覽器的ShadowDOM的支援。

在全域性作用域問題上,我們已經使用一系列的命名規範來編碼。想OOCSS, SMACSSBEMSUIT,每一個都提供著一種方式模擬健全的作用域規則,達到避免命名衝突效果。

雖然馴服CSS無疑是一個巨大的進步,但這些方法都沒有解決我們樣式表上真正的問題。無論我們選擇哪個規範,我們依然被卡在全域性類名上。

但,在2015年的四月22號將會發生改變。

、、、
正如我們此前的一篇文章涉及到——“Block,Element,修改你的JavaScript元件”——我們可以利用Webpack把我們的CSS
作為一種JavaScript模組來引用。如果這聽起來很陌生,去讀讀這篇文章會是一個good idea,以免你錯失接下來要講的內容。

使用Webpack的css-loader,引用一個元件的CSS如下:

乍一看,這很奇怪,我們引用的是CSS而不是JavaScript

通常,一個require引入的應該提供一些區域性作用域。如果不是,明顯低會產生全域性作用域的副作用,這是一種拙劣的設計。而CSS的全域性作用域特性,卻必定產生這樣的副作用。

因此我們在思考

、、、

2015年4月22日,Tobias Koppers這位對Webpack孜孜不倦的程式碼提交者,提交了一個css-loader新特性的版本提交。當時叫placeholder,而現在叫local-scope。這個特性允許我們輸出classname從我們的CSS到使用中的JavaScript程式碼。

簡而言之,下面這種寫法:

我們改為

看看我們匯出的CSS是怎麼樣的,我們的程式碼大概如下:

在上面的例子中我們使用css-loader的定製的語法  :local(.idntifier) ,輸出了兩個的識別符號,foo和bar。
這些識別符號對應著class strings,這將用在javascript檔案中去。例如,當我們使用React

重要的是,這些識別符號對映的class strings,在全域性作用域上是保證唯一的。
我們不再需要給所有的類名新增冗長的字首來模擬範圍。多個元件可以自定義自己的foo和bar識別符號。——不像傳統的全域性作用域的模式,也不會產生命名衝突。

、、、

非常關鍵的一點,不得不承認這已經發生了巨大轉變。
我們現在更有信心地大膽修改我們的CSS,不用小心翼翼地怕影響其他頁面的元素。我們引入了一個健全的作用域模式

全域性CSS的好處是,元件間通過通用的class來達到複用的效果——這仍然可以在區域性作用域模型上實現。關鍵的區別是,就像我們編碼在其他語言上,我們需要顯式地引入我們依賴的類。假想一下在全域性命名環境,我們引入的區域性CSS不需要很多。

“編寫可維護的CSS現在是值得提倡的,但不是通過謹慎地準守一個命名約定,而是在開發過程中通過獨立的封裝”

由於這個作用域模型,我們把實際的classname的控制權移交給Webpack。幸運的是,這是我可以配置的。預設情況下,css-loader會把識別符號轉換成為hash。
例如:

 

編譯為:

在開發環境除錯來講,會帶帶來一些阻礙。為了令到我們的classes變得更加有用,我們可在Webpack的config裡面設定css-loader的引數,配置class的格式。

在這一次,我們的foo這個class會比之前編譯的更加好辨認:

我們能清晰地看得到識別符號的名字,以及他來自哪個元件。使用node_env環境變數,我們能根據開發模式和生產環境配置不同的class命名模式。

 

一旦我們發現這個特性,我們不用猶豫地在我們最新的專案上本地化起來。如果按照慣例,我們已經為元件化而使用BEM命名CSS,這真是天作之合。

有趣的是,一種現象很快地出現了,我們大部分CSS檔案裡只有區域性化class:

 

全域性性的class僅僅在web應用裡面的一小部分,本能地引開出一個重要問題:

“如果不需要特殊語法,我們的class預設是區域性性的,而讓全域性性的class需要例外。怎麼樣?”

如果這樣,我們上面的程式碼就變成如下:

 

雖然這class通常會過於模糊,但當他們轉換為css-lodaer的區域性作用域的格式後將會消除這一問題。並且確保了明確的模組作用域來使用。

少數情況,我們無法避免全域性樣式,我們可以明確地表明一個特殊的全域性語法。例如,當樣式使用ReactCSSTransitionGroup來生成一個無作用域classes。

.panel :global .transition-active-enter{…}

在這個例子中,我們不只是使用本地化方式命名我的模組,我們也命名了一個不在我們的作用域上的全域性class。

、、、

一旦我開始調查我如何實現這個預設區域性化class語法,我們意識到它不會太困難。
為了達到這個目的,我們推薦PostCSS——一個神奇的工具允許你編寫自定義的CSS轉換外掛。今天最受歡迎的CSS構建工具Autoprefixer實際上是PostCSS外掛,同時為一個獨立的工具而已。

為讓區域性CSS正式地使用,我已經開源了一個高度實驗性質的外掛postcss-local-scope。它仍然在發展,所以在生產環境中使用你需要控制風險。

如果你使用Webpack,這是非常簡單的流程:掛上postcss-loaderpostcss-local-scope在你的CSS構建流程。比起文件,我已經建立了一個示例庫——postcss-local-scope-example。裡面顯示了怎麼使用的例子。
令人激動的是,引入區域性作用域僅僅是一個開始。
讓構建工具處理classname有一些潛在的巨大影響。從長遠來看,我們應該停止人為的編譯器,而是讓計算機來優化輸出。

“在未來,我們可以在一個最優的編譯時間內,自動化找出可重用的樣式,生成可元件之間共享的class”

一旦你嘗試了區域性CSS,你就回不去了。真正體驗過,樣式的區域性作用性在所有瀏覽器上執行正常,你會難以忘記的體驗。

引入區域性作用域對我們處理CSS有重大的的連鎖反應。命名規範,重用模式,潛在的樣式抽離,分包等等,都會直接受到這種轉變的影響。我們僅僅在這裡開始了區域性CSS的時代。

理解這種轉變的影響是我們依舊需要努力。伴隨你有價值的投入和實驗,我希望這是作為一個更大的社群的一次談話

“加入我們,check出postcss-local-scope-example的程式碼,眼見為實”

一旦你行動了,我認為你會同意這並不誇張: 全域性CSS的日子將會終結,區域性CSS才是未來。

 

後記:
2015年5月24日: postcss-local-scope的最初想法已經被Webpack的TobiasKoppers所接受。這意味著改專案已經被棄用了。現在我們初步確認在css-loader上通過一個module的標誌可以支援CSS Modules。我建立了一個庫來演示CSSModules在css-loader上的用法,包括類的繼承及職能元件間共享樣式等。

相關文章