原文連結
CSS全稱Cascading Style Sheets(層疊樣式表),用來為HTML添加樣式,本質上是一種标記類語言。CSS前期發展非常迅速,1994年哈肯·維姆·萊首次提出CSS,1996年12月W3C推出了第一個正式版本。随後不到兩年的時間,1998年5月便推出了第二個版本,一直沿用至今。但是CSS3的制訂工作卻遲遲沒有完成。CSS3最初的草案在1999年便被提出,但是直到今日CSS3規範仍然有部分特性沒有完成。如果說ES6與ES5相隔的6年時間讓開發者們熬盡了心肝,那麼從提案到釋出相隔近20年光陰的CSS3可以說是千呼萬喚始出來,而且猶抱琵琶半遮面。
CSS的缺陷
CSS的初衷是為了彌補HTML原生樣式的不足,早期對樣式要求并不複雜的web網站僅僅需要少量的CSS代碼即可。在如今web應用程式追求極緻使用者體驗的潮流下,對CSS的要求也不斷增強。複雜CSS開發是一件非常痛苦的事情,最主要的原因是受限于浏覽器的實作以及CSS自身的弱程式設計能力:
- 浏覽器實作不理想甚至實作方案各一。對CSS的相容處理幾乎是每個前端工程師必備的技能,究其根本是浏覽器對CSS規範的實作程度和方案不一。其中尤以IE浏覽器最甚,包括以IE核心的衆多國産浏覽器。雖然目前絕大多數web應用已經不在相容IE8以下浏覽器,但IE8和IE9仍然讓前端工程師們頭疼不已;
- CSS的弱程式設計能力。CSS通過“selector-properties”的模式為HTML文檔增加樣式,簡單的文法可以讓沒有任何程式設計基礎的初學者或者設計人員很快上手。但CSS不支援嵌套,甚至運算、變量、複用等這些幾乎是編寫複雜代碼的必備特性。從CSS3引入了
以及處于草案階段的cal()
可以隐約看出W3C有意加強CSS的程式設計能力;var()
開發者們不斷探索着能夠彌補這些缺陷的解決方案,CSS預編譯器是第一種順勢而生的革命性方案。
CSS預編譯
CSS預編譯的工作原理是提供便捷的文法和特性供開發者編寫源代碼,随後經過專門的編譯工具将源碼轉化為CSS文法。最早的CSS預編譯器是2007年起源于Ruby on Rails社群的SASS,目前比較流行的其他CSS預編譯器如Less、Stylus的誕生都一定程度上受到了SASS的影響和啟發。
CSS預編譯器幾乎成為現如今開發CSS的标配,它從以下幾個方面提升了CSS開發的效率:
- 增強程式設計能力;
- 增強可複用性;
- 增強可維護性;
- 更便于解決浏覽器相容性。
不同的預編譯器特性雖然有所差異,但核心功能均圍繞這些目标打造,比如:
- 嵌套;
- 變量;
- mixin/繼承;
- 運算;
- 子產品化;
嵌套是所有預編譯器都支援的文法特性,也是原生CSS最讓開發者頭疼的問題之一;mixin/繼承是為了解決hack和代碼複用;變量和運算增強了源碼的可程式設計能力;子產品化的支援不僅更利于代碼複用,同時也提高了源碼的可維護性。
PostCSS
CSS預編譯的理念與Babel有一定相通之處,最重要的差別是:預編譯文法并非規範的CSS,而是各成一派。由預編譯文法編寫的源代碼不能在任何宿主浏覽器中運作。從這個角度考慮,CSS預編譯更像CoffeeScript、TypeScript等JavaScript子集。可以預見的是,如果未來CSS規範推出了預編譯類似的特性和文法,這些預編譯器都将成為曆史的塵埃。PostCSS則反其道而行之,從理念上更加接近Babel,業内也有人将其稱為“CSS的Babel”。
PostCSS鼓勵開發者使用規範的CSS原生文法編寫源代碼,然後配置編譯器需要相容的浏覽器版本,最後經過編譯将源碼轉化為目标浏覽器可用的CSS代碼。PostCSS提供了豐富的插件用于實作不同場景的編譯需求,最常用的比如autoprefixer、sprites等,編譯流程如下圖所示:

PostCSS并不是另一種CSS預編譯器,與SASS、Less等預編譯器也并不沖突。PostCSS與Babel的不同之處在于,它所支援的所謂“未來CSS文法”并不是嚴格的CSS規範,其中大部分文法和特性目前隻是CSS4的草案而已。很多人将PostCSS稱為“CSS後編譯器”,這個稱謂可以一定程度上說明目前業界對PostCSS的普遍使用方案,請看下圖:
即使是PostCSS支援的“未來CSS文法”也并不能完全彌補CSS的缺陷,是以目前普遍的方案是将CSS預編譯與PostCSS綜合在一起:
- 使用CSS預編譯彌補CSS源碼的弱程式設計能力,比如變量、運算、繼承、子產品化等;
- 使用PostCSS處理針對浏覽器的需求,比如autoprefix、自動css sprites等。
Webpack結合預編譯與PostCSS實作CSS建構
通過Webpack配置項中的
use
指定的loader是按照索引反向執行,比如存在下述配置方案:
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader'
]
}
.less
字尾類型的檔案依次經過
less-loader
、
css-loader
和
style-loader
編譯。在這種工作模式的基礎上,結合圖3-4所示的編譯流程,使用Webpack結合CSS預編譯與PostCSS的編譯方案便一目了然了:
{
test: /\.less$/,
use: [{
loader: 'style-loader',
options: {} // style-loader options
},{
loader: 'css-loader',
options: {
importLoaders: 2 // css-loader options
}
},{
loader: 'postcss-loader',
options: {} // postcss-loader options
},{
loader: 'less-loader',
options: {} // less-loader options
}]
}
上述配置中有以下需要注意的細節:
- css-loader中
選項的作用是用于配置css-loader作用于importLoaders
的資源之前需要經過其他loader的個數。@import
用于css源碼中引用其他子產品的關鍵字,如果你的項目中确定不會涉及子產品化可以忽略此配置項;@import
- 如果需要将編譯後的css檔案獨立導出,則需将style-loader[注]替換為extract-text-webpack-plugin,如下:
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
filename: './dest/[name].[contenthash].css'
use: [{
loader: 'css-loader',
options: {
importLoaders: 2 // css-loader options
}
},{
loader: 'postcss-loader',
options: {} // postcss-loader options
},{
loader: 'less-loader',
options: {} // less-loader options
}],
publicPath: '/'
})
}
注:很多開發者容易混淆css-loader和style-loader的作用。css-loader的作用是解析css源檔案并擷取其引用的資源,比如引用的子產品、
@import
引用的圖檔等,然後根據Webpack配置編譯這些資源。style-loader負責将css代碼通過
url()
标簽插入html文檔中,是以如果獨立導出css檔案就不再需要style-loader。css-loader必須在style-loader之前執行。
<style>