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()
以及處於草案階段的var()
可以隱約看出W3C有意加強CSS的編程能力;
開發者們不斷探索着能夠彌補這些缺陷的解決方案,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中
importLoaders
選項的作用是用於配置css-loader作用於@import
的資源之前需要經過其他loader的個數。@import
用於css源碼中引用其他模塊的關鍵字,如果你的項目中確定不會涉及模塊化可以忽略此配置項; - 如果需要將編譯后的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
引用的模塊、url()
引用的圖片等,然后根據Webpack配置編譯這些資源。style-loader負責將css代碼通過<style>
標簽插入html文檔中,所以如果獨立導出css文件就不再需要style-loader。css-loader必須在style-loader之前執行。