PostCSSプラグインの作成

ドキュメント

サポート

ステップ1:アイデアを創出する

新しいPostCSSプラグインを作成することで、作業に役立つ分野はたくさんあります。

ステップ2:プロジェクトを作成する

プラグインを作成するには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プラグインは2つのことを行います。

  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)
  …
}

リスナーには、enterとexitの2種類があります。 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ノードには、ASTを変換するためのDOMのようなAPIがあります。 APIドキュメントをご覧ください。ノードには、周囲を移動するメソッド(Node#nextNode#parent など)、子を見るメソッド(Container#some など)、ノードを削除するメソッド、内部に新しいノードを追加するメソッドがあります。

プラグインのメソッドは、2番目の引数でノードクリエーターを受け取ります。

    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

2番目の引数には、警告を追加するための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-publish` を呼び出します。 `clean-publish` は、npmパッケージから開発設定を削除するためのツールです。このツールはプラグインボイラープレートに追加されています。

新しいプラグイン(小さなものであっても)について、`@postcss` メンションを付けてツイートしてください。または、[私たちのチャット]でプラグインについて教えてください。マーケティングのお手伝いをします。

新しいプラグインをPostCSSプラグインカタログに追加します。