如何撰寫自訂語法

PostCSS 可以轉換任何語法的樣式,而不僅限於 CSS。透過撰寫自訂語法,您可以轉換任何所需格式的樣式。

撰寫自訂語法比撰寫 PostCSS 外掛程式困難許多,但這是一場很棒的冒險。

有 3 種 PostCSS 語法套件

語法

自訂語法的良好範例是 SCSS。有些使用者可能想要使用 PostCSS 外掛程式轉換 SCSS 原始碼,例如當他們需要加入廠商前綴或變更屬性順序時。因此,此語法應從 SCSS 輸入輸出 SCSS。

語法 API 是非常簡單的純粹物件,具有 parsestringify 函式

module.exports = {
  parse:     require('./parse'),
  stringify: require('./stringify')
}

剖析器

剖析器的良好範例是 安全剖析器,它會剖析格式錯誤/損毀的 CSS。由於產生損毀的輸出毫無意義,因此此套件僅提供剖析器。

剖析器 API 是接收字串並傳回 RootDocument 節點的函式。第二個參數是接收包含 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 解析器包含兩個步驟

  1. 分詞器會逐字元讀取輸入字串,並建立一個代碼陣列。例如,它會將空白符號加入到 ['space', '\n '] 代碼,並偵測字串到 ['string', '"\"{"'] 代碼。
  2. 解析器會讀取代碼陣列,建立節點實例,並建立樹狀結構。

效能

解析輸入通常是 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 }startend 屬性,以及具有 Input 實例的 input 屬性。

您的分詞器應該儲存原始位置,以便您可以將值傳播到解析器,以確保原始碼對應表正確更新。

原始值

一個好的 PostCSS 解析器應該提供所有資訊(包含空白符號),以產生位元組對位元組相等的輸出。這並不困難,但尊重使用者的輸入並允許整合式煙霧測試。

一個解析器應該將所有額外的符號儲存在 node.raws 物件中。這是一個開放的結構,你可以新增額外的金鑰。例如,SCSS 解析器 將註解類型(/* *///)儲存在 node.raws.inline 中。

預設的解析器會從註解和空白中清除 CSS 值。它將原始值連同註解儲存在 node.raws.value.raw 中,並在節點值未變更時使用它。

測試

當然,PostCSS 生態系統中的所有解析器都必須有測試。

如果你的解析器只是延伸 CSS 語法(例如 SCSS安全解析器),你可以使用 PostCSS 解析器測試。它包含單元和整合測試。

字串化器

樣式指南產生器是一個字串化器的良好範例。它會產生包含 CSS 組件的輸出 HTML。對於此用例,不需要解析器,因此套件應該只包含一個字串化器。

字串化器 API 比解析器 API 複雜一點。PostCSS 會產生原始碼對應,因此字串化器不能只傳回字串。它必須將每個子字串與其原始碼節點連結起來。

字串化器是一個接收 RootDocument 節點和建構器回呼的函式。然後它會使用每個節點的字串和節點實例呼叫建構器。

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 進行比較即可。