CSS預編譯與PostCSS以及Webpack構建CSS綜合方案

才子鍋鍋發表於2017-09-15

本文首發於部落格園,原文連結

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開發的效率:

  1. 增強程式設計能力;
  2. 增強可複用性;
  3. 增強可維護性;
  4. 更便於解決瀏覽器相容性。

不同的預編譯器特性雖然有所差異,但核心功能均圍繞這些目標打造,比如: * 巢狀; * 變數; * mixin/繼承; * 運算; * 模組化;

巢狀是所有預編譯器都支援的語法特性,也是原生CSS最讓開發者頭疼的問題之一;mixin/繼承是為了解決hack和程式碼複用;變數和運算增強了原始碼的可程式設計能力;模組化的支援不僅更利於程式碼複用,同時也提高了原始碼的可維護性。

PostCSS

CSS預編譯的理念與Babel有一定相通之處,最重要的區別是:預編譯語法並非規範的CSS,而是各成一派。由預編譯語法編寫的原始碼不能在任何宿主瀏覽器中執行。從這個角度考慮,CSS預編譯更像CoffeeScript、TypeScript等JavaScript子集。可以預見的是,如果未來CSS規範推出了預編譯類似的特性和語法,這些預編譯器都將成為歷史的塵埃。PostCSS則反其道而行之,從理念上更加接近Babel,業內也有人將其稱為“CSS的Babel”。

PostCSS鼓勵開發者使用規範的CSS原生語法編寫原始碼,然後配置編譯器需要相容的瀏覽器版本,最後經過編譯將原始碼轉化為目標瀏覽器可用的CSS程式碼。PostCSS提供了豐富的外掛用於實現不同場景的編譯需求,最常用的比如autoprefixer、sprites等,編譯流程如下圖所示:

enter image description here

PostCSS並不是另一種CSS預編譯器,與SASS、Less等預編譯器也並不衝突。PostCSS與Babel的不同之處在於,它所支援的所謂“未來CSS語法”並不是嚴格的CSS規範,其中大部分語法和特性目前只是CSS4的草案而已。很多人將PostCSS稱為“CSS後編譯器”,這個稱謂可以一定程度上說明目前業界對PostCSS的普遍使用方案,請看下圖: enter image description here

即使是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-loadercss-loaderstyle-loader編譯。在這種工作模式的基礎上,結合第一張圖所示的編譯流程,使用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之前執行。

相關文章