使用 PostCSS 進行 CSS 處理


在 Web 應用開發中,CSS 代碼的編寫是重要的一部分。CSS 規范從最初的 CSS1 到現在的 CSS3,再到 CSS 規范的下一步版本,規范本身一直在不斷的發展演化之中。這給開發人員帶來了效率上的提高。不過與其他 Web 領域的規范相似的處境是,CSS 規范在瀏覽器兼容性方面一直存在各種各樣的問題。不同瀏覽器在 CSS 規范的實現方面的進度也存在很大差異。另外,CSS 規范本身的發展速度與社區的期待還有一定的差距。這也是為什么 SASS 和 LESS 等 CSS 預處理語言可以流行的重要原因。SASS 和 LESS 等提供了很多更實用的功能,也體現了開發人員對 CSS 語言的需求。本文中要介紹的 PostCSS 是目前流行的一個對 CSS 進行處理的工具。PostCSS 依托其強大的插件體系為 CSS 處理增加了無窮的可能性。

 

PostCSS 介紹

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

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 插件。

Webpack

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

清單 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 目錄。

清單 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 所示。

清單 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 規范中的標准屬性名即可。

代碼清單 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 及其常用插件進行了詳細介紹,希望可以幫助開發人員提高開發效率。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM