如何從 SASS 無痛轉移到 POSTCSS
先說結論吧...目前這是不可能的(被歐),先別急著左鍵跳出啊,接下來要聊一下:為什麼要轉,轉得時候遇到什麼樣的問題,以及最後為什麼放棄了?是一個實作失敗的全紀錄。
先談談 SASS
SASS 在官網上形容自己是 most powerful CSS extension language,表示 sass 是基本 css 語法的延伸,提供很多更方便的寫法來撰寫你需要的樣式。他的作法是這樣:
" 先用 sass 定義的語法完成你的樣式表 .scss
,接著再使用 sass 的 preprocessor,把檔案轉成一般的 .css
檔案。(通常透過 task runner,如 grunt、gulp) "
preprocessor 在做什麼?
以下幾個例子是目前 css 做不到的,所以需要 css preprocessor 的地方。
-
定義變數
將重複用到的值,定義變數會比較方便處理,你不會想要輸入:%s/OLD_VALUE/NEW_VALUE/gc
來代換樣式吧? -
定義 mixin
有些樣式,是多種 css 屬性設定集合起來的,這種情況就很適合用 mixin。另外也可以用在 vendor prefix 的需求。 -
載入其他樣式表
上面所定義的包括像變數、mixin 等等必須要全部先 require 進來到同一個檔案才能夠使用。另外這樣也方便管理所有會使用到的 dependency。 -
運算子
提供像width: $compoment-width / 2;
這種運算變數的功能
另外還有幾個像 Nested
、Partial
及 Inheritance
功能,sass 的文件說明的相當詳細
為何要轉?
個人都是用 SASS 的語法寫樣式,所以相當習慣它的寫法。再加上 bootstrap v4 是使用 SASS。我在開發用戶端時,在樣式的部份,作法都是這樣:
" 一個 scss 檔上面列出所有需要的 bootstrap 樣式檔,然後在加上自己的樣式。"
/* ./bootstrap/ 有 boostrap v4 的 source */
/* import bootstrap 的核心檔 */
@import './bootstrap/_normalize.scss';
...
/* import 需要的 bootstrap component */
@import './bootstrap/_navbar.scss';
...
/* import 自己的樣式檔 */
@import './bootstrap/_my-style.scss';
...
一直以來這樣的作法使用 SASS 是沒有什麼問題的,但 SASS 有幾個問題:
- 慢
- 需要 Ruby
- 無法決定要對樣式原始檔作哪些事,SASS 會作全部的 patch
這時候 postcss 出現了。
postcss 的原則
首先 postcss 是用 javascript 所寫的,所以就不用為了只要轉換 sass 而安裝 Ruby。
還有它的作法和 webpack 及 karma 很相似,就是**「需要作哪些事,再使用對應的 plugin」**。這樣的特性,第一是不會有冗贅的 dependency 和動作,所以質輕,而且速度快。第二,因為自己需要知道要用哪些 plugin,所以對自己樣式的寫法會更了解。
好,接下來開始嘗試用 postcss 來轉換 sass。
sass 2 postcss
以 grunt 舉例,下面是 GruntFile 裡的設定:
postcss: {
default: {
options:{
processors: [
// 列出 processor
],
parser: require('postcss-scss') // 使用的 parser
},
src: 'app.scss',
dest: 'app.out.css'
}
}
首先,因為要轉換 sass,所以使用 postcss-scss plugin,並指定給 options.parser
。
照理說 postcss-scss
的目的是要正常的 parse sass 語法,並讓其他 processor 使用,但我使用的結果,它只有讓 sass 的註解語法生效:
// 這是 sass 的 comment
// parse 過後
/* 這是 sass 的 comment */
所以只好嘗試加上其他的 plugin 來完成剩下的部份。
因為 app.scss
中有列出所有的 dependency,所以我首先需要讓 @import
作用,postcss-partial-import plugin 用起來沒問題(不知道什麼原因用 postcss-import
來 import 的話,sass 不會先被 parse...)
import ok 的話,接下來就用 postcss-sassy-mixin 完美的處理掉 mixin 的語法和其中的變數代換,最後再使用 postcss-simple-vars 把檔案中的變數代換成值。
postcss: {
default: {
options:{
processors: [
// 列出 processor
require('postcss-partial-import')(),
require('postcss-sassy-mixins')(),
require('postcss-simple-vars')()
],
parser: require('postcss-scss') // 使用的 parser
},
src: 'app.scss',
dest: 'app.out.css'
}
}
目前看來還行
但接下來當在處理運算子的部份時,就找不到現成的 plugin 可以使用了...更何況還有 Nested
、Partial
及 Inheritance
這幾個語法還沒處理到。
所以我決定放棄了...
截長補短
是的,我放棄了,是放棄完全用 postcss 取代掉 SASS,但還是可以把 SASS 沒有的功能,使用 postcss 來完成啊!
這是妥協後決定的方案,先一部分的流程開始使用 postcss 來轉換樣式檔,既然 postcss 著重在「彈性」這項特性,未來如果 css 圈子有什麼變化,應該也可以很快代換才是。
所以現在 build 的流程,會先用 SASS 產生合法的 css,最後再用 postcss 的 autoprefixer 及 cssnano 兩個 plugin 來做到 vendor prefix 及 minify。
不斷更迭的 css 語法
為什麼 postcss 要轉換 sass 會這麼困難呢...?首先跟 SASS 的角色有關,不只 SASS,還有 LESS 都是之前比較知名的 preprocessor,他們的語法,其實都是自己訂的,並不是標準規範。
從 2015 年,W3C 開始著手規劃新一代的 css 標準,會加入許多 preprocessor 所提供的功能,在 cssnext 這個網站上有列出大致的走向。
另外其實 postcss 有所謂的「sassy」plugin,比如 precss,稱為 sassy 就表示語法只是很像 SASS,但並不支援 SASS 的語法。
為什麼不和 SASS 統一呢?可能的原因很多,並不清楚。有可能是背後有其他政治因素(?),抑或者是 sassy 的語法是改良後的 SASS,被人認為是比較易讀、好懂的?舉兩者在使用 mixin 的差別:
/* SASS 定義 mixin */
@mixin icon ($name) {
padding-left: 16px;
&::after {
content: "";
background-url: url(/icons/$(name).png);
}
}
/* 使用 mixin */
div {
@include icon('jcc');
}
/* 'sassy' 定義 mixin */
@define-mixin icon $name {
padding-left: 16px;
&::after {
content: "";
background-url: url(/icons/$(name).png);
}
}
/* 使用 mixin */
div {
@mixin icon jcc;
}
兩者非常相像,但有決定性的不同,孰優孰劣見人見智(個人覺得 sassy 比較易讀一些。)
如果不統一的理由是後者的話,說不定哪天 SASS 會把語法更新成所謂的 sassy 方式。如果真的這樣,就完全能夠「無痛」的從 SASS 轉移到 postcss 了!