使用 PostCSS 進行 CSS 處理

在 Web 應用開發中,CSS 代碼的編寫是重要的一部分。CSS 規範從最初的 CSS1 到如今的 CSS3,再到 CSS 規範的下一步版本,規範自己一直在不斷的發展演化之中。這給開發人員帶來了效率上的提升。不過與其餘 Web 領域的規範類似的處境是,CSS 規範在瀏覽器兼容性方面一直存在各類各樣的問題。不一樣瀏覽器在 CSS 規範的實現方面的進度也存在很大差別。另外,CSS 規範自己的發展速度與社區的期待還有必定的差距。這也是爲何 SASS 和 LESS 等 CSS 預處理語言能夠流行的重要緣由。SASS 和 LESS 等提供了不少更實用的功能,也體現了開發人員對 CSS 語言的需求。本文中要介紹的 PostCSS 是目前流行的一個對 CSS 進行處理的工具。PostCSS 依託其強大的插件體系爲 CSS 處理增長了無窮的可能性。css

 

PostCSS 介紹

PostCSS 自己是一個功能比較單一的工具。它提供了一種方式用 JavaScript 代碼來處理 CSS。它負責把 CSS 代碼解析成抽象語法樹結構(Abstract Syntax Tree,AST),再交由插件來進行處理。插件基於 CSS 代碼的 AST 所能進行的操做是多種多樣的,好比能夠支持變量和混入(mixin),增長瀏覽器相關的聲明前綴,或是把使用未來的 CSS 規範的樣式規則轉譯(transpile)成當前的 CSS 規範支持的格式。從這個角度來講,PostCSS 的強大之處在於其不斷髮展的插件體系。目前 PostCSS 已經有 200 多個功能各異的插件。開發人員也能夠根據項目的須要,開發出本身的 PostCSS 插件。html

PostCSS 從其誕生之時就帶來了社區對其類別劃分的爭議。這主要是因爲其名稱中的 post,很容易讓人聯想到 PostCSS 是用來作 CSS 後處理(post-processor)的,從而與已有的 CSS 預處理(pre-processor)語言,如 SASS 和 LESS 等進行類比。實際上,PostCSS 的主要功能只有兩個:第一個就是前面提到的把 CSS 解析成 JavaScript 能夠操做的 AST,第二個就是調用插件來處理 AST 並獲得結果。所以,不能簡單的把 PostCSS 歸類成 CSS 預處理或後處理工具。PostCSS 所能執行的任務很是多,同時涵蓋了傳統意義上的預處理和後處理。PostCSS 是一個全新的工具,給前端開發人員帶來了不同的處理 CSS 的方式。前端

 

使用 PostCSS

PostCSS 通常不單獨使用,而是與已有的構建工具進行集成。PostCSS 與主流的構建工具,如 Webpack、Grunt 和 Gulp 均可以進行集成。完成集成以後,選擇知足功能需求的 PostCSS 插件並進行配置。下面將具體介紹如何在 Webpack、Grunt 和 Gulp 中使用 PostCSS 的 Autoprefixer 插件。react

Webpack

Webpack 中使用 postcss-loader 來執行插件處理。在代碼清單 1 中,postcss-loader 用來對.css 文件進行處理,並添加在 style-loader 和 css-loader 以後。經過一個額外的 postcss 方法來返回所須要使用的 PostCSS 插件。require('autoprefixer') 的做用是加載 Autoprefixer 插件。web

清單 1. 在 Webpack 中使用 PostCSS 插件
 1 var path = require('path');
 2  
 3 module.exports = {
 4  context: path.join(__dirname, 'app'),
 5  entry: './app',
 6  output: {
 7    path: path.join(__dirname, 'dist'),
 8    filename: 'bundle.js'
 9  },
10  module: {
11    loaders: [
12      {
13        test:   /\.css$/,
14        loader: "style-loader!css-loader!postcss-loader"
15      }
16    ]
17  },
18  postcss: function () {
19    return [require('autoprefixer')];
20  }
21 }

 

 
Gulp

Gulp 中使用 gulp-postcss 來集成 PostCSS。在代碼清單 2 中,CSS 文件由 gulp-postcss 處理以後,產生的結果寫入到 dist 目錄。json

清單 2. 在 Gulp 中使用 PostCSS 插件
1 var gulp = require('gulp');
2  
3 gulp.task('css', function() {
4  var postcss = require('gulp-postcss');
5  return gulp.src('app/**/*.css')
6    .pipe(postcss([require('autoprefixer')]))
7    .pipe(gulp.dest('dist/'));
8 });

 

 
Grunt

Grunt 中使用 grunt-postcss 來集成 PostCSS。Grunt 中須要使用 grunt.loadNpmTasks 方法來加載插件,如代碼清單 3 所示。gulp

清單 3. 在 Grunt 中使用 PostCSS 插件
 1 module.exports = function(grunt) {
 2  grunt.initConfig({
 3    postcss: {
 4      options: {
 5        processors: [
 6          require('autoprefixer')()
 7        ]
 8      },
 9      dist: {
10        src: 'app/**/*.css',
11        expand: true,
12        dest: 'dist'
13      }
14    }
15  });
16   grunt.loadNpmTasks('grunt-postcss');
17 }

 

下面介紹經常使用的 PostCSS 插件。瀏覽器

 

經常使用插件

Autoprefixer

Autoprefixer 是一個流行的 PostCSS 插件,其做用是爲 CSS 中的屬性添加瀏覽器特定的前綴。因爲 CSS 規範的制定和完善通常須要花費比較長的時間,瀏覽器廠商在實現某個 CSS 新功能時,會使用特定的瀏覽器前綴來做爲正式規範版本以前的實驗性實現。好比 Webkit 核心的瀏覽器使用-webkit-,微軟的 IE 使用-ms-。爲了兼容不一樣瀏覽器的不一樣版本,在編寫 CSS 樣式規則聲明時一般須要添加額外的帶前綴的屬性。這是一項繁瑣而無趣的工做。Autoprefixer 能夠自動的完成這項工做。Autoprefixer 能夠根據須要指定支持的瀏覽器類型和版本,自動添加所需的帶前綴的屬性聲明。開發人員在編寫 CSS 時只須要使用 CSS 規範中的標準屬性名便可。app

代碼清單 4 中給出了使用 CSS 彈性盒模型的 display 屬性聲明。模塊化

清單 4. 標準的 CSS 彈性盒模型的 display 屬性聲明
1 #content {
2  display: flex;
3 }

 

在通過 Autoprefixer 處理以後獲得的 CSS 如代碼清單 5 所示。

清單 5. 使用 Autoprefixer 處理以後的 CSS
1 #content {
2  display: -webkit-box;
3  display: -webkit-flex;
4  display: -ms-flexbox;
5  display: flex;
6 }

 

Autoprefixer 使用 Can I Use 網站提供的數據來肯定所要添加的不一樣瀏覽器的前綴。隨着瀏覽器版本的升級,瀏覽器在新版本中可能已經提供了對標準屬性的支持,從而再也不須要添加額外的前綴。Autoprefixer 能夠配置須要支持的瀏覽器。如「last 2 versions」表示主流瀏覽器的最近兩個版本,「ie 6-8」表示 IE 6 到 8,「> 1%」表示全球使用率大於 1%的瀏覽器版本。代碼清單 6 中給出了配置 Autoprefixer 插件的示例。

清單 6. 配置 Autoprefixer 插件
1 require('autoprefixer')({
2  browsers: ['last 2 versions']
3 })

 

Autoprefixer 除了添加所須要的屬性名稱前綴以外,還能夠移除 CSS 代碼中冗餘的屬性名稱前綴。遺留 CSS 代碼中可能包含由開發人員手動添加的舊版本的瀏覽器所支持的帶前綴的屬性名稱。Autoprefixer 默認狀況下會移除這些冗餘的前綴。能夠經過配置對象中的 remove 屬性來配置該行爲。

cssnext

cssnext 插件容許開發人員在當前的項目中使用 CSS 未來版本中可能會加入的新特性。cssnext 負責把這些新特性轉譯成當前瀏覽器中可使用的語法。從實現角度來講,cssnext 是一系列與 CSS 未來版本相關的 PostCSS 插件的組合。好比,cssnext 中已經包含了對 Autoprefixer 的使用,所以使用了 cssnext 就再也不須要使用 Autoprefixer。

自定義屬性和變量

CSS 的層疊變量的自定義屬性規範(CSS Custom Properties for Cascading Variables)容許在 CSS 中定義屬性並在樣式規則中做爲變量來使用它們。自定義屬性的名稱以「--」開頭。當聲明瞭自定義屬性以後,能夠在樣式規則中使用「var()」函數來引用,如代碼清單 7 所示。

清單 7. CSS 自定義屬性和變量
1 :root {
2  --text-color: black;
3 }
4  
5 body {
6  color: var(--text-color);
7 }

辦公資源網址導航 https://www.wode007.com

在通過 cssnext 轉換以後的 CSS 代碼如代碼清單 8 所示。

清單 8. 轉換以後的 CSS 代碼
1 body {
2  color: black;
3 }

 

 
自定義選擇器

CSS 擴展規範(CSS Extensions)中容許建立自定義選擇器,好比能夠對全部的標題元素(h1 到 h6)建立一個自定義選擇器並應用樣式。經過「@custom-selector」來定義自定義選擇器。在代碼清單 9 中,「--heading」是自定義選擇器的名稱,其等同於選擇器聲明「h1, h2, h3, h4, h5, h6」。

清單 9. 自定義選擇器
@custom-selector :--heading h1, h2, h3, h4, h5, h6;
 
:--heading {
 font-weight: bold;
}

 

通過 cssnext 處理以後的 CSS 如代碼清單 10 所示。

清單 10. 轉換以後的 CSS 代碼
1 h1,
2 h2,
3 h3,
4 h4,
5 h5,
6 h6 {
7  font-weight: bold;
8 }

 

 
樣式規則嵌套

樣式規則嵌套是一個很是實用的功能,能夠減小重複的選擇器聲明。這也是 SASS 和 LESS 等 CSS 預處理器流行的一個重要緣由。CSS 嵌套模塊規範(CSS Nesting Module)中定義了標準的樣式規則嵌套方式。能夠經過 cssnext 把標準的樣式嵌套格式轉換成當前的格式。CSS 嵌套規範中定義了兩種嵌套方式:第一種方式要求嵌套的樣式聲明使用「&」做爲前綴,「&」只能做爲聲明的起始位置;第二種方式的樣式聲明使用「@nest」做爲前綴,而且「&」能夠出如今任意位置。代碼清單 11 中給出了兩種不一樣聲明方式的示例。

清單 11. 樣式規則嵌套
1 .message {
2  font-weight: normal;
3  & .header {
4    font-weight: bold;
5  }
6   @nest .body & {
7    color: black;
8  }
9 }

 

通過 cssnext 轉換以後的 CSS 代碼如代碼清單 12 所示。

清單 12. 轉換以後的 CSS 代碼
 1 .message {
 2  font-weight: normal
 3 }
 4  
 5 .message .header {
 6  font-weight: bold;
 7 }
 8  
 9 .body .message {
10  color: black;
11 }

 

 
CSS 模塊化

在編寫 CSS 代碼時會遇到的一個很重要的問題是 CSS 代碼的組織方式。當項目中包含的 CSS 樣式很是多時,該問題尤爲突出。這主要是因爲不一樣 CSS 文件中的樣式聲明可能產生名稱衝突。如今的 Web 開發大多采用組件化的組織方式,即把應用劃分紅多個不一樣的組件。每一個組件均可以有本身的 CSS 樣式聲明。好比,兩個組件的 CSS 中可能都定義了對於 CSS 類 title 的樣式規則。當這兩個組件被放在同一個頁面上時,衝突的 CSS 類名稱可能形成樣式的錯亂。對於此類 CSS 類名衝突問題,通常的作法是避免全局名稱空間中的樣式聲明,而是每一個組件有本身的名稱空間。BEM 經過特殊的命名 CSS 類名的方式來解決名稱衝突的問題。兩個不一樣組件中的標題的 CSS 類名可能爲 component1__title 和 component2__title。

CSS 模塊(CSS modules)並不要求使用 BEM 那樣複雜的命名規範。每一個組件能夠自由選擇最合適的簡單 CSS 類名。組件的 CSS 類名在使用時會被轉換成帶惟一標識符的形式。這樣就避免了名稱衝突。在組件開發中能夠繼續使用簡單的 CSS 類名,而不用擔憂名稱衝突問題。代碼清單 13 中給出了使用 CSS 模塊規範的 CSS 代碼。樣式規則以前的「:global」表示這是一個全局樣式聲明。其餘的樣式聲明是局部的。

清單 13. 使用 CSS 模塊規範的 CSS 代碼
:global .title {
 font-size: 20px;
}
 
.content {
 font-weight: bold;
}

 

通過轉換以後的 CSS 樣式聲明如代碼清單 14 所示。全局的 CSS 類名 title 保存不變,局部的 CSS 類名 content 被轉換成_content_6xmce_5。這樣就確保了不會與其餘組件中名稱爲 content 的類名衝突。

清單 14. 轉換以後的 CSS 代碼
1 .title {
2  font-size: 20px;
3 }
4  
5 ._content_6xmce_5 {
6  font-weight: bold;
7 }

 

因爲在組件的 HTML 代碼中引用的 CSS 類名和最終生成的類名並不相同,所以須要一箇中間的過程來進行類名的轉換。對於 React 來講,可使用 react-css-modules 插件;在其餘狀況下,可使用 PostHTML 對 HTML 進行處理。postcss-modules 插件把 CSS 模塊中的 CSS 類名的對應關係保存在一個 JavaScript 對象中,能夠被 PostHTML 中的 posthtml-css-modules 插件來使用。

在代碼清單 15 中,在使用 postcss-modules 插件時提供了一個方法 getJSON。當 CSS 模塊的轉換完成時,該方法會被調用。該方法的參數 json 參數表示的是轉換結果的 JavaScript 對象。該對象被以 JavaScript 文件的形式保存到 cssModules 目錄下,並添加了模塊導出的邏輯。

清單 15. 保存 CSS 模塊的輸出文件
1 require('postcss-modules')({
2    getJSON: function(cssFileName, json) {
3      var cssName = path.basename(cssFileName, '.css');
4      var jsonFileName = path.resolve(dist, 'cssModules', cssName + '.js');
5      mkdirp.sync(path.dirname(jsonFileName));
6      fs.writeFileSync(jsonFileName, "module.exports = " + JSON.stringify(json) + ";");
7    }
8  })

 

代碼清單 16 中給出了使用 Gulp 的 gulp-posthtml 來處理 HTML 文件的任務。posthtml-css-modules 能夠處理一個目錄下的多個 CSS 模塊輸出文件。

清單 16. 使用 PostHTML 處理 HTML 裏支持 CSS 模塊
1 gulp.task('posthtml', function() {
2  var posthtml = require('gulp-posthtml');
3  return gulp.src('app/**/*.html')
4    .pipe(posthtml([ require('posthtml-css-modules')(path.join(dist, 'cssModules')) ]))
5    .pipe(gulp.dest('dist/'));
6 });

 

在 HTML 文件中使用「css-module」屬性來指定對應的 CSS 類名。在代碼清單 17 中,名稱「header.content」的 header 表示的是 CSS 文件名,而 content 是該文件中定義的 CSS 類名。

清單 17. 使用 CSS 模塊的 HTML 文件
1 <div css-module="header.content">Hello world</div>

 

在通過處理以後,獲得的 HTML 內容如代碼清單 18 所示。

清單 18. 轉換以後的 HTML 文件
1 <div class="_content_6xmce_5">Hello world</div>

 

<!--資源文件處理-->

在 CSS 中常常會須要引用外部資源,如圖片和字體等。在 CSS 代碼中處理這些資源時會遇到一些常見的問題,好比圖片的路徑問題,內聯圖片內容,生成圖片 Sprites 等。對於這些問題,都有專門的 PostCSS 插件來提供所需的功能。

postcss-assets 插件用來處理圖片和 SVG。在 CSS 聲明中引用圖片時,可使用 resolve 加上圖片路徑的形式,如「resolve(‘logo.png’)」。在插件處理時,會按照必定的順序在指定的目錄中查找該文件,若是找到,會用圖片的真實路徑來替換。能夠經過選項 loadPaths 來指定查找的路徑,basePath 來指定項目的根目錄。在 CSS 聲明中,可使用 width、height 和 size 方法來獲取到圖片的寬度、高度和尺寸。當須要內聯一個圖片時,可使用 inline 方法。inline 會把圖片轉換成 Base64 編碼的 data url 的格式,這樣能夠減小對圖片的 HTTP 請求。代碼清單 19 給出了使用示例。

清單 19. Postcss-assets 插件使用示例
1 require('postcss-assets')({
2    loadPaths: ['assets/images']
3 })

 

代碼清單 20 中給出了使用 resolve 的 CSS 樣式聲明。

清單 20. 使用 resolve 的 CSS 樣式聲明
1 .logo {
2  background-image: resolve('logo.png');
3 }

 

代碼清單 21 中給出了 cssnext 處理以後的 CSS 代碼。

清單 21. 轉換以後的 CSS 代碼
1 .logo {
2  background-image: url('/assets/images/logo.png');
3 }

 

 
其餘插件

還有其餘實用的 PostCSS 插件能夠在開發中使用。如 postcss-stylelint 用來檢查 CSS 中可能存在的格式問題。cssnano 用來壓縮 CSS 代碼。postcss-font-magician 用來生成 CSS 中的 @font-face 聲明。precss 容許在 CSS 中使用相似 SASS 的語法。

 

開發 PostCSS 插件

雖然 PostCSS 已經有 200 多個插件,但在開發中仍然可能存在已有插件不能知足需求的狀況。這個時候能夠開發本身的 PostCSS 插件。開發插件是一件很容易的事情。每一個插件本質只是一個 JavaScript 方法,用來對由 PostCSS 解析的 CSS AST 進行處理。

每一個 PostCSS 插件都是一個 NodeJS 的模塊。使用 postcss 的 plugin 方法來定義一個新的插件。插件須要一個名稱,通常以「postcss-」做爲前綴。插件還須要一個進行初始化的方法。該方法的參數是插件所支持的配置選項,而返回值則是另一個方法,用來進行實際的處理。該處理方法會接受兩個參數,css 表明的是表示 CSS AST 的對象,而 result 表明的是處理結果。代碼清單 22 中給出了一個簡單的 PostCSS 插件。該插件使用 css 對象的 walkDecls 方法來遍歷全部的「color」屬性聲明,並對「color」屬性值進行檢查。若是屬性值爲 black,就使用 result 對象的 warn 方法添加一個警告消息。

清單 22. PostCSS 插件示例
 1 var postcss = require('postcss');
 2  
 3 module.exports = postcss.plugin('postcss-checkcolor', function(options) {
 4  return function(css, result) {
 5    css.walkDecls('color', function(decl) {
 6      if (decl.value == 'black') {
 7        result.warn('No black color.', {decl: decl});
 8      }
 9    });
10  };
11 })

 

代碼清單22中的插件的功能比較簡單。PostCSS 插件通常經過不一樣的方法來對當前的 CSS 樣式規則進行修改。如經過 insertBefore 和 insertAfter 方法來插入新的規則。

小結

 

對於 CSS 的處理一直都是 Web 開發中的一個複雜問題,其中一部分來源於 CSS 規範,一部分來源於不一樣瀏覽器實現帶來的兼容性問題。PostCSS 爲處理 CSS 提供了一種新的思路。經過 PostCSS 強大的插件體系,能夠對 CSS 進行各類不一樣的轉換和處理,從而儘量的把繁瑣複雜的工做交由程序去處理,而把開發人員解放出來。本文對 PostCSS 及其經常使用插件進行了詳細介紹,但願能夠幫助開發人員提升開發效率。

相關文章
相關標籤/搜索