如何撰寫自訂語法
PostCSS 可以轉換任何語法的樣式,而不僅限於 CSS。透過撰寫自訂語法,您可以轉換任何所需格式的樣式。
撰寫自訂語法比撰寫 PostCSS 外掛程式困難許多,但這是一場很棒的冒險。
有 3 種 PostCSS 語法套件
- 剖析器將輸入字串剖析為節點樹。
- 字串化器透過節點樹產生輸出字串。
- 語法包含剖析器和字串化器。
語法
自訂語法的良好範例是 SCSS。有些使用者可能想要使用 PostCSS 外掛程式轉換 SCSS 原始碼,例如當他們需要加入廠商前綴或變更屬性順序時。因此,此語法應從 SCSS 輸入輸出 SCSS。
語法 API 是非常簡單的純粹物件,具有 parse
和 stringify
函式
module.exports = {
parse: require('./parse'),
stringify: require('./stringify')
}
剖析器
剖析器的良好範例是 安全剖析器,它會剖析格式錯誤/損毀的 CSS。由於產生損毀的輸出毫無意義,因此此套件僅提供剖析器。
剖析器 API 是接收字串並傳回 Root
或 Document
節點的函式。第二個參數是接收包含 PostCSS 選項的物件的函式。
const postcss = require('postcss')
module.exports = function parse (css, opts) {
const root = postcss.root()
// Add other nodes to root
return root
}
對於開源剖析器,npm 套件必須在 peerDependencies
中具有 postcss
,而不能在直接 dependencies
中具有。
主要理論
關於解析器的書籍有很多;但別擔心,因為 CSS 語法非常簡單,因此解析器會比程式語言解析器簡單許多。
預設的 PostCSS 解析器包含兩個步驟
- 分詞器會逐字元讀取輸入字串,並建立一個代碼陣列。例如,它會將空白符號加入到
['space', '\n ']
代碼,並偵測字串到['string', '"\"{"']
代碼。 - 解析器會讀取代碼陣列,建立節點實例,並建立樹狀結構。
效能
解析輸入通常是 CSS 處理器中最耗時的任務。因此,擁有快速的解析器非常重要。
最佳化的主要規則是,沒有基準測試就沒有效能。您可以查看 PostCSS 基準測試 來建立自己的基準測試。
在解析任務中,分詞步驟通常會花費最多時間,因此應優先考慮其效能。不幸的是,類別、函式和高階結構會減慢分詞器的速度。請準備好使用重複陳述撰寫雜亂的程式碼。這就是為什麼難以擴充預設 PostCSS 分詞器 的原因;複製和貼上將是必要的惡。
第二個最佳化是使用字元碼,而不是字串。
// Slow
string[i] === '{'
// Fast
const OPEN_CURLY = 123 // `{'
string.charCodeAt(i) === OPEN_CURLY
第三個最佳化是「快速跳躍」。如果您找到開啟引號,您可以透過 indexOf
更快地找到下一個關閉引號
// Simple jump
next = string.indexOf('"', currentPosition + 1)
// Jump by RegExp
regexp.lastIndex = currentPosion + 1
regexp.test(string)
next = regexp.lastIndex
解析器可以是一個寫得很好的類別。在那裡不需要複製貼上和硬核最佳化。您可以擴充預設 PostCSS 解析器。
Node 原始碼
每個節點都應該有 source
屬性,以產生正確的原始碼對應表。此屬性包含具有 { line, column }
的 start
和 end
屬性,以及具有 Input
實例的 input
屬性。
您的分詞器應該儲存原始位置,以便您可以將值傳播到解析器,以確保原始碼對應表正確更新。
原始值
一個好的 PostCSS 解析器應該提供所有資訊(包含空白符號),以產生位元組對位元組相等的輸出。這並不困難,但尊重使用者的輸入並允許整合式煙霧測試。
一個解析器應該將所有額外的符號儲存在 node.raws
物件中。這是一個開放的結構,你可以新增額外的金鑰。例如,SCSS 解析器 將註解類型(/* */
或 //
)儲存在 node.raws.inline
中。
預設的解析器會從註解和空白中清除 CSS 值。它將原始值連同註解儲存在 node.raws.value.raw
中,並在節點值未變更時使用它。
測試
當然,PostCSS 生態系統中的所有解析器都必須有測試。
如果你的解析器只是延伸 CSS 語法(例如 SCSS 或 安全解析器),你可以使用 PostCSS 解析器測試。它包含單元和整合測試。
字串化器
樣式指南產生器是一個字串化器的良好範例。它會產生包含 CSS 組件的輸出 HTML。對於此用例,不需要解析器,因此套件應該只包含一個字串化器。
字串化器 API 比解析器 API 複雜一點。PostCSS 會產生原始碼對應,因此字串化器不能只傳回字串。它必須將每個子字串與其原始碼節點連結起來。
字串化器是一個接收 Root
或 Document
節點和建構器回呼的函式。然後它會使用每個節點的字串和節點實例呼叫建構器。
module.exports = function stringify (root, builder) {
// Some magic
const string = decl.prop + ':' + decl.value + ';'
builder(string, decl)
// Some science
};
主要理論
PostCSS 預設字串化器 只是一個類別,針對每個節點類型有一個方法,以及許多偵測原始屬性的方法。
在大部分情況下,只要擴充此類別就已足夠,例如在 SCSS 字串化器 中。
建構函式
建構函式將作為第二個引數傳遞給 stringify
函式。例如,預設的 PostCSS 字串化器類別會將其儲存到 this.builder
屬性。
建構函式接收輸出子字串和原始節點,將此子字串附加到最終輸出。
有些節點包含中間的其他節點。例如,規則在開頭有一個 {
、中間有許多宣告和一個關閉的 }
。
對於這些情況,您應將第三個引數傳遞給建構函式:'start'
或 'end'
字串
this.builder(rule.selector + '{', rule, 'start')
// Stringify declarations inside
this.builder('}', rule, 'end')
原始值
良好的 PostCSS 自訂語法會儲存所有符號,並在沒有變更的情況下提供逐位元組相等的輸出。
這就是每個節點都有 node.raws
物件來儲存空白符號等的原因。
所有與原始碼相關的資料,而非 CSS 結構,都應在 Node#raws
中。例如,postcss-scss
會在 Comment#raws.inline
中保留內嵌註解的布林標記(// comment
,而非 /* comment */
)。
請小心,因為有時這些原始屬性不會存在;有些節點可能是手動建置的,或者在移至其他父節點時可能會失去其縮排。
這就是預設字串化器有一個 raw()
方法,可藉由其他節點自動偵測原始屬性的原因。例如,它會查看其他節點以偵測縮排大小,然後將其乘以目前的節點深度。
測試
字串化器也必須有測試。
您可以使用 PostCSS Parser Tests 中的單元和整合測試案例。只要將輸入 CSS 與解析器和字串化器後的 CSS 進行比較即可。