カスタム構文の書き方

PostCSSは、CSSだけでなく、あらゆる構文のスタイルを変換できます。カスタム構文を作成することで、任意の形式のスタイルを変換できます。

カスタム構文の作成は、PostCSSプラグインの作成よりもはるかに難しいですが、素晴らしい冒険です。

PostCSS構文パッケージには3つのタイプがあります。

構文

カスタム構文の良い例は、SCSSです。一部のユーザーは、ベンダープレフィックスを追加したり、プロパティの順序を変更したりする必要がある場合など、PostCSSプラグインを使用してSCSSソースを変換したい場合があります。そのため、この構文はSCSS入力からSCSSを出力する必要があります。

構文APIは、parse関数とstringify関数を持つ非常にシンプルなプレーンオブジェクトです。

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

パーサー

パーサーの良い例は、不正な形式/壊れたCSSを解析するSafe Parserです。壊れた出力を生成しても意味がないため、このパッケージはパーサーのみを提供します。

パーサーAPIは、文字列を受け取り、RootノードまたはDocumentノードを返す関数です。2番目の引数は、PostCSSオプションを含むオブジェクトを受け取る関数です。

const postcss = require('postcss')

module.exports = function parse (css, opts) {
  const root = postcss.root()
  // Add other nodes to root
  return root
}

オープンソースのパーサーnpmパッケージでは、postcssは直接のdependenciesではなく、peerDependenciesに含める必要があります。

主要な理論

パーサーに関する書籍はたくさんありますが、CSS構文は非常に簡単なので、パーサーはプログラミング言語のパーサーよりもはるかにシンプルになります。心配しないでください。

デフォルトのPostCSSパーサーには、2つのステップがあります。

  1. 入力文字列を1文字ずつ読み取り、トークン配列を構築するトークナイザー。たとえば、スペース記号を['space', '\n ']トークンに結合し、文字列を['string', '"\"{"']トークンとして検出します。
  2. トークン配列を読み取り、ノードインスタンスを作成し、ツリーを構築するパーサー

パフォーマンス

入力の解析は、多くの場合、CSSプロセッサで最も時間のかかるタスクです。そのため、高速なパーサーを用意することが非常に重要です。

最適化の主なルールは、ベンチマークがなければパフォーマンスはないということです。独自のベンチマークを作成するには、PostCSSベンチマークを参照してください。

解析タスクの中で、トークン化ステップは多くの場合、最も時間がかかるため、そのパフォーマンスを優先する必要があります。残念ながら、クラス、関数、および高レベルの構造は、トークナイザーの速度を低下させる可能性があります。繰り返しのステートメントを含む汚いコードを書く準備をしてください。そのため、デフォルトのPostCSSトークナイザーを拡張することは困難です。コピー&ペーストが必要悪となります。

2番目の最適化は、文字列の代わりに文字コードを使用することです。

// Slow
string[i] === '{'

// Fast
const OPEN_CURLY = 123 // `{'
string.charCodeAt(i) === OPEN_CURLY

3番目の最適化は「高速ジャンプ」です。開始引用符が見つかった場合、indexOfを使用して次の終了引用符をはるかに高速に見つけることができます。

// Simple jump
next = string.indexOf('"', currentPosition + 1)

// Jump by RegExp
regexp.lastIndex = currentPosion + 1
regexp.test(string)
next = regexp.lastIndex

パーサーは、よく書かれたクラスにすることができます。コピーペーストやハードコアな最適化は必要ありません。デフォルトのPostCSSパーサーを拡張できます。

ノードソース

すべてのノードには、正しいソースマップを生成するためのsourceプロパティが必要です。このプロパティには、{ line, column }を持つstartプロパティとendプロパティ、およびInputインスタンスを持つinputプロパティが含まれています。

トークナイザーは元の位置を保存して、パーサーに値を伝達し、ソースマップが正しく更新されるようにする必要があります。

生の値

優れたPostCSSパーサーは、バイト単位で等しい出力を生成するために、すべての情報(スペース記号を含む)を提供する必要があります。それほど難しくはありませんが、ユーザー入力に敬意を払い、統合スモークテストを可能にします。

パーサーは、すべての追加記号をnode.rawsオブジェクトに保存する必要があります。これはオープンな構造であり、追加のキーを追加できます。たとえば、SCSSパーサーは、コメントタイプ(/* */または//)をnode.raws.inlineに保存します。

デフォルトのパーサーは、CSS値からコメントとスペースを削除します。元の値とコメントをnode.raws.value.rawに保存し、ノード値が変更されていない場合はそれを使用します。

テスト

もちろん、PostCSSエコシステムのすべてのパーサーにはテストが必要です。

パーサーがCSS構文を拡張するだけの場合(SCSSSafe Parserなど)、PostCSS Parser Testsを使用できます。これには、ユニットテストと統合テストが含まれています。

文字列化

スタイルガイドジェネレーターは、文字列化の良い例です。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文字列化のように、このクラスを拡張するだけで十分な場合がほとんどです。

ビルダー関数

ビルダー関数は、2番目の引数としてstringify関数に渡されます。たとえば、デフォルトのPostCSS文字列化クラスは、それをthis.builderプロパティに保存します。

ビルダーは、出力部分文字列とソースノードを受け取り、この部分文字列を最終出力に追加します。

一部のノードには、途中に他のノードが含まれています。たとえば、ルールには、先頭に{、内部に多くの宣言、および末尾に}があります。

このような場合は、3番目の引数として'start'または'end'文字列をビルダー関数に渡す必要があります。

this.builder(rule.selector + '{', rule, 'start')
// Stringify declarations inside
this.builder('}', rule, 'end')

生の値

優れたPostCSSカスタム構文は、すべての記号を保存し、変更がなかった場合はバイト単位で等しい出力を提供します。

そのため、すべてのノードには、スペース記号などを格納するためのnode.rawsオブジェクトがあります。

ソースコードに関連し、CSS構造に関連しないすべてのデータは、Node#rawsに配置する必要があります。たとえば、postcss-scssは、インラインコメント(/* comment */ではなく// comment)のブールマーカーをComment#raws.inlineに保持します。

これらの生のプロパティが存在しない場合があるため、注意してください。一部のノードは手動で構築されているか、別の親ノードに移動されたときにインデントを失っている可能性があります。

そのため、デフォルトの文字列化には、他のノードによって生のプロパティを自動検出するためのraw()メソッドがあります。たとえば、他のノードを見てインデントサイズを検出し、それを現在のノードの深さで乗算します。

テスト

文字列化にもテストが必要です。

PostCSS Parser Testsのユニットテストと統合テストケースを使用できます。入力CSSと、パーサーと文字列化後のCSSを比較するだけです。