譯者注(GeoffZhu): 這篇適合一些使用過預處理CSS的開發者,比如less,sass或stylus,如果你都沒用過,那你一定不是個好司機。在PostCSS中早就可以使用CSS Modules了,該篇作者貢獻了一個新工具,可以讓更多開發者方便的使用最新的CSS Modules。
我們和全域性作用域的css鬥爭了多年,現在終於是時候結束它了。不管你用的是什麼語言還是框架,CSS命名衝突將不再是個問題。我將給你展示一下PostCSS和PostCSS-modules如何使用,並且可以在服務端使用它們。 CSS起初只是一個美化文件的工具,但是事情到1996年發生了變化。瀏覽器中不再單單隻有文件了,即時通訊,各種軟體,遊戲,沒什麼是瀏覽器不能承載的。
當今,我們在HTML和CSS方面已經走了很遠很遠,開發者們激發出了CSS所有的潛力,甚至創造出了一些CSS本身都快駕馭不了的東西。
每一個有經驗的開發者都知道 — 每次使用全域性名稱空間都是留下了一個產生bug的隱患,因為很快就可能出現類似命名衝突之類的問題,再加上其他方面(專案越來越大等)的影響,程式碼越來越不易維護。
對於CSS來說,這意味著有問題的佈局。CSS特異性和CSS寬泛性之間,一直存在著如史詩般的對決。僅僅是因為每個選擇器都可能會影響到那些不想被影響的元素,使之產生了衝突。
基本所有程式語言都支援區域性作用域。和CSS朝夕相伴的JavaScript有AMD, CommonJS和最終確定的ES6 modules。但是我們並沒有一個可以模組化CSS的方法。
對於一個高質量專案來說,獨立的UI元件(也就是元件化)非常重要的 — 每個元件小巧獨立,可以拼合成複雜的頁面,這讓我們節省了很多的工作。但是我們始終有一個疑問,如何防止全域性命名衝突那?
解決方法
因為有前人的探尋,現在我們有Object-Oriented CSS, BEM, SMACSS等等,這些都是非常棒並且非常有用的方法。他們通過增加字首的辦法,解決了命名衝突的問題。
通過增加字首的辦法解決命名衝突是個體力活(manual mangling)。我們手動的去編寫長長的選擇器。你也可以使用預編譯的css語言,但是它們並沒有從根本上解決問題(還是體力活)。下面是我們用BEM規範書寫的一個獨立元件(對於現有的除BEM之外的方法,思想上基本也是這樣):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* 普通 CSS */ .article { font-size: 16px; } .article__title { font-size: 24px; } /* 使用css預處理語言 */ .article { font-size: 16px; &__title { font-size: 24px; } } |
CSS模組(CSS Modules)
2015年出現了另外兩種方法的實現。分別是CSS-in-JS 和 CSS Modules。我們將主要談論後者。
CSS模組允許你將所有css class自動打碎,這是CSS模組(CSS Modules)的預設設定。然後生成一個JSON檔案(sources map)和原本的class關聯:
1 2 3 4 5 6 7 8 |
/* post.css */ .article { font-size: 16px; } .title { font-weight: 24px; } |
上面的post.css將會被轉換成類似下面這樣:
1 2 3 4 5 6 7 |
.xkpka { font-size: 16px; } .xkpkb { font-size: 24px; } |
被打碎替換的classes將被儲存在一個JSON物件中:
1 |
`{ "article": "xkpka", "title": "xkpkb" } ` |
在轉換完成後,你可以直接引用這個JSON物件到專案中,這樣就可以用之前寫過的class名來直接使用它了。
1 2 3 4 5 6 7 8 9 10 11 12 |
import styles from './post.json'; class Post extends React.Component { render() { return ( <div className={ styles.article }> <div className={ styles.title }>…</div> … </div> ); } } |
更多給力的功能, 可以看看 這篇非常好的文章.
不光是保留了之前提到的幾種方法的優點,還自動解決了元件css分離的問題。這就是CSS模組(CSS Modules),聽起來非常不錯吧!
到這裡,我們有遇到了另一個問題: 我們現在的CSS Modules相關工具,只能在客戶端(瀏覽器)使用,把它放到一個非Node.js的服務端環境中是十分十分困難的。
PostCSS-modules
為了在服務端和客戶端都能使用CSS Modules,我寫了個PostCSS-modules,它是一個PostCSS外掛,讓你可以在服務端使用模組化的CSS,並且服務端語言可以是Ruby, PHP, Python 或者其他語言。
PostCSS是一個CSS前處理器,它是用JS實現的。它支援靜態檢查CSS,支援變數和混入(mixins),能讓你使用現在還未被瀏覽器支援的未來CSS語法,內聯影像等等。例如使用最為廣泛的Autoprefixer,它只是PostCSS的一個外掛。
如果你使用Autoprefixer, 其實你早就在用PostCSS了。所以,新增PostCSS-modules到你的專案依賴列表,並不是一件難事。我先給你打個樣(例項),用Gulp and EJS,其實你可以用任何語言做類似的事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// Gulpfile.js var gulp = require('gulp'); var postcss = require('gulp-postcss'); var cssModules = require('postcss-modules'); var ejs = require('gulp-ejs'); var path = require('path'); var fs = require('fs'); function getJSONFromCssModules(cssFileName, json) { var cssName = path.basename(cssFileName, '.css'); var jsonFileName = path.resolve('./build', cssName + '.json'); fs.writeFileSync(jsonFileName, JSON.stringify(json)); } function getClass(module, className) { var moduleFileName = path.resolve('./build', module + '.json'); var classNames = fs.readFileSync(moduleFileName).toString(); return JSON.parse(classNames)[className]; } gulp.task('css', function() { return gulp.src('./css/post.css') .pipe(postcss([ cssModules({ getJSON: getJSONFromCssModules }), ])) .pipe(gulp.dest('./build')); }); gulp.task('html', ['css'], function() { return gulp.src('./html/index.ejs') .pipe(ejs({ className: getClass }, { ext: '.html' })) .pipe(gulp.dest('./build')); }); gulp.task('default', ['html']); |
我們只需要執行gulp任務,就能得到轉換後的CSS檔案和JSON檔案,然後就可以在EJS模版裡面用了:
1 2 3 4 |
<article class="<%= className('post', 'article') %>"> <h1 class="<%= className('post', 'title') %>">Title</h1> ... </article> |
如果你想看看實際的程式碼,我在GitHub給你準備了個example。更多的例子可以看PostCSS-modules和CSS Modules
輕鬆編寫可維護的CSS,沒有臃腫的mixins。長長的字首將成為歷史,歡迎來到未來的CSS世界。