撰寫 PostCSS 外掛程式

文件

支援

步驟 1:建立一個概念

撰寫新的 PostCSS 外掛程式將有助於你在許多領域的工作

步驟 2:建立一個專案

撰寫外掛程式有兩種方式

對於私人外掛程式

  1. postcss/ 資料夾中建立一個新檔案,並將其命名為你的外掛程式。
  2. 從我們的樣板程式碼複製 外掛程式樣板

對於公開外掛程式

  1. 使用 PostCSS 外掛樣板 中的指南建立外掛目錄。
  2. 在 GitHub 或 GitLab 上建立儲存庫。
  3. 在那裡發布您的程式碼。
module.exports = (opts = {}) => {
  // Plugin creator to check options or prepare caches
  return {
    postcssPlugin: 'PLUGIN NAME'
    // Plugin listeners
  }
}
module.exports.postcss = true

步驟 3:尋找節點

大多數的 PostCSS 外掛會做兩件事

  1. 在 CSS 中尋找某些東西(例如 will-change 屬性)。
  2. 變更找到的元素(例如在 will-change 之前插入 transform: translateZ(0) 作為舊瀏覽器的多重載入)。

PostCSS 將 CSS 解析成節點樹(我們稱之為 AST)。此樹可能包含

您可以使用 AST Explorer 瞭解 PostCSS 如何將不同的 CSS 轉換成 AST。

您可以透過將方法新增到外掛物件來尋找具有特定類型的所有節點

module.exports = (opts = {}) => {
  return {
    postcssPlugin: 'PLUGIN NAME',
    Once (root) {
      // Calls once per file, since every file has single Root
    },
    Declaration (decl) {
      // All declaration nodes
    }
  }
}
module.exports.postcss = true

以下是 外掛事件 的完整清單。

如果您需要具有特定名稱的宣告或 at-rule,可以使用快速搜尋

    Declaration: {
      color: decl => {
        // All `color` declarations
      }
      '*': decl => {
        // All declarations
      }
    },
    AtRule: {
      media: atRule => {
        // All @media at-rules
      }
    }

對於其他情況,您可以使用正規表示式或特定剖析器

其他用於分析 AST 的工具

別忘了正規表示式和剖析器是繁重的任務。在使用繁重的工具檢查節點之前,您可以使用 String#includes() 進行快速測試

if (decl.value.includes('gradient(')) {
  let value = valueParser(decl.value)
  …
}

有兩種類型的監聽器:進入和離開。OnceRootAtRuleRule 將在處理子節點之前被呼叫。OnceExitRootExitAtRuleExitRuleExit 則在處理節點內的所有子節點之後被呼叫。

您可能希望在監聽器之間重複使用一些資料。您可以使用執行階段定義的監聽器來執行此操作

module.exports = (opts = {}) => {
  return {
    postcssPlugin: 'vars-collector',
    prepare (result) {
      const variables = {}
      return {
        Declaration (node) {
          if (node.variable) {
            variables[node.prop] = node.value
          }
        },
        OnceExit () {
          console.log(variables)
        }
      }
    }
  }
}

您可以使用 prepare() 動態產生監聽器。例如,使用 Browserslist 來取得宣告屬性。

步驟 4:變更節點

當您找到正確的節點時,您需要變更它們或在它們周圍插入/刪除其他節點。

PostCSS 節點具有類 DOM 的 API,可轉換 AST。查看我們的 API 文件。節點具有用於在周圍移動的方法(例如 Node#nextNode#parent),查看子節點(例如 Container#some),移除節點或在其中新增節點。

外掛程式的函式會在第二個引數中接收節點建立器

    Declaration (node, { Rule }) {
      let newRule = new Rule({ selector: 'a', source: node.source })
      node.root().append(newRule)
      newRule.append(node)
    }

如果您新增了新節點,複製 Node#source 以產生正確的原始碼對應非常重要。

外掛程式會重新檢視您變更或新增的所有節點。如果您變更任何子節點,外掛程式也會重新檢視父節點。只有 OnceOnceExit 不會再次被呼叫。

const plugin = () => {
  return {
    postcssPlugin: 'to-red',
    Rule (rule) {
      console.log(rule.toString())
    },
    Declaration (decl) {
      console.log(decl.toString())
      decl.value = 'red'
    }
  }
}
plugin.postcss = true

await postcss([plugin]).process('a { color: black }', { from })
// => a { color: black }
// => color: black
// => a { color: red }
// => color: red

由於訪客會在任何變更時重新拜訪節點,僅新增子節點將導致無限迴圈。若要防止此情況,您需要檢查是否已處理此節點

    Declaration: {
      'will-change': decl => {
        if (decl.parent.some(decl => decl.prop === 'transform')) {
          decl.cloneBefore({ prop: 'transform', value: 'translate3d(0, 0, 0)' })
        }
      }
    }

您也可以使用 Symbol 來標記已處理的節點

const processed = Symbol('processed')

const plugin = () => {
  return {
    postcssPlugin: 'example',
    Rule (rule) {
      if (!rule[processed]) {
        process(rule)
        rule[processed] = true
      }
    }
  }
}
plugin.postcss = true

第二個引數也有 result 物件可新增警告

    Declaration: {
      bad: (decl, { result }) {
        decl.warn(result, 'Deprecated property bad')
      }
    }

如果您的外掛程式依賴於另一個檔案,您可以將訊息附加到 result 以表示執行器(webpack、Gulp 等),當此檔案變更時,它們應該重新建置 CSS

    AtRule: {
      import: (atRule, { result }) {
        const importedFile = parseImport(atRule)
        result.messages.push({
          type: 'dependency',
          plugin: 'postcss-import',
          file: importedFile,
          parent: result.opts.from
        })
      }
    }

如果相依性是目錄,您應該改用 dir-dependency 訊息類型

result.messages.push({
  type: 'dir-dependency',
  plugin: 'postcss-import',
  dir: importedDir,
  parent: result.opts.from
})

如果您發現語法錯誤(例如,未定義的客製化屬性),您可以擲回特殊錯誤

if (!variables[name]) {
  throw decl.error(`Unknown variable ${name}`, { word: name })
}

步驟 5:對抗挫折

我討厭程式設計
我討厭程式設計
我討厭程式設計
它運作了!
我愛程式設計

即使是簡單的外掛程式,您也會遇到錯誤,而且至少需要 10 分鐘進行除錯。您可能會發現,簡單的原始構想在現實世界中無法運作,而且您需要變更所有內容。

別擔心。每個錯誤都是可以找到的,而且找到另一個解決方案可能會讓您的外掛程式變得更好。

從撰寫測試開始。外掛程式樣板在 index.test.js 中有一個測試範本。呼叫 npx jest 來測試您的外掛程式。

在您的文字編輯器中使用 Node.js 除錯器,或僅使用 console.log 來除錯程式碼。

PostCSS 社群可以協助您,因為我們都遇到過相同的問題。別害怕在 專屬頻道 中提問。

步驟 6:公開

當您的外掛程式準備好時,請在您的儲存庫中呼叫 npx clean-publishclean-publish 是用來從 npm 套件中移除開發組態的工具。我們已將此工具新增到我們的樣板外掛程式中。

使用 @postcss 標籤撰寫關於你的新外掛程式(即使它很小)的推文。或在 [我們的聊天室] 中說明你的外掛程式。我們將協助你行銷。

將你的新外掛程式新增至 PostCSS 外掛程式目錄。